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,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