ruber 0.0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. data/COPYING +339 -0
  2. data/INSTALL +137 -0
  3. data/LICENSE +8 -0
  4. data/bin/ruber +65 -0
  5. data/data/share/apps/ruber/core_components.yaml +31 -0
  6. data/data/share/apps/ruber/ruberui.rc +109 -0
  7. data/data/share/icons/ruber.png +0 -0
  8. data/data/share/pixmaps/ruby.png +0 -0
  9. data/icons/ruber-16.png +0 -0
  10. data/icons/ruber-32.png +0 -0
  11. data/icons/ruber-48.png +0 -0
  12. data/icons/ruber-8.png +0 -0
  13. data/lib/ruber/application/application.rb +288 -0
  14. data/lib/ruber/application/plugin.yaml +11 -0
  15. data/lib/ruber/component_manager.rb +899 -0
  16. data/lib/ruber/config/config.rb +82 -0
  17. data/lib/ruber/config/plugin.yaml +3 -0
  18. data/lib/ruber/document_project.rb +209 -0
  19. data/lib/ruber/documents/document_list.rb +416 -0
  20. data/lib/ruber/documents/plugin.yaml +4 -0
  21. data/lib/ruber/editor/document.rb +506 -0
  22. data/lib/ruber/editor/editor_view.rb +167 -0
  23. data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
  24. data/lib/ruber/exception_widgets.rb +245 -0
  25. data/lib/ruber/external_program_plugin.rb +397 -0
  26. data/lib/ruber/filtered_output_widget.rb +342 -0
  27. data/lib/ruber/gui_states_handler.rb +231 -0
  28. data/lib/ruber/kde_config_option_backend.rb +167 -0
  29. data/lib/ruber/kde_sugar.rb +249 -0
  30. data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
  31. data/lib/ruber/main_window/main_window.rb +524 -0
  32. data/lib/ruber/main_window/main_window_actions.rb +537 -0
  33. data/lib/ruber/main_window/main_window_internal.rb +239 -0
  34. data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
  35. data/lib/ruber/main_window/output_color_widget.rb +35 -0
  36. data/lib/ruber/main_window/plugin.yaml +58 -0
  37. data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
  38. data/lib/ruber/main_window/status_bar.rb +156 -0
  39. data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
  40. data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
  41. data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
  42. data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
  43. data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
  44. data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
  45. data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
  46. data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
  47. data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
  48. data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
  49. data/lib/ruber/main_window/workspace.rb +442 -0
  50. data/lib/ruber/output_widget.rb +1093 -0
  51. data/lib/ruber/plugin.rb +264 -0
  52. data/lib/ruber/plugin_like.rb +589 -0
  53. data/lib/ruber/plugin_specification.rb +106 -0
  54. data/lib/ruber/plugin_specification_reader.rb +451 -0
  55. data/lib/ruber/project.rb +493 -0
  56. data/lib/ruber/project_backend.rb +105 -0
  57. data/lib/ruber/projects/plugin.yaml +11 -0
  58. data/lib/ruber/projects/project_files_list.rb +314 -0
  59. data/lib/ruber/projects/project_files_widget.rb +301 -0
  60. data/lib/ruber/projects/project_list.rb +314 -0
  61. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
  62. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
  63. data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
  64. data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
  65. data/lib/ruber/qt_sugar.rb +673 -0
  66. data/lib/ruber/settings_container.rb +515 -0
  67. data/lib/ruber/settings_dialog.rb +244 -0
  68. data/lib/ruber/settings_dialog_manager.rb +503 -0
  69. data/lib/ruber/utils.rb +414 -0
  70. data/lib/ruber/yaml_option_backend.rb +159 -0
  71. data/outsider_files +15 -0
  72. data/plugins/autosave/autosave.rb +404 -0
  73. data/plugins/autosave/plugin.yaml +16 -0
  74. data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
  75. data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
  76. data/plugins/command/command.png +0 -0
  77. data/plugins/command/command.rb +74 -0
  78. data/plugins/command/plugin.yaml +11 -0
  79. data/plugins/find_in_files/find_in_files.rb +337 -0
  80. data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
  81. data/plugins/find_in_files/find_in_files_ui.rc +11 -0
  82. data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
  83. data/plugins/find_in_files/plugin.yaml +23 -0
  84. data/plugins/find_in_files/ui/config_widget.rb +58 -0
  85. data/plugins/find_in_files/ui/config_widget.ui +41 -0
  86. data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
  87. data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
  88. data/plugins/project_browser/plugin.yaml +10 -0
  89. data/plugins/project_browser/project_browser.rb +245 -0
  90. data/plugins/rake/plugin.yaml +39 -0
  91. data/plugins/rake/rake.png +0 -0
  92. data/plugins/rake/rake.rb +567 -0
  93. data/plugins/rake/rake_extension.rb +153 -0
  94. data/plugins/rake/rake_widgets.rb +615 -0
  95. data/plugins/rake/rakeui.rc +27 -0
  96. data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
  97. data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
  98. data/plugins/rake/ui/choose_task_widget.rb +77 -0
  99. data/plugins/rake/ui/choose_task_widget.ui +72 -0
  100. data/plugins/rake/ui/config_widget.rb +127 -0
  101. data/plugins/rake/ui/config_widget.ui +123 -0
  102. data/plugins/rake/ui/project_widget.rb +217 -0
  103. data/plugins/rake/ui/project_widget.ui +246 -0
  104. data/plugins/rspec/plugin.yaml +30 -0
  105. data/plugins/rspec/rspec.png +0 -0
  106. data/plugins/rspec/rspec.rb +945 -0
  107. data/plugins/rspec/rspec.svg +90 -0
  108. data/plugins/rspec/rspecui.rc +20 -0
  109. data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
  110. data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
  111. data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
  112. data/plugins/ruby_development/plugin.yaml +27 -0
  113. data/plugins/ruby_development/ruby_development.png +0 -0
  114. data/plugins/ruby_development/ruby_development.rb +453 -0
  115. data/plugins/ruby_development/ruby_developmentui.rc +19 -0
  116. data/plugins/ruby_development/ui/project_widget.rb +112 -0
  117. data/plugins/ruby_development/ui/project_widget.ui +108 -0
  118. data/plugins/ruby_runner/config_widget.rb +116 -0
  119. data/plugins/ruby_runner/plugin.yaml +26 -0
  120. data/plugins/ruby_runner/project_widget.rb +62 -0
  121. data/plugins/ruby_runner/ruby.png +0 -0
  122. data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
  123. data/plugins/ruby_runner/ruby_runner.rb +411 -0
  124. data/plugins/ruby_runner/ui/config_widget.rb +92 -0
  125. data/plugins/ruby_runner/ui/config_widget.ui +91 -0
  126. data/plugins/ruby_runner/ui/project_widget.rb +60 -0
  127. data/plugins/ruby_runner/ui/project_widget.ui +48 -0
  128. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
  129. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
  130. data/plugins/state/plugin.yaml +28 -0
  131. data/plugins/state/state.rb +520 -0
  132. data/plugins/state/ui/config_widget.rb +92 -0
  133. data/plugins/state/ui/config_widget.ui +89 -0
  134. data/plugins/syntax_checker/plugin.yaml +18 -0
  135. data/plugins/syntax_checker/syntax_checker.rb +662 -0
  136. data/ruber.desktop +10 -0
  137. data/spec/annotation_model_spec.rb +174 -0
  138. data/spec/common.rb +119 -0
  139. data/spec/component_manager_spec.rb +1259 -0
  140. data/spec/document_list_spec.rb +626 -0
  141. data/spec/document_project_spec.rb +373 -0
  142. data/spec/document_spec.rb +779 -0
  143. data/spec/editor_view_spec.rb +167 -0
  144. data/spec/external_program_plugin_spec.rb +676 -0
  145. data/spec/filtered_output_widget_spec.rb +642 -0
  146. data/spec/gui_states_handler_spec.rb +304 -0
  147. data/spec/kde_config_option_backend_spec.rb +214 -0
  148. data/spec/kde_sugar_spec.rb +101 -0
  149. data/spec/ktexteditor_wrapper_spec.rb +305 -0
  150. data/spec/output_widget_spec.rb +1703 -0
  151. data/spec/plugin_spec.rb +1393 -0
  152. data/spec/plugin_specification_reader_spec.rb +1765 -0
  153. data/spec/plugin_specification_spec.rb +401 -0
  154. data/spec/project_backend_spec.rb +172 -0
  155. data/spec/project_files_list_spec.rb +401 -0
  156. data/spec/project_list_spec.rb +511 -0
  157. data/spec/project_spec.rb +990 -0
  158. data/spec/qt_sugar_spec.rb +328 -0
  159. data/spec/settings_container_spec.rb +617 -0
  160. data/spec/settings_dialog_manager_spec.rb +773 -0
  161. data/spec/settings_dialog_spec.rb +419 -0
  162. data/spec/state_spec.rb +991 -0
  163. data/spec/utils_spec.rb +406 -0
  164. data/spec/workspace_spec.rb +869 -0
  165. data/spec/yaml_option_backend_spec.rb +246 -0
  166. metadata +284 -0
@@ -0,0 +1,244 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'ruber/settings_dialog_manager'
22
+
23
+ module Ruber
24
+
25
+ =begin rdoc
26
+ Dialog used to change the options contained in an SettingsContainer. It is a specialized
27
+ <tt>KDE::PageDialog</tt>.
28
+
29
+ The widgets added to the option container are displayed in pages corresponding to
30
+ the captions included in the widgets' description. If more than one widget has
31
+ the same caption, they're displayed in the same page, one below the other (using
32
+ a Qt::VBoxLayout). The icon used for each page is the first one found among the
33
+ widgets in that page.
34
+
35
+ The process of keeping the options in sync between the widgets and the option container
36
+ can be automatized using the functionality provided by the internal SettingsDialogManager.
37
+ If this automatic management can't be achieved for a particular option (for example,
38
+ because the value should be obtained combining the data of more than one widget),
39
+ you can still automatically manage the other widgets and integrate the manual
40
+ management of the widgets which need it with the automatic management. To do so,
41
+ you'll need to define a read_settings, a store_settings and a read_default_settings
42
+ method in your widget. In this case, you can access the dialog using the @settings_dialog
43
+ instance variable, which is created by this class when the widget is created.
44
+
45
+ ===Manual option management example
46
+ Suppose you have an option called <tt>:default_path</tt> in the :general group which stores a path. In
47
+ your widget, however, the path is split in two parts: the directory and the filename,
48
+ which are shown in two line edit widgets, called respectively <tt>default_dir_widget</tt>
49
+ and <tt>default_file_widget</tt>. Since the automatic option management system assumes
50
+ that each option corresponds to a single widget, you can't use it. So, you define
51
+ a <tt>read_settings</tt>, a <tt>store_settings</tt> and a <tt>read_default_settings</tt>
52
+ method in your widget class like this:
53
+
54
+ class MyWidget < Qt::Widget
55
+
56
+ def initialize parent = nil
57
+ super
58
+ @default_dir_widget = KDE::LineEdit.new self
59
+ @default_file_widget = KDE::LineEdit.new self
60
+ end
61
+
62
+ def read_settings
63
+ path = @settings_dialog.settings_container[:general, :default_path]
64
+ @default_dir_widget.text = File.dirname(path)
65
+ @default_file_widget.text = File.basename(path)
66
+ end
67
+
68
+ def store_settings
69
+ path = File.join @default_dir_widget.text, @default_file_widget.text
70
+ @settings_dialog.settings_container[:general, :default_path] = path
71
+ end
72
+
73
+ def read_default_settings
74
+ path = @settings_dialog.settings_container.default(:general, :default_path)
75
+ @default_dir_widget.text = File.dirname(path)
76
+ @default_file_widget.text = File.basename(path)
77
+ end
78
+
79
+ end
80
+
81
+ Note that the <tt>@settings_dialog</tt> instance variable has been automatically
82
+ created by the dialog when the widget has been created and contains a reference
83
+ to the dialog itself.
84
+
85
+ =end
86
+ class SettingsDialog < KDE::PageDialog
87
+
88
+ slots :store_settings, :read_default_settings
89
+
90
+ =begin rdoc
91
+ The option container. This method can be used by the widgets which need to define
92
+ the read_settings, store_settings and read_default_settings methods to access the
93
+ option container of the dialog.
94
+ =end
95
+ attr_accessor :container
96
+ alias_method :settings_container, :container
97
+
98
+ =begin rdoc
99
+ Creates a new SettingsDialog. The dialog will store settings in the SettingsContainer
100
+ _container_, manage the options _options_ and display the widgets _widgets_.
101
+ _options_ is an array of option objects (see SettingsContainer#add_option).
102
+ _widgets_ is an array containing
103
+ the description of the widgets to be displayed in the dialog (see SettingsContainer#add_widget).
104
+
105
+ This method defines a new instance variable for each widget it creates. The variable
106
+ is called @settings_dialog and contains a reference to the dialog itself. It can be
107
+ used from custom widgets' implementation of read_settings, store_settings and
108
+ read_default_settings methods to access the option container corresponding to
109
+ the dialog.
110
+ =end
111
+ def initialize container, options, widgets, title = nil
112
+ super Ruber[:main_window]
113
+ self.buttons = Ok|Apply|Cancel|Default
114
+ self.window_title = title if title
115
+ @container = container
116
+ grouped_widgets = Hash.new{|h, k| h[k] = []}
117
+ widgets.each{|w| grouped_widgets[w.caption] << w }
118
+ @page_items = []
119
+ @widgets = Hash.new{|h, k| h[k] = []}
120
+ grouped_widgets.keys.sort.each do |c|
121
+ icon = nil
122
+ page = Qt::Widget.new self
123
+ page.layout = Qt::VBoxLayout.new page
124
+ grouped_widgets[c].each do |w|
125
+ widget = if w.respond_to?(:code) and w.code then widget_from_code(w.code)
126
+ else widget_from_class(w.class_obj)
127
+ end
128
+ widget.parent = page
129
+ widget.instance_variable_set :@settings_dialog, self
130
+ @widgets[c] << widget
131
+ page.layout.add_widget widget
132
+ icon ||= KDE::Icon.new w.pixmap if w.pixmap rescue nil
133
+ end
134
+ @page_items << add_page(page, c)
135
+ @page_items[-1].icon = icon if icon
136
+ end
137
+ @manager = SettingsDialogManager.new self, options, @widgets.values.flatten
138
+ connect self, SIGNAL(:okClicked), self, SLOT(:store_settings)
139
+ connect self, SIGNAL(:applyClicked), self, SLOT(:store_settings)
140
+ connect self, SIGNAL(:defaultClicked), self, SLOT(:read_default_settings)
141
+ enable_button_apply false
142
+ end
143
+
144
+ =begin rdoc
145
+ Returns an array containing all the widgets created for the dialog, that is the
146
+ widgets created from the data in the third argument of the constructor.
147
+ =end
148
+ def widgets
149
+ res = []
150
+ @widgets.each_value{|a| res += a}
151
+ res
152
+ end
153
+
154
+ =begin rdoc
155
+ This method reads settings from the option container and displays them in the widgets.
156
+ It calls the read_settings method of the option dialog manager and the read_settings
157
+ method of each widgets which provide it.
158
+ =end
159
+ def read_settings
160
+ @manager.read_settings
161
+ @widgets.values.flatten.each{|w| w.read_settings if w.respond_to? :read_settings}
162
+ end
163
+
164
+ =begin rdoc
165
+ This method takes the values from the dialog widgets and stores them in to option container.
166
+ It calls the store_settings method of the option dialog manager and the store_settings
167
+ method of each widgets which provide it, then calls the write method of the container
168
+ so that the options are written to file.
169
+ =end
170
+ def store_settings
171
+ @manager.store_settings
172
+ @widgets.values.flatten.each{|w| w.store_settings if w.respond_to? :store_settings}
173
+ @container.write
174
+ end
175
+
176
+ =begin rdoc
177
+ This method works as read_settings except for the fact that it reads the default
178
+ values of the options instad of the values set by the user.
179
+ =end
180
+ def read_default_settings
181
+ @manager.read_default_settings
182
+ @widgets.values.flatten.each{|w| w.read_default_settings if w.respond_to? :read_default_settings}
183
+ end
184
+
185
+ =begin rdoc
186
+ Override of <tt>KDE::PageDialog#exec</tt> which makes sure the first page is the current
187
+ page and gives focus to the first widget on the page and reads the settings from
188
+ the option container.
189
+
190
+ This is needed because usually the dialog object isn't deleted after having been
191
+ closed, so the interface should be updated manually.
192
+ =end
193
+ def exec
194
+ read_settings
195
+ if @page_items[0]
196
+ self.current_page = @page_items[0]
197
+ self.current_page.widget.layout.item_at(0).widget.set_focus
198
+ end
199
+ super
200
+ end
201
+
202
+ =begin rdoc
203
+ Override of <tt>KDE::PageDialog#show</tt> which makes sure the first page is the current
204
+ page and gives focus to the first widget on the page and reads the settings from
205
+ the option container.
206
+
207
+ This is needed because usually the dialog object isn't deleted after having been
208
+ closed, so the interface should be updated manually.
209
+ =end
210
+ def show
211
+ read_settings
212
+ self.current_page = @page_items[0]
213
+ self.current_page.widget.layout.item_at(0).widget.set_focus
214
+ super
215
+ end
216
+
217
+ private
218
+
219
+ =begin rdoc
220
+ Method called to create a widget which is specified using a <tt>code</tt> entry
221
+ in the third argument to initialize. _code_ is the string containing the code.
222
+
223
+ It evaluates _code_ in the toplevel binding and returns the resulting object. Derived
224
+ classes may override it to change this behaviour (for example, they may want to
225
+ evaluate the code in another binding).
226
+ =end
227
+ def widget_from_code code
228
+ eval code, TOPLEVEL_BINDING
229
+ end
230
+
231
+ =begin rdoc
232
+ Method called to create a widget which is specified using the <tt>class_obj</tt> entry
233
+ in the third argument to initialize. _cls_ is the widget's class.
234
+
235
+ The widget is created simply calling new on _cls_ (no arguments are passed to it).
236
+ Derived classes may want to override it to change this behaviour (for example,
237
+ they may want to pass some argument to +new+).
238
+ =end
239
+ def widget_from_class cls
240
+ cls.new
241
+ end
242
+
243
+ end
244
+ end
@@ -0,0 +1,503 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'facets/string/snakecase'
22
+ require 'yaml'
23
+
24
+ module Ruber
25
+
26
+ =begin rdoc
27
+ Class which takes care of automatically syncronize (some of) the options in
28
+ an SettingsContainer with the widgets in the SettingsDialog.
29
+
30
+ In the following documentation, the widgets passed as argument to the constructor
31
+ will be called <i>upper level widgets</i>, because they're the topmost widgets
32
+ this class is interested in. The term <i>child widget</i> will be used to refer
33
+ to any widget which is child of an upper level widget, while the generic _widget_
34
+ can refer to both. The upper <i>level widget corresponding to </i> a widget is
35
+ the upper level widget the given widget is child of (or the widget itself if it
36
+ already is an upper level widget)
37
+
38
+ To syncronize options and widgets contents, this class looks among the upper level
39
+ widgets and their children for the widget which have an <tt>object_name</tt> of the form
40
+ <tt>_group__option</tt>, that is: an underscore, a group name, two underscores,
41
+ an option name (both the group and the option name can contain underscores. This
42
+ is the reason for which _two_ underscores are used to separate them). Such a
43
+ widget is associated with the option with name _name_ belonging to the group
44
+ _group_.
45
+
46
+ Having a widget associated with an option means three things:
47
+ 1. when the widget emits some signals (which mean that the settings have changed,
48
+ the dialog's Apply button is enabled)
49
+ 2. when the dialog is displayed, the widget is updated so that it displays the
50
+ value stored in the SettingsContainer
51
+ 3. when the Apply or the OK button of the dialog is clicked, the option in the
52
+ SettingsContainer is updated with the value of the widget.
53
+
54
+ For all this to happen, who writes the widget (or the upper level widget the widget
55
+ is child of) must give it several custom properties
56
+ (this can be done with the UI designer, if it is used to create the widget, or
57
+ by hand). These properties are:
58
+ * signal: it's a string containing the signal(s) on which the Apply button of the
59
+ dialog will be enabled. If more than one signal should be used, you can specify
60
+ them using a YAML array (that is, enclose them in square brackets and separate
61
+ them with a comma followed by one space). If the widget has only a single signal
62
+ with that name, you can omit the signal's signature, otherwise you need to include
63
+ it (for example, <tt>Qt::LineEdit</tt> has a single signall called <tt>textChanged</tt>,
64
+ so you can simply use that instead of <tt>textChanged(QString)</tt>. On the other
65
+ hand, <tt>Qt::ComboBox</tt> has two signals called +currentIndexChanged+, so
66
+ you must fully specify them: <tt>currentIndexChanged(QString)</tt> or
67
+ <tt>currentIndexChanged(int)</tt>).
68
+ * read: is the method to use to sync the value in the widget with that in
69
+ the SettingsContainer. The reason of the name "read" is that it is used to
70
+ read the value from the container.
71
+ * store: is the method to use to sync the value in the SettingsContainer with that
72
+ in the widget.
73
+ * access: a method name to derive the +read+ and the +store+ methods from. The
74
+ +store+ method has the same name as this property, while the +read+
75
+ method has the same name with ending "?" and "!" removed and an ending
76
+ "=" added.
77
+
78
+ In the documentation for this class, the term <i>read method</i> refers to the
79
+ method specified in the +read+ property or derived from the +access+ property by
80
+ adding the <tt>=</tt> to it. The term <i>store method</i> refers to the
81
+ method specified in the +store+ or +access+ property.
82
+
83
+ If the +read+, +store+ or +access+ properties start with a <tt>$</tt>, they'll be called
84
+ on the upper level widget corresponding to the widget, instead than on the widget
85
+ itself.
86
+
87
+ Not all of the above properties need to be specified. In particular, the +access+
88
+ property can't coexhist the +read+ and the +store+ properties. On the other hand,
89
+ you can't give only one of the +read+ and +store+ properties. If you omit the
90
+ +access+, +store+ and +read+ property entierely, and the +signal+ property only
91
+ contains one signal, then an +access+ property is automatically created using the
92
+ name of the signal after having removed from its end the strings 'Edited', 'Changed',
93
+ 'Modified', '_edited', '_changed', '_modified' (for example, if the signal is
94
+ '<tt>textChanged(QString)</tt>, then the +access+ property will become +text+.
95
+
96
+ If the +signal+ property hasn't been specified, a default one will be used, depending
97
+ on the class of the widgets. See the documentation for DEFAULT_PROPERTIES to see
98
+ which signals will be used for which classes. If neither the +access+ property
99
+ nor the +read+ and +store+ properties have been given, a default +access+ property
100
+ will also be used. If the class of the widget is not included in DEFAULT_PROPERTIES,
101
+ an exception will be raised.
102
+
103
+ A read method must accept one argument, which is the value of the option, and
104
+ display it in the corresponding widget in whichever way is appropriate. A store
105
+ method, instead, should take no arguments, retrieve the option value from the widget,
106
+ again using the most appropriate way, and return it.
107
+
108
+ Often, the default +access+ method is almost, but not completely, enough. For
109
+ example, if the widget is a <tt>KDE::UrlRequester</tt>, but you want to store
110
+ the option as a string, instead of using a <tt>KDE::Url</tt>, you'd need to create
111
+ a method whose only task is to convert a <tt>KDE::Url</tt> to a string and vice
112
+ versa. The same could happen with a symbol and a <tt>Qt::LineEdit</tt>. To avoid
113
+ such a need, this class also performs automatic conversions, when reading or storing
114
+ an option. It works this way: if the value to store in the +SettingsContainer+ or
115
+ in the widget are of a different class from the one previously contained there,
116
+ the +DEFAULT_CONVERSIONS+ hash is scanned for an entry corresponding to the two
117
+ classes and, if found, the value returned by the corresponding +Proc+ is stored
118
+ instead of the original one.
119
+
120
+ ===Example
121
+
122
+ Consider the following situation:
123
+
124
+ <b>Options:</b>
125
+ <tt>OpenStruct.new({:name => :number, :group => :G1, :default => 4})</tt>::
126
+ this is an option which contains an integral value, with default 4
127
+ <tt>OpenStruct.new({:name => :path, :group => :G1, :default => ENV[['HOME']]})</tt>::
128
+ this is an option which contains a string representing a path. The default value
129
+ is the user's home directory (contained in the environment variable HOME)
130
+ <tt>OpenStruct.new({:name => :list, :group => :G2, :default => %w[a b c]})</tt>::
131
+ this is an option which contains an array of strings, with default value
132
+ <tt>['a', 'b', 'c']</tt>.
133
+
134
+ <b>Widgets:</b>
135
+
136
+ There's a single upper level widget, of class +MyWidget+, which contains a
137
+ <tt>Qt::SpinBox</tt>, a <tt>KDE::UrlRequester</tt> and a <tt>Qt::LineEdit</tt>.
138
+ The value displayed in the spin box should be associated to the <tt>:number</tt>
139
+ option, while the url requester should be associated to the <tt>:path</tt> option.
140
+ The line edit widget should be associated with the <tt>:list</tt> option, by splitting
141
+ the text on commas.
142
+
143
+ We can make some observations:
144
+ * the spin box doesn't need anything except having its name set to match the
145
+ <tt>:number</tt> option: the default signal and access method provided by
146
+ <tt>DEFAULT_PROPERTIES</tt> are perfectly adequate to this situation.
147
+ * the url requester doesn't need any special settings, aside from the object name:
148
+ the default signal and access method provided by <tt>DEFAULT_PROPERTIES</tt> are
149
+ almost what we need and the only issue is that the methods take and return a
150
+ <tt>KDE::Url</tt> instead of a string. But since the <tt>DEFAULT_CONVERSIONS</tt>
151
+ contains conversion procs for this pair of classes, even this is handled automatically
152
+ * the line edit requires custom +read+ and +store+ methods (which can be specified
153
+ with a signle +access+ property), because there's no default conversion from
154
+ array to string and vice versa. The default signal, instead, is suitable for
155
+ our needs, so we don't need to specify one.
156
+
157
+ Here's how the class +MyWidget+ could be written (here, all widgets are created
158
+ manually. In general, it's more likely that you'd use the Qt Designer to create
159
+ it. In that case, you can set the widgets' properties using the designer itself).
160
+ Note that in the constructor we make use of the block form of the widgets' constructors,
161
+ which evaluates the given block in the new widget's context.
162
+
163
+ class MyWidget < Qt::Widget
164
+
165
+ def initialize parent = nil
166
+ super
167
+ @spin_box = Qt::SpinBox.new{ self.object_name = '_G1__number'}
168
+ @url_req = KDE::UrlRequester.new{self.object_name = '_G1__path'}
169
+ @line_edit = Qt::LineEdit.new do
170
+ self.object_name = '_G2__list'
171
+ set_property 'access', '$items'
172
+ end
173
+ end
174
+
175
+ # This is the store method for the list option. It takes the text in the line
176
+ # edit and splits it on commas, returning the array
177
+ def items
178
+ @line_edit.text.split ','
179
+ end
180
+
181
+ # This is the read method for the list option. It takes the array containing
182
+ # the value of the option (an array) as argument, then sets the text of the
183
+ # line edit to the string obtained by calling join on the array
184
+ def items= array
185
+ @line_edit.text = array.join ','
186
+ end
187
+
188
+ end
189
+
190
+ =end
191
+ class SettingsDialogManager < Qt::Object
192
+
193
+ =begin rdoc
194
+ A hash containing the default signal and access methods to use for a number of
195
+ classes, when the +signal+ property isn't given. The keys of the hash are the
196
+ classes, while the values are arrays of two elements. The first element is the
197
+ name of the signal, while the second is the name of the access method.
198
+ =end
199
+ DEFAULT_PROPERTIES = {
200
+ Qt::CheckBox => [ 'toggled(bool)', "checked?"],
201
+ Qt::PushButton => [ 'toggled(bool)', "checked?"],
202
+ KDE::PushButton => [ 'toggled(bool)', "checked?"],
203
+ KDE::ColorButton => [ 'changed(QColor)', "color"],
204
+ KDE::IconButton => [ 'iconChanged(QString)', "icon"],
205
+ Qt::LineEdit => [ 'textChanged(QString)', "text"],
206
+ KDE::LineEdit => [ 'textChanged(QString)', "text"],
207
+ KDE::RestrictedLine => [ 'textChanged(QString)', "text"],
208
+ Qt::ComboBox => [ 'currentIndexChanged(int)', "current_index"],
209
+ KDE::ComboBox => [ 'currentIndexChanged(int)', "current_index"],
210
+ KDE::ColorCombo => [ 'currentIndexChanged(int)', "color"],
211
+ Qt::TextEdit => [ 'textChanged(QString)', "text"],
212
+ KDE::TextEdit => [ 'textChanged(QString)', "text"],
213
+ Qt::PlainTextEdit => [ 'textChanged(QString)', "text"],
214
+ Qt::SpinBox => [ 'valueChanged(int)', "value"],
215
+ KDE::IntSpinBox => [ 'valueChanged(int)', "value"],
216
+ Qt::DoubleSpinBox => [ 'valueChanged(double)', "value"],
217
+ KDE::IntNumInput => [ 'valueChanged(int)', "value"],
218
+ KDE::DoubleNumInput => [ 'valueChanged(double)', "value"],
219
+ Qt::TimeEdit => [ 'timeChanged(QTime)', "time"],
220
+ Qt::DateEdit => [ 'dateChanged(QDate)', "date"],
221
+ Qt::DateTimeEdit => [ 'dateTimeChanged(QDateTime)', "date_time"],
222
+ Qt::Dial => [ 'valueChanged(int)', "value"],
223
+ Qt::Slider => [ 'valueChanged(int)', "value"],
224
+ KDE::DatePicker => [ 'dateChanged(QDate)', "date"],
225
+ KDE::DateTimeWidget => [ 'valueChanged(QDateTime)', "date_time"],
226
+ KDE::DateWidget => [ 'changed(QDate)', "date"],
227
+ KDE::FontComboBox => [ 'currentFontChanged(QFont)', "current_font"],
228
+ KDE::FontRequester => [ 'fontSelected(QFont)', "font"],
229
+ KDE::UrlRequester => [ 'textChanged(QString)', "url"]
230
+ }
231
+
232
+ =begin rdoc
233
+ Hash which contains the Procs used by <tt>convert_value</tt> to convert a value
234
+ from its class to another. Each key must an array of two classes, corresponding
235
+ respectively to the class to convert _from_ and to the class to convert _to_. The
236
+ values should be Procs which take one argument of class corresponding to the first
237
+ entry of the key and return an object of class equal to the second argument of the
238
+ key.
239
+
240
+ If you want to implement a new automatic conversion, all you need to do is to
241
+ add the appropriate entries here. Note that usually you'll want to add two entries:
242
+ one for the conversion from A to B and one for the conversion in the opposite
243
+ direction.
244
+ =end
245
+ DEFAULT_CONVERSIONS = {
246
+ [Symbol, String] => proc{|sym| sym.to_s},
247
+ [String, Symbol] => proc{|str| str.to_sym},
248
+ [String, KDE::Url] => proc{|str| KDE::Url.from_path(str)},
249
+ # KDE::Url#path_or_url returns nil if the KDE::Url is not valid, so, we use
250
+ # || '' to ensure the returned object is a string
251
+ [KDE::Url, String] => proc{|url| url.path_or_url || ''},
252
+ [String, Fixnum] => proc{|str| str.to_i},
253
+ [Fixnum, String] => proc{|n| n.to_s},
254
+ [String, Float] => proc{|str| str.to_f},
255
+ [Float, String] => proc{|x| x.to_s},
256
+ }
257
+
258
+ slots :settings_changed
259
+
260
+
261
+ =begin rdoc
262
+ Creates a new SettingsDialogManager. The first argument is the SettingsDialog whose
263
+ widgets will be managed by the new instance. The second argument is an array with
264
+ the option objects corresponding to the options which are candidates for automatic
265
+ management. The keys of the hash are the option
266
+ objects, which contain all the information about the options, while the values
267
+ are the values of the options. _widgets_ is a list of widgets where to look for
268
+ for widgets corresponding to the options.
269
+
270
+ When the +SettingsDialogManager+ instance is created, associations between options
271
+ and widgets are created, but the widgets themselves aren't updated with the values
272
+ of the options.
273
+ =end
274
+ def initialize dlg, options, widgets
275
+ super(dlg)
276
+ @widgets = widgets
277
+ @container = dlg.settings_container
278
+ @associations = {}
279
+ options.each{|o| setup_option o}
280
+ end
281
+
282
+ =begin rdoc
283
+ Updates all the widgets corresponding to an automatically managed option by calling
284
+ the associated reader methods, passing them the value read from the option container,
285
+ converted as described in the the documentation for SettingsDialogManager,
286
+ <tt>convert_value</tt> and <tt>DEFAULT_CONVERSIONS</tt>. It emits the
287
+ <tt>settings_changed</tt> signal with *true* as argument.
288
+ =end
289
+ def read_settings
290
+ @associations.each_pair do |o, data|
291
+ group, name = o[1..-1].split('__').map(&:to_sym)
292
+ value = @container.relative_path?(group, name) ? @container[group, name, :abs] : @container[group, name]
293
+ old_value = call_widget_method data
294
+ value = convert_value(value, old_value)
295
+ call_widget_method data, value
296
+ end
297
+ end
298
+
299
+ =begin rdoc
300
+ Updates all the automatically managed options by calling setting them to the values
301
+ returned by the associated 'store' methods,
302
+ converted as described in the the documentation for +SettingsDialogManager+,
303
+ <tt>convert_value</tt> and <tt>DEFAULT_CONVERSIONS</tt>. It emits the
304
+ <tt>settings_changed</tt> signal with *false* as argument.
305
+ =end
306
+ def store_settings
307
+ @associations.each_pair do |o, data|
308
+ group, name = o[1..-1].split('__').map(&:to_sym)
309
+ value = call_widget_method(data)
310
+ old_value = @container[group, name]
311
+ value = convert_value value, old_value
312
+ @container[group, name] = value
313
+ end
314
+ end
315
+
316
+ =begin rdoc
317
+ It works like <tt>read_settings</tt> except for the fact that it updates the
318
+ widgets with the default values of the options.
319
+ =end
320
+ def read_default_settings
321
+ @associations.each_pair do |o, data|
322
+ group, name = o[1..-1].split('__').map(&:to_sym)
323
+ value = @container.default(group, name)
324
+ old_value = call_widget_method data
325
+ value = convert_value(value, old_value)
326
+ call_widget_method data, value
327
+ end
328
+ end
329
+
330
+ private
331
+
332
+ =begin rdoc
333
+ Looks in the list of widgets passed to the constructor and their children for a
334
+ widget whose name corresponds to that of the option (see widget_name for the meaning
335
+ of _corresponds_). If one is found, the setup_automatic_option method is called
336
+ for that option. If no widget is found, nothing is done.
337
+ =end
338
+ def setup_option opt
339
+ name = widget_name opt.group, opt.name
340
+ widgets = @widgets.find! do |w|
341
+ if w.object_name == name then [w, w]
342
+ else
343
+ child = w.find_child(Qt::Widget, name)
344
+ child ? [child, w] : nil
345
+ end
346
+ end
347
+ setup_automatic_option opt, *widgets if widgets
348
+ end
349
+
350
+ =begin rdoc
351
+ Associates the widget _w_ with the option _option_. _ulw_ is the upper level
352
+ widget associated with _widget_. Associating a widget with an option means two
353
+ things:
354
+ * connecting each signal specified in the +signal+ property of the widget with
355
+ the settings_changed slot of *self*
356
+ * creating an entry in the <tt>@associations</tt> instance variable with the name
357
+ of the widget as key and a hash as value. Each hash has a +read+ and a +store+
358
+ entry, which are both arrays: the second element is the name of the +read+ and
359
+ +store+ method respectively, while the first is the widget on which it should
360
+ be called (which will be either _widget_ or the corresponding upper level widget,
361
+ _ulw_).
362
+
363
+ As explained in the documentation for this class, some of the properties may be
364
+ missing and be automatically determined. If one of the following situations happen,
365
+ +ArgumentError+ will be raised:
366
+ * no +signal+ property exist for _widget_ and its class is not in +DEFAULT_PROPERTIES+
367
+ * _widget_ doesn't specify neither the +access+ property nor the +read+ and the
368
+ +store+ properties and more than one signal is specified in the +signal+ property
369
+ * both the +access+ property and the +read+ and/or +store+ properties are specified
370
+ for _widget_
371
+ * the signature of one signal isn't given and it couldn't be determined automatically
372
+ because either no signal or more than one signals of _widget_ have that name
373
+ =end
374
+ def setup_automatic_option opt, widget, ulw
375
+ #Qt::Variant#to_string returns nil if the variant is invalid
376
+ signals = Array(YAML.load(widget.property('signal').to_string || '[]'))
377
+ #Qt::Variant#to_string returns nil if the variant is invalid
378
+ methods = %w[read store access].map{|m| prop = widget.property(m).to_string}
379
+ if signals.empty?
380
+ data = DEFAULT_PROPERTIES.fetch(widget.class) do
381
+ raise ArgumentError, "No default signal exists for class #{widget.class}, you need to specify one"
382
+ end
383
+ signals = [data[0]]
384
+ methods[2] = data[1] if methods == Array.new(3, nil)
385
+ end
386
+ signals.each_index do |i|
387
+ signals[i] = add_signal_signature signals[i], widget
388
+ connect widget, SIGNAL(signals[i]), self, SLOT(:settings_changed)
389
+ end
390
+ data = find_associated_methods signals, methods, widget, ulw
391
+ @associations[widget.object_name] = data
392
+ end
393
+
394
+ =begin rdoc
395
+ Returns the name of the widget corresponding to the option belonging to group
396
+ _group_ and with name _name_. The widget name has the form: underscore, group,
397
+ double underscore, name. For example if _group_ is :general and _name_ is _path_,
398
+ this method would return "_general__path"
399
+ =end
400
+ def widget_name group, name
401
+ "_#{group}__#{name}"
402
+ end
403
+
404
+ =begin rdoc
405
+ Returns the full signature of the signal with name _sig_ in the <tt>Qt::Object</tt>
406
+ obj. If _sig_ already has a signature, it is returned as it is. Otherwise, the list
407
+ of signals of _obj_ is searched for a signal whose name is equal to _sig_ and the
408
+ full name of that signal is returned. If no signal has that name, or if more than
409
+ one signal have that name, +ArgumentError+ is raised.
410
+ =end
411
+ def add_signal_signature sig, obj
412
+ return sig if sig.index '('
413
+ mo = obj.meta_object
414
+ reg = /^#{Regexp.quote sig}/
415
+ signals = mo.each_signal.find_all{|s| s.signature =~ reg}
416
+ raise ArgumentError, "Ambiguous signal name, '#{sig}'" if signals.size > 1
417
+ raise ArgumentError, "No signal with name '#{sig}' exist" if signals.empty?
418
+ signals.first.signature
419
+ end
420
+
421
+ =begin rdoc
422
+ Finds the read and store methods to associate to the widget _widget_. It returns
423
+ an hash with keys <tt>:read</tt> and <tt>:store</tt> and for values pairs of the
424
+ form <tt>[receiver, method]</tt>, where _method_ is the method to call to read
425
+ or store the option value and _receiver_ is the object to call the method on (it
426
+ may be either _widget_, if the corresponding property doesn't begin with +$+,
427
+ or its upper level widget, _ulw_, if the property begins with +$+).
428
+
429
+ _signals_ is an array with all the signals included in the +signal+ property of
430
+ the widget. _methods_ is an array with the contents of the +read+, +store+ and
431
+ +access+ properties, (in this order), converted to string (if one property is
432
+ missing, the corresponding entry will be *nil*). _widget_ is the widget for which
433
+ the association should be done and _ulw_ is the corresponding upper level widget.
434
+
435
+ See the documentation for <tt>setup_automatic_option</tt> for a description of
436
+ the situation on which an +ArgumentError+ exception is raised.
437
+ =end
438
+ def find_associated_methods signals, methods, widget, ulw
439
+ read, store, access = methods
440
+ if access and (read or store)
441
+ raise ArgumentError, "The widget #{widget.object_name} has both the access property and one or both of the store and read properties"
442
+ elsif access
443
+ read = access.sub(/[?!=]$/,'')+'='
444
+ store = access
445
+ elsif !access and !(store and read)
446
+ if signals.size > 1
447
+ raise ArgumentError, "When more signals are specified, you need to specify also the access property or both the read and store properties"
448
+ end
449
+ sig = signals.first
450
+ store ||= sig[/[^(]+/].snakecase.sub(/_(?:changed|modified|edited)$/, '')
451
+ read ||= sig[/[^(]+/].snakecase.sub(/_(?:changed|modified|edited)$/, '') + '='
452
+ end
453
+ data = {}
454
+ data[:read] = read[0,1] == '$' ? [ulw, read[1..-1]] : [widget, read]
455
+ data[:store] = store[0,1] == '$' ? [ulw, store[1..-1]] : [widget, store]
456
+ data
457
+ end
458
+
459
+ =begin rdoc
460
+ :call-seq:
461
+ manager.call_widget_method data
462
+ manager.call_widget_method data, value
463
+
464
+ Helper method which calls the 'read' or 'store' method for an association. If called
465
+ in the first form, calls the 'store' method, while in the first form it calls the
466
+ 'read' method passing _value_ as argument. _data_ is one of the hashes contained
467
+ in the <tt>@associations</tt> instance variable as values: it has the following
468
+ form: <tt>{:read => [widget, read_method], :store => [widget, store_method]}. In
469
+ both versions, it returns what the called method returned.
470
+ =end
471
+ def call_widget_method *args
472
+ data = args[0]
473
+ if args.size == 1 then data[:store][0].send data[:store][1]
474
+ else data[:read][0].send data[:read][1], args[1]
475
+ end
476
+ end
477
+
478
+ =begin rdoc
479
+ Converts the value _new_ so that it has the same class as _old_. To do so, it looks
480
+ in the +DEFAULT_CONVERSIONS+ hash for an entry whose key is an array with the class
481
+ of _new_ as first entry and the class of _old_ as second entry. If the key is found,
482
+ the corresponding value (which is a +Proc+), is called, passing it _new_ and the
483
+ method returns its return value. If the key isn't found (including the case when
484
+ _new_ and _old_ have the same class), _new_ is returned as it is.
485
+ =end
486
+ def convert_value new, old
487
+ prc = DEFAULT_CONVERSIONS[[new.class, old.class]]
488
+ if prc then
489
+ prc.call new
490
+ else new
491
+ end
492
+ end
493
+
494
+ =begin rdoc
495
+ Enables the Apply button of the dialog
496
+ =end
497
+ def settings_changed
498
+ parent.enable_button_apply true
499
+ end
500
+
501
+ end
502
+
503
+ end