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,353 @@
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/kernel/require_relative'
22
+ require_relative 'ui/choose_plugins_widget'
23
+
24
+ module Ruber
25
+
26
+ =begin rdoc
27
+ Dialog where the user can choose the plugins to load and the directories where
28
+ to look for plugins.
29
+
30
+ The main functionality is provided by the main widget, of class ChoosePluginsWidget.
31
+ =end
32
+ class ChoosePluginsDlg < KDE::Dialog
33
+
34
+ slots :write_settings
35
+
36
+ =begin rdoc
37
+ Creates a new +ChoosePluginsDlg+ and initializes it using both the settings
38
+ in the configuration file and the loaded plugins.
39
+ =end
40
+ def initialize parent = nil
41
+ super
42
+ self.caption = "Choose plugins"
43
+ self.buttons = Ok | Cancel | Apply | Default | Reset
44
+ self.main_widget = ChoosePluginsWidget.new
45
+ enable_button_apply false
46
+ main_widget.connect(SIGNAL('plugins_changed()')){enable_button_apply true}
47
+ main_widget.connect(SIGNAL('directories_changed()')){enable_button_apply true}
48
+ connect self, SIGNAL(:applyClicked), self, SLOT(:write_settings)
49
+ connect self, SIGNAL(:okClicked), self, SLOT(:write_settings)
50
+ connect self, SIGNAL(:defaultClicked), main_widget, SLOT(:apply_defaults)
51
+ end
52
+
53
+ # See ChoosePluginsWidget#plugins
54
+ def plugins
55
+ main_widget.plugins
56
+ end
57
+
58
+ private
59
+
60
+ =begin rdoc
61
+ Writes the settings to the configuration file and disables the Apply button
62
+ =end
63
+ def write_settings
64
+ main_widget.write_settings
65
+ enable_button_apply false
66
+ end
67
+
68
+ =begin rdoc
69
+ Override the default behaviour when the user presses the Ok or Apply buttons and
70
+ there are problems with dependencies (unresolved dependencies or circular dependencies).
71
+ In this case, it displays a message box where the user can choose whether he
72
+ truly wants to save the settings or not. If he chooses to write the settings,
73
+ the usual behaviour is followed. Otherwise, the method does nothing.
74
+ =end
75
+ def slotButtonClicked btn
76
+ if btn == Ok || btn == Apply and !main_widget.deps_satisfied?
77
+ msg = "There are dependencies problems among plugins. Are you sure you want to accept these settings?"
78
+ if KDE::MessageBox.question_yes_no(nil, msg) == KDE::MessageBox::Yes
79
+ super
80
+ else return
81
+ end
82
+ else super
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ =begin rdoc
89
+ Widget which implements most of the functionality of the ChoosePluginsDlg class.
90
+
91
+ It contains a list of plugins in the current search path, where the user can choose
92
+ the plugins to load (using checkboxes), and a list of directories where the user
93
+ can choose where to look for plugins.
94
+
95
+ In the plugin list, chosen plugins are shown with a selected mark, while plugins
96
+ which will be loaded to satisfy the dependencies of chosen plugins will be shown
97
+ with a partial mark.
98
+
99
+ Whenever the user changes the chosen plugins, the dependencies are re-computed.
100
+ If there's a problem, the user is warned with a message box.
101
+ =end
102
+ class ChoosePluginsWidget < Qt::Widget
103
+
104
+ signals :directories_changed, :plugins_changed
105
+
106
+ slots :add_directory, :remove_directory, :write_settings, :apply_defaults,
107
+ 'plugin_toggled(QStandardItem*)'
108
+
109
+ =begin rdoc
110
+ Creates a new ChoosePluginsWidget.
111
+
112
+ The list of directories where to look for plugins is read from the configuration
113
+ file. The chosen plugins are read from the configuration file, but excluding all
114
+ those which aren't currently loaded (the reason to do so is that a chosen plugin
115
+ might have failed to load and thus it should not appear in the chosen list). Also,
116
+ any plugin which is included in the chosen list but whose PDF can't be found is
117
+ excluded.
118
+
119
+ Dependencies are then computed, and dependencies of the chosen plugins are marked
120
+ as such. If a dependency problem occurs, the user is warned and all plugins are
121
+ deselected.
122
+ =end
123
+ def initialize parent = nil
124
+ super
125
+ @ui = Ui::ChoosePluginsWidget.new
126
+ @ui.setupUi self
127
+
128
+ dirs = Ruber[:config][:general].plugin_dirs
129
+
130
+ @chosen_plugins = Ruber[:config][:general, :plugins].map(&:to_sym)
131
+ loaded = Ruber[:components].plugins.map(&:plugin_name)
132
+ @chosen_plugins.delete_if{|i| !loaded.include? i}
133
+
134
+ read_plugins dirs
135
+ res = find_deps(:warning) do |e|
136
+ "There were problems making dependencies. #{create_failure_message e}\nAll plugins will be deselected"
137
+ end
138
+ if res
139
+ @chosen_plugins.clear
140
+ @needed_plugins = []
141
+ end
142
+ m = Qt::StandardItemModel.new @ui.plugins
143
+ @ui.plugins.model = m
144
+
145
+ connect m, SIGNAL('itemChanged(QStandardItem*)'), self, SLOT('plugin_toggled(QStandardItem*)')
146
+
147
+ def m.flags idx
148
+ Qt::ItemIsSelectable|Qt::ItemIsEnabled| Qt::ItemIsUserCheckable
149
+ end
150
+
151
+ @url = KDE::UrlRequester.new self
152
+ @ui.directories.custom_editor = @url.custom_editor
153
+ @url.mode = KDE::File::Directory | KDE::File::LocalOnly
154
+ connect @ui.directories, SIGNAL(:changed), self, SLOT(:slot_directories_changed)
155
+ @ui.directories.items = dirs
156
+
157
+ fill_plugin_list
158
+ end
159
+
160
+
161
+ def sizeHint #:nodoc:
162
+ Qt::Size.new 600, 550
163
+ end
164
+
165
+ =begin rdoc
166
+ Writes the settings to the configuration file. The directories listed in the
167
+ directory widget are stored in the <i>general/plugin_dirs</i> entry, while
168
+ the chosen plugins are stored in the <i>general/plugins</i> entry. Plugins
169
+ which haven't been chosen but are dependencies of the chosen ones aren't stored.
170
+ =end
171
+ def write_settings
172
+ dirs = @ui.directories.items
173
+ Ruber[:config][:general,:plugin_dirs] = dirs
174
+ plugins = []
175
+ @ui.plugins.model.each_row do |r|
176
+ plugins << r[0].data.to_string if r[0].fully_checked?
177
+ end
178
+ Ruber[:config][:general, :plugins] = plugins
179
+ Ruber[:config].write
180
+ end
181
+
182
+ =begin rdoc
183
+ Sets both the chosen plugins and the plugin directories to their default values
184
+ (see Appplication::DEFAULT_PLUGIN_PATHS and Application::DEFAULT_PLUGINS). If a
185
+ dependency problem occurs, the user is warned and all plugins are deselected
186
+ =end
187
+ def apply_defaults
188
+ dirs = Ruber[:config].default :general, :plugin_dirs
189
+ @chosen_plugins = Ruber[:config].default( :general, :plugins).split(',').map{|i| i.to_sym}
190
+ read_plugins dirs
191
+ res = find_deps(:warning) do |e|
192
+ "There were problems making dependencies. #{create_failure_message e}\nAll plugins will be deselected"
193
+ end
194
+ if res
195
+ @chosen_plugins.clear
196
+ @needed_plugins = []
197
+ end
198
+ @ui.directories.clear
199
+ @ui.directories.items = dirs
200
+
201
+ fill_plugin_list
202
+ end
203
+
204
+ =begin rdoc
205
+ Returns *false* if there's a dependency problem among the chosen plugins and
206
+ *true* otherwise.
207
+ =end
208
+ def deps_satisfied?
209
+ begin
210
+ find_deps
211
+ true
212
+ rescue ComponentManager::UnresolvedDep, ComponentManager::CircularDep
213
+ false
214
+ end
215
+ end
216
+
217
+ =begin rdoc
218
+ Returns a hash containing both the chosen plugins and their dependencies. The
219
+ keys are the plugin names, while the values are the directories of the plugins.
220
+ =end
221
+ def plugins
222
+ @ui.plugins.model.enum_for(:each_row).map do |name, _, dir|
223
+ [name.data.to_string.to_sym, dir.text] if name.checked?
224
+ end.compact.to_h
225
+ end
226
+
227
+ private
228
+
229
+ =begin rdoc
230
+ Finds all the plugins in subdirectories of directories in the _dir_ array,
231
+ reads their PDFs and stores the data in the <tt>@plugins_files</tt> and
232
+ <tt>@plugin_data</tt> instance variables. Also removes from the <tt>@chosen</tt>
233
+ instance variable the plugins whose PDF wasn't found.
234
+ =end
235
+ def read_plugins dirs
236
+ @plugins_files = ComponentManager.find_plugins dirs, true
237
+ @plugin_data = @plugins_files.map{|_, v| [v.name, v]}.to_h
238
+ @chosen_plugins.delete_if{|i| !@plugin_data[i]}
239
+ end
240
+
241
+ =begin rdoc
242
+ Finds the dependencies of the chosen plugins and stores them in the
243
+ <tt>@needed_plugins</tt> instance variable. If a dependency error occurs, if a
244
+ block was passed, a message box of type _type_ is displayed. The text of the
245
+ message box is obtained by calling the block with the exception as argument. If
246
+ an error occurs and no block is passed, the exception is passed on.
247
+
248
+ This method returns *nil* if no error occurs and the value returned by the
249
+ message box otherwise.
250
+ =end
251
+ def find_deps msg_type = :warning
252
+ chosen_data = @chosen_plugins.map{|i| @plugin_data[i]}
253
+ begin @needed_plugins = ComponentManager.fill_dependencies chosen_data, @plugin_data.values
254
+ rescue ComponentManager::UnresolvedDep, ComponentManager::CircularDep => e
255
+ if block_given? then KDE::MessageBox.send(msg_type, yield(e))
256
+ else raise
257
+ end
258
+ end
259
+ nil
260
+ end
261
+
262
+ =begin rdoc
263
+ Fills the list of plugins with the names, descriptions and paths of all plugins
264
+ found and marks each one according to whether it is among the chosen plugins,
265
+ the needed plugins or neither.
266
+
267
+ It also resizes the columns of the plugin list widget so that they match the
268
+ size of the contents.
269
+ =end
270
+ def fill_plugin_list
271
+ m = @ui.plugins.model
272
+ m.clear
273
+ m.horizontal_header_labels = %w[Name Description Directory]
274
+ @plugins_files.each do |k, v|
275
+ name = Qt::StandardItem.new v.about.human_name
276
+ name.data = Qt::Variant.new(k.to_s)
277
+ desc = Qt::StandardItem.new v.about.description
278
+ dir = Qt::StandardItem.new v.directory
279
+ m.append_row [name, desc, dir]
280
+ end
281
+ update_plugin_status
282
+ 3.times{|i| @ui.plugins.resize_column_to_contents i}
283
+ end
284
+
285
+ =begin rdoc
286
+ Changes the contents of the <tt>@chosen_plugins</tt> and <tt>@needed_plugins</tt>
287
+ instance variable to reflect the checked status of the plugins in the plugin list
288
+ widget. Emits the <tt>plugins_changed</tt> signal.
289
+ =end
290
+ def update_plugin_status
291
+ @ui.plugins.model.each_row do |name, desc, dir|
292
+ plug_name = name.data.to_string.to_sym
293
+ state = if @chosen_plugins.include? plug_name then Qt::Checked
294
+ elsif @needed_plugins.include? plug_name then Qt::PartiallyChecked
295
+ else Qt::Unchecked
296
+ end
297
+ name.check_state = state
298
+ end
299
+ emit plugins_changed
300
+ end
301
+
302
+ =begin rdoc
303
+ Updates the plugin status to reflect the changes made to the plugin corresponding
304
+ to the item _it_.
305
+ =end
306
+ def plugin_toggled it
307
+ name = it.data.to_string
308
+ if it.check_state == Qt::Checked then @chosen_plugins << name.to_sym
309
+ else @chosen_plugins.delete name.to_sym
310
+ end
311
+ find_deps{|e| create_failure_message( e) + "\nPlease, be sure to correct the problem before accepting changes" }
312
+ update_plugin_status
313
+ end
314
+
315
+ =begin rdoc
316
+ Updates the plugin widgets and emits the {#directories_changed signal}
317
+
318
+ If the dependencies aren't respected, the user is warned with a message box.
319
+ @return [nil]
320
+ =end
321
+ def slot_directories_changed
322
+ new_dirs = @ui.directories.items
323
+ find_deps{|e| create_failure_message( e) + "\nPlease, be sure to correct the problem before accepting changes" }
324
+ fill_plugin_list
325
+ emit directories_changed
326
+ nil
327
+ end
328
+ slots :slot_directories_changed
329
+
330
+ =begin rdoc
331
+ Creates an appropriate failure message for the exception _e_, which should be
332
+ an instance of ComponentManager::DependencyError. It is meant to generate consistent
333
+ error messages for all the places in the widget where a dependency error can
334
+ happen.
335
+ =end
336
+ def create_failure_message e
337
+ case e
338
+ when ComponentManager::UnresolvedDep
339
+ deps = e.missing.map do |p1, p2|
340
+ "#{d}, needed by #{pl.join ', '}"
341
+ end
342
+ "Some dependencies couldn't be satisifed:\n#{deps.join "\n"}"
343
+ when ComponentManager::CircularDep
344
+ deps = e.missing.map do |p1, p2|
345
+ "#{p1} and #{p2}"
346
+ end
347
+ "There were circular dependencies between the following plugins:\n#{deps.join "\n"}"
348
+ end
349
+ end
350
+
351
+ end
352
+
353
+ end
@@ -0,0 +1,524 @@
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
+ # Special comments used here to mark things to do:
22
+ # OBSOLETE: method to remove (or likely so)
23
+ # MOVE: method which should be moved somewhere else (to another class)
24
+
25
+ require 'forwardable'
26
+
27
+ require 'ruber/plugin_like'
28
+ require 'ruber/gui_states_handler'
29
+
30
+ require 'ruber/main_window/main_window_internal'
31
+ require 'ruber/main_window/main_window_actions'
32
+
33
+ require 'ruber/main_window/status_bar'
34
+ require 'ruber/main_window/workspace'
35
+
36
+ module Ruber
37
+
38
+ =begin rdoc
39
+ The application's main window. It is made of a menu bar, a tool bar, a workspace
40
+ and a status bar. The workspace (see Workspace) is the main window's central widget
41
+ and where most of the user interaction happens. It contains the editors and the
42
+ tool widgets.
43
+ =end
44
+ class MainWindow < KParts::MainWindow
45
+
46
+ include PluginLike
47
+
48
+ include GuiStatesHandler
49
+
50
+ extend Forwardable
51
+ ##
52
+ # :method: current_document_changed(QObject* obj) [SIGNAL]
53
+ # Signal emitted whenever the current document changes. _obj_ is the new current
54
+ # document and will be <tt>Qt::NilObject</tt> if there's no current document.
55
+
56
+ ##
57
+ # :method: active_editor_changed(QObject* obj) [SIGNAL]
58
+ # Signal emitted whenever the active editor changes _obj_ is the new active editor
59
+ # and will be <tt>Qt::NilObject</tt> if there's no active editor
60
+
61
+
62
+ signals 'current_document_changed(QObject*)', 'active_editor_changed(QObject*)'
63
+
64
+ slots :load_settings
65
+
66
+ =begin rdoc
67
+ The widget which contains the tool widgets.
68
+
69
+ The primary use of this method is to connect to the signals emitted from the workspace
70
+ when a tool widget is raised, shown or hidden. All of its other methods are also
71
+ provided by the MainWindow, so you shouldn't need to access the workspace to use
72
+ them.
73
+ =end
74
+ attr_reader :workspace
75
+
76
+ =begin rdoc
77
+ A hash with the data stored in the session manager by plugins. The hash is only
78
+ availlable after the <tt>restore</tt> method has been called (which means that it
79
+ will always be unavaillable if ruber wasn't started by the session manager). If
80
+ the hash isn't availlable, *nil* is returned
81
+
82
+ <b>NOTE:</b> only for use by Application when restoring the session
83
+ =end
84
+ attr_reader :last_session_data
85
+
86
+ =begin rdoc
87
+ Creates a new MainWindow. <i>_manager</i> is the component manager, while _pdf_
88
+ is the plugin description for this object.
89
+ =end
90
+ def initialize _manager, pdf
91
+ super nil, 0
92
+ initialize_plugin pdf
93
+ initialize_states_handler
94
+ self.central_widget = Workspace.new self
95
+ # We need the instance variable to use it with Forwardable
96
+ @workspace = central_widget
97
+ @views = central_widget.instance_variable_get :@views
98
+ @current_view = nil
99
+ @active_editor = nil
100
+ @auto_activate_editors = true
101
+ @editors_mapper = Qt::SignalMapper.new self
102
+ @ui_states = {}
103
+ @actions_state_handlers = Hash.new{|h, k| h[k] = []}
104
+ @about_plugin_actions = []
105
+ @last_session_data = nil
106
+ self.status_bar = StatusBar.new self
107
+ self.connect(SIGNAL('current_document_changed(QObject*)')) do |doc|
108
+ change_state 'current_document', !doc.nil_object?
109
+ end
110
+ connect Ruber[:components], SIGNAL('component_loaded(QObject*)'), self, SLOT('add_about_plugin_action(QObject*)')
111
+ connect Ruber[:components], SIGNAL('unloading_component(QObject*)'), self, SLOT('remove_about_plugin_action(QObject*)')
112
+ connect Ruber[:components], SIGNAL('unloading_component(QObject*)'), self, SLOT('remove_plugin_ui_actions(QObject*)')
113
+ connect @editors_mapper, SIGNAL('mapped(QWidget*)'), self, SLOT('remove_view(QWidget*)')
114
+ setup_actions action_collection
115
+ @views.connect(SIGNAL('currentChanged(int)')) do |idx|
116
+ if @auto_activate_editors then activate_editor idx
117
+ else activate_editor nil
118
+ end
119
+ end
120
+ Ruber[:projects].connect( SIGNAL('current_project_changed(QObject*)') ) do |prj|
121
+ change_state "active_project_exists", !prj.nil?
122
+ make_title
123
+ end
124
+ connect Ruber[:projects], SIGNAL('closing_project(QObject*)'), self, SLOT('close_project_files(QObject*)')
125
+
126
+ setup_GUI
127
+ create_shell_GUI true
128
+
129
+ config_obj = Ruber[:config].kconfig
130
+ apply_main_window_settings KDE::ConfigGroup.new( config_obj, 'MainWindow')
131
+ recent_files = KDE::ConfigGroup.new config_obj, 'Recent files'
132
+ action_collection.action("file_open_recent").load_entries recent_files
133
+ recent_projects = KDE::ConfigGroup.new config_obj, 'Recent projects'
134
+ action_collection.action("project-open_recent").load_entries recent_projects
135
+ status_bar.show
136
+ setup_initial_states
137
+ end
138
+
139
+ ##
140
+ # :method: add_tool
141
+ # Adds a tool widget to the main window. See Workspace#add_tool_widget for more information.
142
+ def_delegator :@workspace, :add_tool_widget, :add_tool
143
+
144
+ ##
145
+ # :method: remove_tool
146
+ # Removes a tool widget from the main window. See Workspace#remove_tool_widget for more information.
147
+ def_delegator :@workspace, :remove_tool_widget, :remove_tool
148
+
149
+ ##
150
+ # :method: toggle_tool
151
+ # Activates a tool widget if it was hidden and hides it if it was active.
152
+ # See Workspace#toggle_tool for more information.
153
+ def_delegators :@workspace, :toggle_tool
154
+
155
+ ##
156
+ # :method: raise_tool
157
+ # Activates a tool widget. See Workspace#raise_tool for more information.
158
+ def_delegators :@workspace, :raise_tool
159
+
160
+ ##
161
+ # :method: show_tool
162
+ # Displays a tool widget. See Workspace#show_tool for more information.
163
+ def_delegators :@workspace, :show_tool
164
+
165
+ ##
166
+ # :method: activate_tool
167
+ # Shows and gives focus to a tool. See Workspace#activate_tool for more information.
168
+ def_delegators :@workspace, :activate_tool
169
+
170
+ ##
171
+ # :method: hide_tool
172
+ # Hides a tool widget. See Workspace#hide_tool for more information.
173
+ def_delegators :@workspace, :hide_tool
174
+
175
+ ##
176
+ # :method: tool_widgets
177
+ # Returns a hash having the tool widgets as keys and their positions as values.
178
+ # See Workspace#tool_widgets for more information.
179
+ def_delegators :@workspace, :tool_widgets
180
+
181
+ =begin rdoc
182
+ Returns the editor view for the given document, creating it if needed
183
+
184
+ @param [Document, String, KDE::Url] doc the document. If it is a string representing
185
+ an absolute path, the document will be obtained using {DocumentList#document},
186
+ while for other strings it will be obtained using {DocumentList#document_with_name}
187
+ @return [EditorView,nil] an editor associated with the document or *nil* if _doc_
188
+ is interpreted as document name but no documents with that name exists
189
+ @raise [ArgumentError] if _doc_ is an absolute path or a @KDE::Url@ but the file
190
+ doesn't exist
191
+ =end
192
+ def editor_for! doc
193
+ doc = unless doc.is_a? Document
194
+ if doc.is_a? KDE::Url or doc.start_with? '/' then Ruber[:documents].document doc
195
+ else Ruber[:documents].document_with_name doc
196
+ end
197
+ end
198
+ return unless doc
199
+ create_editor_if_needed doc
200
+ end
201
+
202
+ =begin rdoc
203
+ Similar to <tt>editor_for!</tt> but it doesn't create the document or the editor
204
+ if they don't exist. If a Document for the string _doc_ doesn't exist, or if it
205
+ doesn't have an editor, returns *nil*.
206
+ =end
207
+ def editor_for doc
208
+ doc = Ruber[:documents][doc] if doc.is_a? String
209
+ return unless doc
210
+ doc.view
211
+ end
212
+
213
+ =begin rdoc
214
+ Returns the active editor, or *nil* if there's no active editor.
215
+
216
+ <b>Note:</b> strictly speaking, this is not the same as <tt>tab_widget.current_widget</tt>.
217
+ For an editor to be active, it's not enough to be in the current tab, but it also
218
+ need to having been its gui merged with the main window gui.
219
+
220
+ TODO: in all places this is called, change the name from <tt>current_view</tt> to
221
+ <tt>active_editor</tt>.
222
+ =end
223
+ def active_editor
224
+ @active_editor
225
+ end
226
+
227
+ =begin rdoc
228
+ Returns the editor in the current tab, or *nil* if there's no tab. Note that this
229
+ may not be the active editor.
230
+ =end
231
+ def current_editor
232
+ @views.current_widget
233
+ end
234
+
235
+ =begin rdoc
236
+ Activates an editor. _arg_ can be either the index of a widget in the tab widget
237
+ (for internal uses only) or the editor to activate, or *nil*. If the editor to
238
+ activate is not in the current tab, the tab which contains it will become the current
239
+ one. Activating an editor deactivates the previously active one.
240
+
241
+ Activating an editor means merging its GUI with the main window's GUI, changing
242
+ the title of the main window accordingly and telling the status bar to refer to
243
+ that view. Also, the <tt>current_document_changed</tt> and <tt>active_editor_changed</tt>
244
+ signals are emitted.
245
+ =end
246
+ def activate_editor arg
247
+ view = arg.is_a?(Integer) ? @views.widget( arg ) : arg
248
+ @views.current_widget = view if view and @views.current_widget != view
249
+ deactivate_editor @active_editor if @active_editor != view
250
+ @active_editor = view
251
+ if @active_editor
252
+ gui_factory.add_client @active_editor.doc.view.send(:internal)
253
+ @active_editor.document.activate
254
+ end
255
+ make_title
256
+ status_bar.view = @active_editor
257
+ emit current_document_changed @active_editor ? @active_editor.document : Qt::NilObject
258
+ emit active_editor_changed @active_editor || Qt::NilObject
259
+ end
260
+
261
+ =begin rdoc
262
+ Returns the document associated with the active editor or *nil* if there's no
263
+ current editor.
264
+
265
+ It's almost the same as <tt>active_editor.doc</tt>, but already takes care of
266
+ the case when <tt>active_editor</tt> is *nil*.
267
+ =end
268
+ def current_document
269
+ view = @views.current_widget
270
+ view ? view.doc : nil
271
+ end
272
+
273
+ =begin rdoc
274
+ Similar to <tt>editor_for!</tt> but, after having retrieved/created the editor
275
+ view, it activates and gives focus to it and moves the cursor to the line _line_
276
+ and column _col_ (both of them are 0-based indexes).
277
+ =end
278
+ def display_doc doc, line = nil, col = 0
279
+ ed = editor_for! doc
280
+ return unless ed
281
+ activate_editor ed
282
+ ed.go_to line, col if line and col
283
+ ed.set_focus
284
+ end
285
+ alias display_document display_doc
286
+
287
+ =begin rdoc
288
+ Executes the contents of the block with automatical editor activation disabled.
289
+ This should be used, for example, when more than one editor should be opened at
290
+ once. Without this, every new editor would become (for a little time) the active
291
+ one, with its gui merged with the main window and so on. This slows things down
292
+ and should be avoided. To do so, you use this method:
293
+ Ruber[:main_window].without_activating do
294
+ ed = nil
295
+ files.each do |f|
296
+ ed = Ruber[:main_window].editor_for! f
297
+ end
298
+ Ruber[:main_window].activate_editor ed
299
+ end
300
+ After the block has been called, if there is no editor, or if the current widget
301
+ in the tab widget is different from the active editor, the current widget will
302
+ be activated.
303
+ <b>Note:</b> automatical editor activation will be restored at the end of this
304
+ method (even if exceptions occur).
305
+ =end
306
+ def without_activating
307
+ begin
308
+ @auto_activate_editors = false
309
+ yield
310
+ ensure
311
+ @auto_activate_editors = true
312
+ if @views.current_index < 0 then activate_editor nil
313
+ elsif @views.current_widget != @active_editor
314
+ activate_editor @views.current_widget
315
+ end
316
+ end
317
+ end
318
+
319
+ =begin rdoc
320
+ Closes the editor _ed_, deleting its tab and closing the corresponding document
321
+ and activating the next tab if auto activating is on. If _ask_ is *true*, the document
322
+ will ask whether to save modifications, otherwise it won't
323
+ =end
324
+ def close_editor ed, ask = true
325
+ ed.document.close_view ed, ask
326
+ end
327
+
328
+ =begin rdoc
329
+ This method is meant to be called in situation where the user may want to save
330
+ a number of documents, for example when the application is quitting, as it avoids
331
+ displaying a dialog box for each modified document.
332
+
333
+ _docs_ can be either an array containing the documents which can be saved (documents
334
+ in it which aren't modified will be ignored) or *nil*. In this case, all the
335
+ open documents (with or without an open editor) will be taken into account.
336
+
337
+ It displays a dialog where the user can choose, among the documents passed as
338
+ first argument, which ones he wants to save. The user has three choiches:
339
+ * save some (or all) the files, then proceed with the operation which has caused
340
+ the dialog to be shown (for example, quitting the application)
341
+ * don't save any file and go on with the operation
342
+ * abort the operation.
343
+
344
+ In the first case, this method attempts to perform save the selected files. If
345
+ any of them can't be saved, the dialog to choose the files to save will be
346
+ displayed again, with only those files which couldn't be saved (after informing
347
+ the user of the failure). The user can again chose which of those files this method
348
+ should attempt to save again, or whether to abort the operation or skip saving.
349
+ This will be repeated until all files have been saved or the user gives up
350
+
351
+ In the second and third cases, the method simply returns respectively +true+ or
352
+ *false*.
353
+
354
+ The value returned by this method is *false* if the user choose to abort the
355
+ operation and *true* otherwise. Calling methods should act appropriately (for
356
+ example, if this is called from a method which closes all documents and returns
357
+ *false*, the documents should't be closed. If it returns true, instead, they
358
+ should be closed, without asking again to save them).
359
+
360
+ <b>Note:</b> if _docs_ only contains non-modified documents, this method will
361
+ do nothing and immediately return *true*.
362
+ =end
363
+ def save_documents docs = nil
364
+ docs ||= Ruber[:docs]
365
+ to_save = docs.select{|d| d.modified?}
366
+ until to_save.empty?
367
+ dlg = SaveModifiedFilesDlg.new to_save, self
368
+ case dlg.exec
369
+ when KDE::Dialog::Yes
370
+ to_save = Ruber[:docs].save_documents dlg.to_save
371
+ unless to_save.empty?
372
+ msg = "The following documents couldn't be saved: #{to_save.join "\n"}\nPlease, choose how to proceed"
373
+ KDE::MessageBox.sorry nil, KDE.i18n(msg)
374
+ end
375
+ when KDE::Dialog::No then to_save.clear
376
+ when Qt::Dialog::Rejected then return false
377
+ end
378
+ end
379
+ true
380
+ end
381
+
382
+ =begin rdoc
383
+ Asks the user whether to save the modified documents and saves the chosen ones.
384
+ Returns *true* all the selected documents where saved and *false* if some of the
385
+ document couldn't be saved.
386
+ MOVE: this should be moved to DocumentList, as there might be documents without
387
+ a window
388
+ =end
389
+ def query_close
390
+ if Ruber[:app].session_saving
391
+ @session_data = Ruber[:components].session_data
392
+ end
393
+ true
394
+ end
395
+
396
+ =begin rdoc
397
+ Provides a standard interface to creating a project from a project file named _file_, automatically
398
+ handling the possible exceptions. In particular, a message box will be displayed
399
+ if the project file doesn't exist or if it exists but it's not a valid project file.
400
+ A message box is also displayed if <i>allow_reuse</i> is *false* and the project
401
+ list already contains a project corresponding to _file_. In all cases in which a
402
+ message box is displayed *nil* is returned. Otherwise, the +Project+ object
403
+ corresponding to +file+ is returned.
404
+
405
+ If a project was created from the project file, it will be made active and the
406
+ old active project (if any) will be closed.
407
+
408
+ <b>Note:</b> _file_ must be an absolute path.
409
+ =end
410
+ def safe_open_project file, allow_reuse = false
411
+ prj = Ruber[:projects][file]
412
+ if !allow_reuse and prj
413
+ text = "A project corresponding to the file #{file} is already open. Please, close it before attempting to open it again"
414
+ KDE::MessageBox.sorry self, KDE.i18n(text)
415
+ return nil
416
+ elsif prj then return prj
417
+ end
418
+ begin prj = Ruber[:projects].project file
419
+ rescue Project::InvalidProjectFile => ex
420
+ text = <<-EOS
421
+ #{file} isn't a valid project file. The error reported was:
422
+ #{ex.message}
423
+ EOS
424
+ KDE::MessageBox.sorry self, KDE.i18n(text)
425
+ return nil
426
+ rescue LoadError
427
+ KDE::MessageBox.sorry self, KDE.i18n(ex.message)
428
+ return nil
429
+ end
430
+ # The following two lines should be removed when we'll allow more than one project
431
+ # open at the same time
432
+ Ruber[:projects].current_project.close if Ruber[:projects].current_project
433
+ Ruber[:projects].current_project = prj
434
+ prj
435
+ end
436
+
437
+ =begin rdoc
438
+ Saves recent files and projects, the position of the splitters and the size of
439
+ the window.
440
+
441
+ TODO: since restoring window position seems to work correctly in Kate (even if
442
+ there's still an open bug, it seems to work, at least for me) see whether it's
443
+ still necessary to store the window size.
444
+ =end
445
+ def save_settings
446
+ @workspace.store_sizes
447
+ # TODO use Ruber[:config] as soon as it works
448
+ # config = Ruber[:config].kconfig
449
+ config = KDE::Global.config
450
+ recent_files = KDE::ConfigGroup.new config, 'Recent files'
451
+ action_collection.action("file_open_recent").save_entries recent_files
452
+ recent_projects = KDE::ConfigGroup.new config, 'Recent projects'
453
+ action_collection.action("project-open_recent").save_entries recent_projects
454
+ config.sync
455
+ c = Ruber[:config][:main_window]
456
+ c.window_size = rect.size
457
+ end
458
+
459
+ =begin rdoc
460
+ Loads the settings (in particular, the default script directory and the
461
+ default project directory)
462
+ =end
463
+ def load_settings
464
+ c = Ruber[:config][:general]
465
+ @default_script_dir = KDE::Url.from_path c.default_script_directory
466
+ @default_project_dir = KDE::Url.from_path c.default_project_directory
467
+ end
468
+
469
+ =begin rdoc
470
+ Gives the focus to the current editor view. _ed_ can be:
471
+ * an EditorView
472
+ * any argument acceptable by <tt>editor_for!</tt>
473
+ * *nil*
474
+ In the first two cases, the editor corresponding to _ed_ is activated and given
475
+ focus; in the last case the active editor is given focus.
476
+ =end
477
+ def focus_on_editor ed = nil
478
+ if ed
479
+ ed = editor_for! ed unless ed.is_a? EditorView
480
+ activate_editor ed if ed
481
+ end
482
+ @active_editor.set_focus if @active_editor
483
+ end
484
+
485
+ =begin rdoc
486
+ The directory where to save files which don't belong to a project
487
+ =end
488
+ # def scripts_directory
489
+ # @default_script_dir
490
+ # end
491
+
492
+ =begin rdoc
493
+ The directory where to create new projects by default
494
+ =end
495
+ def projects_directory
496
+ @default_project_dir
497
+ end
498
+
499
+ =begin rdoc
500
+ Executes the action with name _action_ contained in the main window's action collection.
501
+
502
+ See <tt>GuiPlugin#execute_action</tt> for more details
503
+ =end
504
+ def execute_action name, *args
505
+ data = plugin_description.actions[name.to_s]
506
+ if data
507
+ slot = data.slot.sub(/\(.*/, '')
508
+ instance_eval(data.receiver).send slot, *args
509
+ true
510
+ elsif (action = action_collection.action(name))
511
+ if action.class == KDE::ToggleAction then KDE::ToggleAction
512
+ action.instance_eval{emit toggled(*args)}
513
+ elsif action.class == KDE::Action
514
+ action.instance_eval{emit triggered()}
515
+ else action.instance_eval{emit triggered(*args)}
516
+ end
517
+ true
518
+ else false
519
+ end
520
+ end
521
+
522
+ end
523
+
524
+ end