ruber 0.0.8 → 0.0.9

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 (81) hide show
  1. data/CHANGES +21 -0
  2. data/data/share/apps/ruber/ruberui.rc +3 -1
  3. data/lib/ruber/application/application.rb +22 -23
  4. data/lib/ruber/application/plugin.yaml +7 -2
  5. data/lib/ruber/{projects → application}/project_files_list.rb +0 -0
  6. data/lib/ruber/{projects → application}/project_files_widget.rb +0 -0
  7. data/lib/ruber/application/ui/project_files_rule_chooser_widget.rb +74 -0
  8. data/lib/ruber/{projects → application}/ui/project_files_rule_chooser_widget.ui +0 -0
  9. data/lib/ruber/application/ui/project_files_widget.rb +117 -0
  10. data/lib/ruber/{projects → application}/ui/project_files_widget.ui +0 -0
  11. data/lib/ruber/component_manager.rb +14 -9
  12. data/lib/ruber/editor/document.rb +35 -5
  13. data/lib/ruber/kde_sugar.rb +16 -0
  14. data/lib/ruber/main_window/choose_plugins_dlg.rb +7 -4
  15. data/lib/ruber/main_window/main_window.rb +131 -193
  16. data/lib/ruber/main_window/main_window_actions.rb +157 -58
  17. data/lib/ruber/main_window/main_window_internal.rb +145 -54
  18. data/lib/ruber/main_window/open_file_in_project_dlg.rb +4 -4
  19. data/lib/ruber/main_window/plugin.yaml +3 -6
  20. data/lib/ruber/main_window/ui/workspace_settings_widget.rb +2 -2
  21. data/lib/ruber/main_window/workspace.rb +62 -32
  22. data/lib/ruber/output_widget.rb +20 -16
  23. data/lib/ruber/pane.rb +11 -5
  24. data/lib/ruber/project.rb +27 -12
  25. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +2 -2
  26. data/lib/ruber/projects/ui/project_files_widget.rb +2 -2
  27. data/lib/ruber/utils.rb +37 -4
  28. data/lib/ruber/version.rb +1 -1
  29. data/lib/ruber/world/document_factory.rb +121 -0
  30. data/lib/ruber/world/document_list.rb +396 -0
  31. data/lib/ruber/world/environment.rb +470 -0
  32. data/lib/ruber/{main_window → world}/hint_solver.rb +1 -1
  33. data/lib/ruber/world/plugin.yaml +11 -0
  34. data/lib/ruber/world/project_factory.rb +131 -0
  35. data/lib/ruber/world/project_list.rb +265 -0
  36. data/lib/ruber/world/ui/workspace_settings_widget.rb +51 -0
  37. data/lib/ruber/{main_window → world}/ui/workspace_settings_widget.ui +0 -0
  38. data/lib/ruber/world/world.rb +307 -0
  39. data/plugins/auto_end/auto_end.rb +135 -9
  40. data/plugins/autosave/autosave.rb +4 -4
  41. data/plugins/find_in_files/find_in_files.rb +5 -5
  42. data/plugins/find_in_files/find_in_files_widgets.rb +1 -1
  43. data/plugins/project_browser/project_browser.rb +4 -4
  44. data/plugins/rake/rake.rb +4 -4
  45. data/plugins/rake/rake_extension.rb +1 -1
  46. data/plugins/rspec/rspec.rb +4 -4
  47. data/plugins/rspec/ruber_rspec_formatter.rb +2 -2
  48. data/plugins/ruby_development/ruby_development.rb +3 -3
  49. data/plugins/ruby_runner/ruby_runner.rb +2 -2
  50. data/plugins/state/plugin.yaml +6 -8
  51. data/plugins/state/state.rb +201 -391
  52. data/plugins/state/ui/config_widget.rb +5 -5
  53. data/plugins/state/ui/config_widget.ui +3 -3
  54. data/plugins/syntax_checker/syntax_checker.rb +4 -0
  55. data/spec/annotation_model_spec.rb +1 -1
  56. data/spec/auto_end_spec.rb +98 -47
  57. data/spec/component_manager_spec.rb +80 -21
  58. data/spec/document_factory_spec.rb +115 -0
  59. data/spec/document_list_spec.rb +560 -450
  60. data/spec/document_spec.rb +143 -55
  61. data/spec/editor_view_spec.rb +2 -2
  62. data/spec/environment_spec.rb +1900 -0
  63. data/spec/hint_solver_spec.rb +5 -5
  64. data/spec/kde_sugar_spec.rb +16 -0
  65. data/spec/output_widget_spec.rb +177 -51
  66. data/spec/pane_spec.rb +29 -5
  67. data/spec/plugin_spec.rb +1 -1
  68. data/spec/project_factory_spec.rb +104 -0
  69. data/spec/project_list_spec.rb +352 -447
  70. data/spec/project_spec.rb +34 -33
  71. data/spec/qt_sugar_spec.rb +2 -2
  72. data/spec/state_spec.rb +508 -811
  73. data/spec/utils_spec.rb +149 -98
  74. data/spec/workspace_spec.rb +120 -9
  75. data/spec/world_spec.rb +1219 -0
  76. metadata +23 -14
  77. data/lib/ruber/documents/document_list.rb +0 -412
  78. data/lib/ruber/documents/plugin.yaml +0 -4
  79. data/lib/ruber/main_window/view_manager.rb +0 -431
  80. data/lib/ruber/projects/plugin.yaml +0 -11
  81. data/lib/ruber/projects/project_list.rb +0 -314
@@ -0,0 +1,470 @@
1
+ =begin
2
+ Copyright (C) 2011 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/world/hint_solver'
22
+ require 'ruber/world/document_list'
23
+
24
+ module Ruber
25
+
26
+ module World
27
+
28
+ class Environment < Qt::Object
29
+
30
+ class ViewList
31
+
32
+ attr_reader :by_activation, :by_document, :by_tab, :tabs
33
+
34
+ def initialize
35
+ @by_activation = []
36
+ @by_tab = {}
37
+ @by_document = {}
38
+ @tabs = {}
39
+ end
40
+
41
+ def add_view view, tab
42
+ @by_activation << view
43
+ (@by_tab[tab] ||= []) << view
44
+ (@by_document[view.document] ||= []) << view
45
+ @tabs[view] = tab
46
+ end
47
+
48
+ def remove_view view
49
+ tab = @tabs[view]
50
+ @by_activation.delete view
51
+ @by_tab[tab].delete view
52
+ @by_tab.delete tab if @by_tab[tab].empty?
53
+ @by_document[view.document].delete view
54
+ @by_document.delete view.document if @by_document[view.document].empty?
55
+ @tabs.delete view
56
+ end
57
+
58
+ def move_to_front view
59
+ @by_activation.unshift @by_activation.delete(view)
60
+ tab = @tabs[view]
61
+ @by_tab[tab].unshift @by_tab[tab].delete(view)
62
+ doc = view.document
63
+ @by_document[doc].unshift @by_document[doc].delete(view)
64
+ end
65
+
66
+ end
67
+
68
+ include Activable
69
+
70
+ include Extension
71
+
72
+ =begin rdoc
73
+ The default hints used by methods like {#editor_for} and {#editor_for!}
74
+ =end
75
+ DEFAULT_HINTS = {
76
+ :exisiting => :always,
77
+ :strategy => [:current, :current_tab, :first],
78
+ :new => :new_tab,
79
+ :split => :horizontal,
80
+ :show => true,
81
+ :create_if_needed => true
82
+ }.freeze
83
+
84
+ signals 'active_editor_changed(QWidget*)'
85
+
86
+ signals :deactivated
87
+
88
+ signals :activated
89
+
90
+ signals 'closing(QObject*)'
91
+
92
+ =begin rdoc
93
+ @return [Project,nil] the project associated with the environment or *nil* if the
94
+ environment is not associated with a project
95
+ =end
96
+ attr_reader :project
97
+
98
+ =begin rdoc
99
+ @return [KDE::TabWidget] the tab widget containing the views contained in the
100
+ environment
101
+ =end
102
+ attr_reader :tab_widget
103
+
104
+ =begin rdoc
105
+ @return [EditorView,nil] the active editor or *nil* if no active editor exists
106
+ =end
107
+ attr_reader :active_editor
108
+
109
+ def initialize prj, parent = nil
110
+ super parent
111
+ @project = prj
112
+ @tab_widget = KDE::TabWidget.new{self.document_mode = true}
113
+ connect @tab_widget, SIGNAL('currentChanged(int)'), self, SLOT('current_tab_changed(int)')
114
+ connect @tab_widget, SIGNAL('tabCloseRequested(int)'), self, SLOT('close_tab(int)')
115
+ @views = ViewList.new
116
+ @hint_solver = HintSolver.new @tab_widget, nil, @views.by_activation
117
+ @documents = MutableDocumentList.new
118
+ @active_editor = nil
119
+ @active = false
120
+ @focus_on_editors = true
121
+ unless prj
122
+ @default_document = Ruber[:world].new_document
123
+ @default_document.object_name = 'default_document'
124
+ @default_document.connect(SIGNAL('closing(QObject*)')){@default_document = nil}
125
+ editor_for! @default_document
126
+ end
127
+ end
128
+
129
+ def editor_for! doc, hints = DEFAULT_HINTS
130
+ doc = Ruber[:world].document doc unless doc.is_a? Document
131
+ hints = DEFAULT_HINTS.merge hints
132
+ editor = @hint_solver.find_editor doc, hints
133
+ unless editor
134
+ return nil unless hints[:create_if_needed]
135
+ view_to_split = @hint_solver.place_editor hints
136
+ editor = doc.create_view
137
+ if view_to_split
138
+ orientation = hints[:split] == :vertical ? Qt::Vertical : Qt::Horizontal
139
+ view_to_split.parent.split view_to_split, editor, orientation
140
+ else
141
+ new_pane = create_tab(editor)
142
+ add_editor editor, new_pane
143
+ @tab_widget.add_tab new_pane, doc.icon, doc.document_name
144
+ new_pane.label = label_for_document doc
145
+ end
146
+ end
147
+ editor
148
+ end
149
+
150
+ def documents
151
+ DocumentList.new @documents
152
+ end
153
+
154
+ def tabs
155
+ @tab_widget.to_a
156
+ end
157
+
158
+ def views doc = nil
159
+ doc ? @views.by_document[doc].dup : @views.by_activation.dup
160
+ end
161
+
162
+ def tab arg
163
+ if arg.is_a?(Pane)
164
+ pane = arg
165
+ while parent = pane.parent_pane
166
+ pane = parent
167
+ end
168
+ @tab_widget.index_of(pane) > -1 ? pane : nil
169
+ else @views.tabs[arg]
170
+ end
171
+ end
172
+
173
+ def activate_editor view
174
+ return view if @active_editor == view
175
+ deactivate_editor @active_editor
176
+ if view
177
+ if active?
178
+ Ruber[:main_window].gui_factory.add_client view.send(:internal)
179
+ @active_editor = view
180
+ end
181
+ @views.move_to_front view
182
+ view_tab = tab(view)
183
+ idx = @tab_widget.index_of view_tab
184
+ @tab_widget.set_tab_text idx, view.document.document_name
185
+ @tab_widget.set_tab_icon idx, view.document.icon
186
+ @tab_widget.current_index = idx
187
+ end
188
+ if active?
189
+ emit active_editor_changed(view)
190
+ view.document.activate if view
191
+ end
192
+ view.set_focus if view and focus_on_editors?
193
+ view
194
+ end
195
+ slots 'activate_editor(QWidget*)'
196
+
197
+ def active_document
198
+ @active_editor ? @active_editor.document : nil
199
+ end
200
+
201
+ def close_editor editor, ask = true
202
+ doc = editor.document
203
+ if doc.views.count > 1 then editor.close
204
+ else doc.close ask
205
+ end
206
+ end
207
+
208
+ def close mode = :save
209
+ if @project then @project.close mode == :save
210
+ else
211
+ docs_to_close = @views.by_document.to_a.select{|d, v| (d.views - v).empty?}
212
+ docs_to_close.map!{|d| d[0]}
213
+ if mode == :save
214
+ return false unless Ruber[:main_window].save_documents docs_to_close
215
+ end
216
+ emit closing(self)
217
+ self.active = false
218
+ docs_to_close.each{|d| d.close false}
219
+ @views.by_activation.dup.each{|v| v.close}
220
+ delete_later
221
+ true
222
+ end
223
+ end
224
+ slots :close
225
+
226
+ def display_document doc, hints = {}
227
+ ed = editor_for! doc, hints
228
+ activate_editor ed
229
+ line = hints[:line]
230
+ ed.go_to line, hints[:column] || 0 if hints[:line]
231
+ ed
232
+ end
233
+
234
+ def close_editors editors, ask = true
235
+ editors_by_doc = editors.group_by &:document
236
+ docs = editors_by_doc.keys
237
+ to_close = docs.select{|doc| (doc.views - editors_by_doc[doc]).empty?}
238
+ if ask
239
+ return unless Ruber[:main_window].save_documents to_close
240
+ end
241
+ to_close.each do |doc|
242
+ doc.close false
243
+ editors_by_doc.delete doc
244
+ end
245
+ editors_by_doc.each_value do |a|
246
+ a.each &:close
247
+ end
248
+ end
249
+
250
+ def replace_editor old, new
251
+ if new.is_a? Document
252
+ new = new.create_view
253
+ elsif new.is_a? String or new.is_a? KDE::Url
254
+ new = Ruber[:world].document(new).create_view
255
+ end
256
+ if old.document.views.count == 1
257
+ return unless old.document.query_close
258
+ close_doc = true
259
+ end
260
+ old.parent.replace_view old, new
261
+ close_editor old, false
262
+ new
263
+ end
264
+
265
+ def focus_on_editors?
266
+ @views.tabs.empty? || @focus_on_editors
267
+ end
268
+
269
+ def query_close
270
+ if Ruber[:app].status != :asking_to_quit
271
+ docs = @views.by_document.select{|d, v| (d.views - v).empty?}
272
+ Ruber[:main_window].save_documents docs.map{|a| a[0]}
273
+ else true
274
+ end
275
+ end
276
+
277
+ def remove_from_project
278
+ raise "environment not associated with a project" unless @project
279
+ emit closing(self)
280
+ self.active = false
281
+ docs_to_close = @views.by_document.select{|d, v| (d.views - v).empty?}
282
+ docs_to_close.each{|d| d[0].close false}
283
+ @views.dup.by_activation.each{|v| v.close}
284
+ end
285
+
286
+ private
287
+
288
+ def maybe_close_default_document doc
289
+ return unless @default_document
290
+ return if @default_document == doc
291
+ return unless @default_document.pristine?
292
+ return if @default_document.views.count != 1
293
+ @default_document.close
294
+ end
295
+
296
+ def add_editor editor, pane
297
+ maybe_close_default_document editor.document
298
+ @views.add_view editor, pane
299
+ doc = editor.document
300
+ editor.parent.label = label_for_document doc
301
+ unless @documents.include? doc
302
+ @documents.add doc
303
+ connect doc, SIGNAL('document_url_changed(QObject*)'), self, SLOT('document_url_changed(QObject*)')
304
+ end
305
+ connect editor, SIGNAL('focus_in(QWidget*)'), self, SLOT('editor_got_focus(QWidget*)')
306
+ connect doc, SIGNAL('modified_changed(bool, QObject*)'), self, SLOT('document_modified_status_changed(bool, QObject*)')
307
+ connect editor, SIGNAL('focus_out(QWidget*)'), self, SLOT('editor_lost_focus(QWidget*)')
308
+ update_pane pane
309
+ end
310
+
311
+ def remove_editor pane, editor
312
+ editor_tab = @views.tabs[editor]
313
+ if @active_editor == editor
314
+ to_activate = @views.by_tab[editor_tab][1]
315
+ activate_editor to_activate
316
+ deactivate_editor editor
317
+ end
318
+ disconnect editor, SIGNAL('focus_in(QWidget*)'), self, SLOT('editor_got_focus(QWidget*)')
319
+ disconnect editor, SIGNAL('focus_out(QWidget*)'), self, SLOT('editor_lost_focus(QWidget*)')
320
+ @views.remove_view editor
321
+ unless @views.by_tab[editor_tab]
322
+ @tab_widget.remove_tab @tab_widget.index_of(editor_tab)
323
+ end
324
+ doc = editor.document
325
+ unless @views.by_document[doc]
326
+ @documents.remove doc
327
+ disconnect doc, SIGNAL('document_url_changed(QObject*)'), self, SLOT('document_url_changed(QObject*)')
328
+ disconnect doc, SIGNAL('modified_changed(bool, QObject*)'), self, SLOT('document_modified_status_changed(bool, QObject*)')
329
+ end
330
+ end
331
+ slots 'remove_editor(QWidget*, QWidget*)'
332
+
333
+ def close_tab idx
334
+ views = @tab_widget.widget(idx).views
335
+ close_editors views
336
+ end
337
+ slots 'close_tab(int)'
338
+
339
+ def editor_got_focus editor
340
+ @focus_on_editors = true
341
+ activate_editor editor
342
+ end
343
+ slots 'editor_got_focus(QWidget*)'
344
+
345
+ def editor_lost_focus editor
346
+ #When an editor is closed, the editor's parent pane is made parentless before the removing_editor
347
+ #signal is emitted by the pane, which means the editor is hidden (and thus
348
+ #looses focus) before it is actually closed. We don't want @focus_on_editors
349
+ #to be changed in that case (otherwise closing an editor which had focus
350
+ #would cause focus not to be given to the next active editor). The only
351
+ #check I can think of is whether the editor's parent pane has a parent
352
+ #or not
353
+ if editor.is_active_window and editor.parent.parent
354
+ @focus_on_editors = false
355
+ end
356
+ end
357
+ slots 'editor_lost_focus(QWidget*)'
358
+
359
+ def do_deactivation
360
+ #hiding the tab widget would make the editors all loose focus, but we
361
+ #want that setting to be kept until the environment becomes active again,
362
+ #so we store the original value in a temp variable and restore it afterwards
363
+ old_focus_on_editors = @focus_on_editors
364
+ # @tab_widget.hide
365
+ deactivate_editor @active_editor
366
+ @focus_on_editors = old_focus_on_editors
367
+ super
368
+ end
369
+
370
+ def do_activation
371
+ #showing the tab widget may make some editors all receive focus, but we
372
+ #want that setting to be kept until the environment becomes active again,
373
+ #so we store the original value in a temp variable and restore it afterwards
374
+ # old_focus_on_editors = @focus_on_editors
375
+ # @tab_widget.show
376
+ # @focus_on_editors = old_focus_on_editors
377
+ activate_editor @views.by_activation[0]
378
+ super
379
+ end
380
+
381
+
382
+ def deactivate_editor view
383
+ if view and @active_editor == view
384
+ view.document.deactivate
385
+ Ruber[:main_window].gui_factory.remove_client view.send(:internal)
386
+ @active_editor = nil
387
+ end
388
+ end
389
+
390
+ def label_for_document doc
391
+ url = doc.url
392
+ label = if url.local_file? then doc.path
393
+ elsif url.valid? then url.pretty_url
394
+ else doc.document_name
395
+ end
396
+ label << ' [modified]' if doc.modified?
397
+ label
398
+ end
399
+
400
+ def create_tab view
401
+ pane = Pane.new view
402
+ pane.label = label_for_document view.document
403
+ connect pane, SIGNAL('removing_view(QWidget*, QWidget*)'), self, SLOT('remove_editor(QWidget*, QWidget*)')
404
+ connect pane, SIGNAL('pane_split(QWidget*, QWidget*, QWidget*)'), self, SLOT('pane_split(QWidget*, QWidget*, QWidget*)')
405
+ connect pane, SIGNAL('view_replaced(QWidget*, QWidget*, QWidget*)'), self, SLOT('view_replaced(QWidget*, QWidget*, QWidget*)')
406
+ pane
407
+ end
408
+
409
+ def pane_split pane, old, new
410
+ add_editor new, tab(pane)
411
+ end
412
+ slots 'pane_split(QWidget*, QWidget*, QWidget*)'
413
+
414
+ def view_replaced pane, old, new
415
+ toplevel_pane = tab(pane)
416
+ add_editor new, toplevel_pane
417
+ remove_editor toplevel_pane, old
418
+ update_pane toplevel_pane
419
+ end
420
+ slots 'view_replaced(QWidget*, QWidget*, QWidget*)'
421
+
422
+ def update_pane pane
423
+ docs = @views.by_tab[pane].map(&:document).uniq
424
+ tool_tip = docs.map(&:document_name).join "\n"
425
+ @tab_widget.set_tab_tool_tip @tab_widget.index_of(pane), tool_tip
426
+ end
427
+ slots 'update_pane(QWidget*)'
428
+
429
+ def current_tab_changed idx
430
+ current_tab = @tab_widget.widget idx
431
+ view = idx >= 0 ? @views.by_tab[current_tab][0] : nil
432
+ activate_editor view
433
+ view
434
+ end
435
+ slots 'current_tab_changed(int)'
436
+
437
+ def remove_tab pane
438
+ @tab_widget.remove_tab @tab_widget.index_of(pane)
439
+ end
440
+ slots 'remove_tab(QWidget*)'
441
+
442
+ def document_url_changed doc
443
+ @tab_widget.each{|t| update_pane t}
444
+ label = label_for_document doc
445
+ @views.by_document[doc].each{|v| v.parent.label = label}
446
+ @tab_widget.each_with_index do |w, i|
447
+ if @views.by_tab[w][0].document == doc
448
+ @tab_widget.set_tab_text i, doc.document_name
449
+ @tab_widget.set_tab_icon i, doc.icon
450
+ end
451
+ end
452
+ end
453
+ slots 'document_url_changed(QObject*)'
454
+
455
+ def document_modified_status_changed mod, doc
456
+ label = label_for_document doc
457
+ @views.by_document[doc].each{|v| v.parent.label = label}
458
+ @tab_widget.each_with_index do |w, i|
459
+ if @views.by_tab[w][0].document == doc
460
+ @tab_widget.set_tab_icon i, doc.icon
461
+ end
462
+ end
463
+ end
464
+ slots 'document_modified_status_changed(bool, QObject*)'
465
+
466
+ end
467
+
468
+ end
469
+
470
+ end