ruber 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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