ruber 0.0.8 → 0.0.9

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