ruber 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/CHANGES +25 -0
  2. data/bin/ruber +0 -0
  3. data/data/share/apps/ruber/ruberui.rc +15 -1
  4. data/data/share/icons/{ruber.png → ruber-old.pgn} +0 -0
  5. data/lib/ruber/application/application.rb +216 -73
  6. data/lib/ruber/application/plugin.yaml +2 -2
  7. data/lib/ruber/document_project.rb +25 -5
  8. data/lib/ruber/documents/document_list.rb +11 -15
  9. data/lib/ruber/editor/document.rb +106 -50
  10. data/lib/ruber/editor/editor_view.rb +4 -2
  11. data/lib/ruber/external_program_plugin.rb +8 -0
  12. data/lib/ruber/kde_config_option_backend.rb +12 -4
  13. data/lib/ruber/kde_sugar.rb +35 -1
  14. data/lib/ruber/main_window/choose_plugins_dlg.rb +10 -10
  15. data/lib/ruber/main_window/hint_solver.rb +263 -0
  16. data/lib/ruber/main_window/main_window.rb +462 -206
  17. data/lib/ruber/main_window/main_window_actions.rb +228 -62
  18. data/lib/ruber/main_window/main_window_internal.rb +169 -115
  19. data/lib/ruber/main_window/plugin.yaml +13 -3
  20. data/lib/ruber/main_window/save_modified_files_dlg.rb +1 -1
  21. data/lib/ruber/main_window/ui/choose_plugins_widget.rb +1 -1
  22. data/lib/ruber/main_window/ui/main_window_settings_widget.rb +1 -1
  23. data/lib/ruber/main_window/ui/new_project_widget.rb +1 -1
  24. data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +1 -1
  25. data/lib/ruber/main_window/ui/output_color_widget.rb +1 -1
  26. data/lib/ruber/main_window/ui/workspace_settings_widget.rb +51 -0
  27. data/lib/ruber/main_window/ui/workspace_settings_widget.ui +28 -0
  28. data/lib/ruber/main_window/view_manager.rb +418 -0
  29. data/lib/ruber/main_window/workspace.png +0 -0
  30. data/lib/ruber/output_widget.rb +43 -37
  31. data/lib/ruber/pane.rb +621 -0
  32. data/lib/ruber/plugin_specification_reader.rb +8 -1
  33. data/lib/ruber/projects/project_files_list.rb +6 -0
  34. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +1 -1
  35. data/lib/ruber/projects/ui/project_files_widget.rb +1 -1
  36. data/lib/ruber/qt_sugar.rb +94 -4
  37. data/lib/ruber/utils.rb +16 -7
  38. data/lib/ruber/version.rb +2 -2
  39. data/plugins/autosave/autosave.rb +62 -1
  40. data/plugins/autosave/plugin.yaml +1 -0
  41. data/plugins/autosave/ui/autosave_config_widget.rb +37 -14
  42. data/plugins/autosave/ui/autosave_config_widget.ui +62 -12
  43. data/plugins/find_in_files/find_in_files_widgets.rb +1 -3
  44. data/plugins/find_in_files/ui/config_widget.rb +1 -1
  45. data/plugins/find_in_files/ui/find_in_files_widget.rb +1 -1
  46. data/plugins/rake/plugin.yaml +1 -1
  47. data/plugins/rake/ui/add_quick_task_widget.rb +1 -1
  48. data/plugins/rake/ui/choose_task_widget.rb +1 -1
  49. data/plugins/rake/ui/config_widget.rb +1 -1
  50. data/plugins/rake/ui/project_widget.rb +1 -1
  51. data/plugins/rspec/rspec.rb +14 -22
  52. data/plugins/rspec/ruber_rspec_formatter.rb +4 -1
  53. data/plugins/rspec/ui/rspec_project_widget.rb +1 -1
  54. data/plugins/ruby_development/plugin.yaml +7 -2
  55. data/plugins/ruby_development/ruby_development.rb +134 -13
  56. data/plugins/ruby_development/ui/config_widget.rb +66 -0
  57. data/plugins/ruby_development/ui/config_widget.ui +58 -0
  58. data/plugins/ruby_development/ui/project_widget.rb +1 -1
  59. data/plugins/ruby_runner/plugin.yaml +2 -2
  60. data/plugins/ruby_runner/ruby_runner.rb +15 -3
  61. data/plugins/ruby_runner/ui/config_widget.rb +1 -1
  62. data/plugins/ruby_runner/ui/project_widget.rb +1 -1
  63. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +1 -1
  64. data/plugins/state/plugin.yaml +6 -2
  65. data/plugins/state/state.rb +305 -81
  66. data/plugins/state/ui/config_widget.rb +1 -1
  67. data/spec/common.rb +11 -3
  68. data/spec/document_list_spec.rb +8 -8
  69. data/spec/document_project_spec.rb +98 -25
  70. data/spec/document_spec.rb +178 -152
  71. data/spec/editor_view_spec.rb +26 -5
  72. data/spec/framework.rb +5 -0
  73. data/spec/hint_solver_spec.rb +450 -0
  74. data/spec/kde_sugar_spec.rb +73 -6
  75. data/spec/output_widget_spec.rb +172 -156
  76. data/spec/pane_spec.rb +1165 -0
  77. data/spec/plugin_specification_reader_spec.rb +37 -1
  78. data/spec/project_files_list_spec.rb +30 -20
  79. data/spec/qt_sugar_spec.rb +269 -0
  80. data/spec/state_spec.rb +566 -353
  81. data/spec/utils_spec.rb +1 -1
  82. data/spec/view_manager_spec.rb +71 -0
  83. metadata +16 -4
@@ -19,6 +19,7 @@
19
19
  =end
20
20
 
21
21
  require 'yaml'
22
+ require 'facets/boolean'
22
23
 
23
24
  module KDE
24
25
 
@@ -30,6 +31,27 @@ module KDE
30
31
  KDE::Url.new val
31
32
  end
32
33
 
34
+ =begin rdoc
35
+ Tells whether a string looks like an url pointing to a file
36
+
37
+ An url is considered to _look like_ an url pointing to a file if it contains a
38
+ scheme part and an authority part, that is, if it starts with a scheme followed
39
+ by two slashes.
40
+
41
+ If _abs_only_ is *true*, this method returns *true* only if the file path is absolute,
42
+ that is if the scheme is followed by three slashes. If _abs_only_ is *false*, it'll
43
+ return *true* for both absolute and relative paths.
44
+ @param [String] str the string to test
45
+ @param [Boolean] abs_only whether this method should return *true* only if the path
46
+ is absolute or also for relative paths
47
+ @return [Boolean] *true* if _str_ looks like an URL pointing to a file (or pointing
48
+ to an absolute file if _abs_only_ is *true*) and *false* otherwise
49
+ =end
50
+ def self.file_url? str, abs_only = false
51
+ slash_number = abs_only ? 3 : 2
52
+ str.match(%r|[\w+.-]+:/{#{slash_number},3}|).to_b
53
+ end
54
+
33
55
  def to_yaml opts = {}
34
56
  YAML.quick_emit(self, opts) do |out|
35
57
  out.scalar taguri, to_encoded.to_s, :plain
@@ -45,7 +67,11 @@ module KDE
45
67
  end
46
68
 
47
69
  def local_file?
48
- is_local_file
70
+ scheme == "file"
71
+ end
72
+
73
+ def remote_file?
74
+ !(local_file? or relative?)
49
75
  end
50
76
 
51
77
  end
@@ -123,6 +149,14 @@ args[:overlays], nil
123
149
  res
124
150
  end
125
151
 
152
+ def urls
153
+ count.times.inject([]) do |res, i|
154
+ url = KDE::Url.new u
155
+ url.path = File.expand_path(u) if url.protocol.empty?
156
+ res << url
157
+ end
158
+ end
159
+
126
160
  end
127
161
 
128
162
  class InputDialog
@@ -125,14 +125,14 @@ deselected.
125
125
  @ui = Ui::ChoosePluginsWidget.new
126
126
  @ui.setupUi self
127
127
 
128
- dirs = Ruber[:config][:general].plugin_dirs
128
+ dirs = Ruber[:app].plugin_dirs
129
129
 
130
130
  @chosen_plugins = Ruber[:config][:general, :plugins].map(&:to_sym)
131
131
  loaded = Ruber[:components].plugins.map(&:plugin_name)
132
132
  @chosen_plugins.delete_if{|i| !loaded.include? i}
133
133
 
134
134
  read_plugins dirs
135
- res = find_deps(:warning) do |e|
135
+ res = find_deps(:sorry) do |e|
136
136
  "There were problems making dependencies. #{create_failure_message e}\nAll plugins will be deselected"
137
137
  end
138
138
  if res
@@ -170,7 +170,7 @@ deselected.
170
170
  =end
171
171
  def write_settings
172
172
  dirs = @ui.directories.items
173
- Ruber[:config][:general,:plugin_dirs] = dirs
173
+ Ruber[:app].plugin_dirs = dirs
174
174
  plugins = []
175
175
  @ui.plugins.model.each_row do |r|
176
176
  plugins << r[0].data.to_string if r[0].fully_checked?
@@ -188,7 +188,7 @@ dependency problem occurs, the user is warned and all plugins are deselected
188
188
  dirs = Ruber[:config].default :general, :plugin_dirs
189
189
  @chosen_plugins = Ruber[:config].default( :general, :plugins).split(',').map{|i| i.to_sym}
190
190
  read_plugins dirs
191
- res = find_deps(:warning) do |e|
191
+ res = find_deps(:sorry) do |e|
192
192
  "There were problems making dependencies. #{create_failure_message e}\nAll plugins will be deselected"
193
193
  end
194
194
  if res
@@ -248,11 +248,11 @@ an error occurs and no block is passed, the exception is passed on.
248
248
  This method returns *nil* if no error occurs and the value returned by the
249
249
  message box otherwise.
250
250
  =end
251
- def find_deps msg_type = :warning
251
+ def find_deps msg_type = :sorry
252
252
  chosen_data = @chosen_plugins.map{|i| @plugin_data[i]}
253
253
  begin @needed_plugins = ComponentManager.fill_dependencies chosen_data, @plugin_data.values
254
254
  rescue ComponentManager::UnresolvedDep, ComponentManager::CircularDep => e
255
- if block_given? then KDE::MessageBox.send(msg_type, yield(e))
255
+ if block_given? then KDE::MessageBox.send msg_type, nil, yield(e)
256
256
  else raise
257
257
  end
258
258
  end
@@ -308,7 +308,7 @@ to the item _it_.
308
308
  if it.check_state == Qt::Checked then @chosen_plugins << name.to_sym
309
309
  else @chosen_plugins.delete name.to_sym
310
310
  end
311
- find_deps{|e| create_failure_message( e) + "\nPlease, be sure to correct the problem before accepting changes" }
311
+ find_deps{|e| create_failure_message( e) + "\nPlease, be sure to correct the problem before pressing the OK or Apply button" }
312
312
  update_plugin_status
313
313
  end
314
314
 
@@ -320,7 +320,7 @@ If the dependencies aren't respected, the user is warned with a message box.
320
320
  =end
321
321
  def slot_directories_changed
322
322
  new_dirs = @ui.directories.items
323
- find_deps{|e| create_failure_message( e) + "\nPlease, be sure to correct the problem before accepting changes" }
323
+ find_deps{|e| create_failure_message( e) + "\nPlease, be sure to correct the problem before pressing the OK or Apply button" }
324
324
  fill_plugin_list
325
325
  emit directories_changed
326
326
  nil
@@ -337,7 +337,7 @@ happen.
337
337
  case e
338
338
  when ComponentManager::UnresolvedDep
339
339
  deps = e.missing.map do |p1, p2|
340
- "#{d}, needed by #{pl.join ', '}"
340
+ "#{p1}, needed by #{p2.join ', '}"
341
341
  end
342
342
  "Some dependencies couldn't be satisifed:\n#{deps.join "\n"}"
343
343
  when ComponentManager::CircularDep
@@ -350,4 +350,4 @@ happen.
350
350
 
351
351
  end
352
352
 
353
- end
353
+ end
@@ -0,0 +1,263 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'ruber/pane'
22
+ require 'ruber/editor/editor_view'
23
+
24
+ module Ruber
25
+
26
+ class MainWindow
27
+
28
+ =begin rdoc
29
+ Helper class used by {MainWindow#editor_for} and friends to find out which editor
30
+ should use or where the new editor should be placed according to the hints given
31
+ them
32
+ =end
33
+ class HintSolver
34
+
35
+ =begin rdoc
36
+ @param [KDE::TabWidget] tabs the tab widget which contains the tabs
37
+ @param [KParts::PartManager] manager the part manager used by the main window
38
+ @param [Array<EditorView>] view_order an array specifying the order in which views
39
+ were activated. Most recently activated views corresponds to lower indexes. It's
40
+ assumed that the main window will keep a reference to this array and update it
41
+ whenever a view becomes activated
42
+ =end
43
+ def initialize tabs, manager, view_order
44
+ @tabs = tabs
45
+ @part_manager = manager
46
+ @view_order = view_order
47
+ end
48
+
49
+ =begin rdoc
50
+ Finds the editor to use for a document according to the given hints
51
+
52
+ If no editor associated with the document and respecting the given hints exists,
53
+ *nil* is returned. This always happens if the @:existing@ hint is @:never@.
54
+ @param [Document] doc the document to retrieve the editor for
55
+ @param hints (see MainWindow#editor_for). See {#MainWindow#editor_for} for the
56
+ values it can contain. Only the @:existing@ and @:strategy@ entries are used
57
+ @return [EditorView,nil] a view associated with the document which respects the
58
+ hints or *nil* if such a view doesn't exist
59
+ =end
60
+ def find_editor doc, hints
61
+ views = []
62
+ case hints[:existing]
63
+ when :never then return nil
64
+ when :current_tab
65
+ views = @tabs.current_widget.select{|v| v.document == doc}
66
+ else @tabs.each{|t| views += t.select{|v| v.document == doc} }
67
+ end
68
+ choose_editor doc, views, hints
69
+ end
70
+
71
+ =begin rdoc
72
+ Finds out where a new editor should respect the given hints
73
+
74
+ The return value tells where the new editor should be placed. If the return value
75
+ is *nil* then the editor should be placed in a new tab; if it's an {EditorView} then
76
+ the editor should be placed in the pane containing the view, splitting it at the
77
+ view
78
+ @param hints (see MainWindow#editor_for). See {#MainWindow#editor_for} for the
79
+ values it can contain. Only the @:newand entry is used
80
+ @return [EditorView, nil] an {EditorView} if the new editor should be placed inside
81
+ an existing pane. The returned view is the view where it's parent should be split
82
+ at. If *nil* is returned, the editor should be put in a new tab
83
+ =end
84
+ def place_editor hints
85
+ case hints[:new]
86
+ when :new_tab then nil
87
+ when :current then @view_order[0]
88
+ when :current_tab
89
+ current = @tabs.current_widget
90
+ current.each_view.to_a[0] if current
91
+ when Integer
92
+ pane = @tabs.widget hints[:new]
93
+ pane.each_view.to_a[0] if pane
94
+ when EditorView then
95
+ view = hints[:new]
96
+ view.parent.is_a?(Pane) ? view : nil
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ =begin rdoc
103
+ Chooses an editor according to the value of the :strategy hint
104
+
105
+ For each strategy specified in the hint, this method calls a method called @choose_editor_*_strategy@,
106
+ where the @*@ stands for the strategy name.
107
+
108
+ If there's no editor satsifying the given strategy, then the @:next@ strategy, which
109
+ always gives a result, is used.
110
+ @param [Document] doc the document associated with the editor
111
+ @param [Array<EditorView>] editors a list of the editors to choose among. They should
112
+ be ordered by tab and, within the same tab, from left to right and top to bottom
113
+ @param hints (see MainWindow#editor_for)
114
+ @return [EditorView,nil] an editor view according to the @:strategy@ hint or *nil*
115
+ if _editors_ is empty
116
+ =end
117
+ def choose_editor doc, editors, hints
118
+ case editors.size
119
+ when 0 then nil
120
+ when 1 then editors.first
121
+ else
122
+ editor = nil
123
+ (Array(hints[:strategy]) + [:next]).each do |s|
124
+ editor = send "choose_editor_#{s}_strategy", doc, editors, hints
125
+ break if editor
126
+ end
127
+ editor
128
+ end
129
+ end
130
+
131
+
132
+ =begin rdoc
133
+ A list of all the editors associated with the given document in a pane
134
+ @param [Document] doc the document
135
+ @param [Pane] tab the pane where to look for editors
136
+ @return [Array<EditorView>] the editor views associated with _doc_ in the pane
137
+ _pane_, in order
138
+ =end
139
+ def editors_in_tab doc, tab
140
+ tab.select{|v| v.document == doc}
141
+ end
142
+
143
+ =begin rdoc
144
+ Chooses an editor according to the @current@ strategy
145
+
146
+ @param (see #choose_editor)
147
+ @return [EditorView,nil] the current editor if it is associated with _doc_
148
+ and *nil* otherwise
149
+ =end
150
+ def choose_editor_current_strategy doc, editors, hints
151
+ current = @view_order[0]
152
+ if current and current.document == doc then current
153
+ else nil
154
+ end
155
+ end
156
+
157
+ =begin rdoc
158
+ Chooses an editor according to the @current_tab@ strategy
159
+
160
+ @param (see #choose_editor)
161
+ @return [EditorView,nil] the first editor in the current tab associated with _doc_
162
+ or *nil* if the current tab doesn't contain any editor associated with _doc_
163
+ =end
164
+ def choose_editor_current_tab_strategy doc, editors, hints
165
+ tab = @tabs.current_widget
166
+ views = tab.find_children(EditorView).select do |c|
167
+ c.is_a?(EditorView) and c.document == doc
168
+ end
169
+ (views & editors).first
170
+ end
171
+
172
+ =begin rdoc
173
+ Chooses an editor according to the @last_current_tab@ strategy
174
+
175
+ @param (see #choose_editor)
176
+ @return [EditorView,nil] the last editor in the current tab associated with _doc_
177
+ or *nil* if the current tab doesn't contain any editor associated with _doc_
178
+ =end
179
+
180
+ def choose_editor_last_current_tab_strategy doc, editors, hints
181
+ tab = @tabs.current_widget
182
+ views = tab.find_children(EditorView).select do |c|
183
+ c.is_a?(EditorView) and c.document == doc
184
+ end
185
+ (views & editors).last
186
+ end
187
+
188
+ =begin rdoc
189
+ Chooses an editor according to the @next@ strategy
190
+
191
+ @param (see #choose_editor)
192
+ @return [EditorView] the first editor associated with _doc_ starting from
193
+ the current tab
194
+ =end
195
+
196
+ def choose_editor_next_strategy doc, editors, hints
197
+ current_index = @tabs.current_index
198
+ editor = editors.find do |e|
199
+ pane = e.parent
200
+ pane = pane.parent_pane while pane.parent_pane
201
+ @tabs.index_of(pane) >= current_index
202
+ end
203
+ editor || editors.first
204
+ end
205
+
206
+ =begin rdoc
207
+ Chooses an editor according to the @previous@ strategy
208
+
209
+ @param (see #choose_editor)
210
+ @return [EditorView] the first editor associated with _doc_ starting from
211
+ the current tab and going backwards
212
+ =end
213
+ def choose_editor_previous_strategy doc, editors, hints
214
+ current_index = @tabs.current_index
215
+ editor = editors.reverse_each.find do |e|
216
+ pane = e.parent
217
+ pane = pane.parent_pane while pane.parent_pane
218
+ @tabs.index_of(pane) < current_index
219
+ end
220
+ editor || editors.last
221
+ end
222
+
223
+ =begin rdoc
224
+ Chooses an editor according to the @first@ strategy
225
+
226
+ @param (see #choose_editor)
227
+ @return [EditorView] the first editor associated with _doc_ starting from
228
+ the first tab
229
+ =end
230
+
231
+ def choose_editor_first_strategy doc, editors, hints
232
+ editors.first
233
+ end
234
+
235
+ =begin rdoc
236
+ Chooses an editor according to the @last@ strategy
237
+
238
+ @param (see #choose_editor)
239
+ @return [EditorView] the last editor associated with _doc_ starting from
240
+ the first tab
241
+ =end
242
+ def choose_editor_last_strategy doc, editors, hints
243
+ editors.last
244
+ end
245
+
246
+ =begin rdoc
247
+ Chooses an editor according to the @last_used@ strategy
248
+
249
+ @param (see #choose_editor)
250
+ @return [EditorView] the last activated editor associated with _doc_
251
+ =end
252
+ def choose_editor_last_used_strategy doc, editors, hints
253
+ # The || part is there in case there\'s a view in editors which doesn't
254
+ # appears in the view order. In this case, it's given a fake index which
255
+ # is greater than all the other possible
256
+ editors.sort_by{|e| @view_order.index(e) || (@view_order.count + 1)}.first
257
+ end
258
+
259
+ end
260
+
261
+ end
262
+
263
+ end
@@ -29,9 +29,12 @@ require 'ruber/gui_states_handler'
29
29
 
30
30
  require 'ruber/main_window/main_window_internal'
31
31
  require 'ruber/main_window/main_window_actions'
32
+ require 'ruber/main_window/hint_solver'
33
+ require 'ruber/main_window/view_manager'
32
34
 
33
35
  require 'ruber/main_window/status_bar'
34
36
  require 'ruber/main_window/workspace'
37
+ require_relative 'ui/workspace_settings_widget'
35
38
 
36
39
  module Ruber
37
40
 
@@ -47,22 +50,24 @@ tool widgets.
47
50
 
48
51
  include GuiStatesHandler
49
52
 
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
-
53
+ extend Forwardable
61
54
 
62
55
  signals 'current_document_changed(QObject*)', 'active_editor_changed(QObject*)'
63
56
 
64
57
  slots :load_settings
65
58
 
59
+ =begin rdoc
60
+ The default hints used by methods like {#editor_for} and {#editor_for!}
61
+ =end
62
+ DEFAULT_HINTS = {
63
+ :exisiting => :always,
64
+ :strategy => [:current, :current_tab, :first],
65
+ :new => :new_tab,
66
+ :split => :horizontal,
67
+ :show => true,
68
+ :create_if_needed => true
69
+ }.freeze
70
+
66
71
  =begin rdoc
67
72
  The widget which contains the tool widgets.
68
73
 
@@ -70,6 +75,8 @@ The primary use of this method is to connect to the signals emitted from the wor
70
75
  when a tool widget is raised, shown or hidden. All of its other methods are also
71
76
  provided by the MainWindow, so you shouldn't need to access the workspace to use
72
77
  them.
78
+
79
+ @return [Workspace] the workspace associated with the main window
73
80
  =end
74
81
  attr_reader :workspace
75
82
 
@@ -94,48 +101,87 @@ is the plugin description for this object.
94
101
  self.central_widget = Workspace.new self
95
102
  # We need the instance variable to use it with Forwardable
96
103
  @workspace = central_widget
97
- @views = central_widget.instance_variable_get :@views
98
- @current_view = nil
99
- @active_editor = nil
104
+ @tabs = central_widget.instance_variable_get :@views
105
+ @tabs.tabs_closable = Ruber[:config][:workspace, :close_buttons]
106
+ @view_manager = ViewManager.new @tabs, self
100
107
  @auto_activate_editors = true
101
- @editors_mapper = Qt::SignalMapper.new self
102
108
  @ui_states = {}
103
109
  @actions_state_handlers = Hash.new{|h, k| h[k] = []}
104
110
  @about_plugin_actions = []
111
+ @switch_to_actions = []
105
112
  @last_session_data = nil
106
113
  self.status_bar = StatusBar.new self
107
114
  self.connect(SIGNAL('current_document_changed(QObject*)')) do |doc|
108
- change_state 'current_document', !doc.nil_object?
115
+ change_state 'current_document', !doc.nil?
109
116
  end
110
117
  connect Ruber[:components], SIGNAL('component_loaded(QObject*)'), self, SLOT('add_about_plugin_action(QObject*)')
111
118
  connect Ruber[:components], SIGNAL('unloading_component(QObject*)'), self, SLOT('remove_about_plugin_action(QObject*)')
112
119
  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*)')
120
+ connect @view_manager, SIGNAL('active_editor_changed(QWidget*)'), self, SLOT('slot_active_editor_changed(QWidget*)')
121
+ connect Ruber[:documents], SIGNAL('document_created(QObject*)'), self, SLOT('document_created(QObject*)')
122
+ connect Ruber[:documents], SIGNAL('closing_document(QObject*)'), self, SLOT(:update_switch_to_list)
123
+
114
124
  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
125
  Ruber[:projects].connect( SIGNAL('current_project_changed(QObject*)') ) do |prj|
121
126
  change_state "active_project_exists", !prj.nil?
122
- make_title
127
+ change_title
123
128
  end
129
+ connect @tabs, SIGNAL('tabCloseRequested(int)'), self, SLOT('close_tab(int)')
124
130
  connect Ruber[:projects], SIGNAL('closing_project(QObject*)'), self, SLOT('close_project_files(QObject*)')
125
-
126
131
  setup_GUI
127
132
  create_shell_GUI true
128
133
 
129
134
  config_obj = Ruber[:config].kconfig
130
135
  apply_main_window_settings KDE::ConfigGroup.new( config_obj, 'MainWindow')
131
136
  recent_files = KDE::ConfigGroup.new config_obj, 'Recent files'
132
- action_collection.action("file_open_recent").load_entries recent_files
137
+ open_recent_action =action_collection.action("file_open_recent")
138
+ open_recent_action.load_entries recent_files
139
+ switch_to_recent_action = action_collection.action("window-switch_to_recent_file")
140
+ switch_to_recent_action.load_entries recent_files
141
+
142
+ # Synchronize the two menus, so that when the user clears one of them the also
143
+ # is also cleared
144
+ connect open_recent_action, SIGNAL(:recentListCleared), switch_to_recent_action, SLOT(:clear)
145
+ connect switch_to_recent_action, SIGNAL(:recentListCleared), open_recent_action, SLOT(:clear)
146
+
133
147
  recent_projects = KDE::ConfigGroup.new config_obj, 'Recent projects'
134
148
  action_collection.action("project-open_recent").load_entries recent_projects
135
149
  status_bar.show
136
150
  setup_initial_states
137
151
  end
138
152
 
153
+ =begin rdoc
154
+ The open tabs
155
+
156
+ @return [Array<Pane>] a list of the top-level pane for each tab
157
+ =end
158
+ def tabs
159
+ @tabs.to_a
160
+ end
161
+
162
+ =begin rdoc
163
+ The views contained in the main window
164
+
165
+ If a document is given as argument, returns all views associated with the document;
166
+ if no document is given, all views are returned.
167
+
168
+ The order of the views in the returned list is the activation order: the view
169
+ which was activated more recently is at position 0 in the array, the one activated
170
+ before that is at position 1 and so on. Views which have never been activated are
171
+ at the end of the array, in an arbitrary order
172
+
173
+ @param [Document,nil] doc the document to return the views for. If *nil*, all the
174
+ views will be returned
175
+ @return [Array<EditorView>] the views associated with the given document, if any,
176
+ or all the views, in activation order, from most recently activated to less recently
177
+ activated
178
+ =end
179
+ def views doc = nil
180
+ if doc then @view_manager.activation_order.select{|v| v.document == doc}
181
+ else @view_manager.activation_order.dup
182
+ end
183
+ end
184
+
139
185
  ##
140
186
  # :method: add_tool
141
187
  # Adds a tool widget to the main window. See Workspace#add_tool_widget for more information.
@@ -179,186 +225,353 @@ is the plugin description for this object.
179
225
  def_delegators :@workspace, :tool_widgets
180
226
 
181
227
  =begin rdoc
182
- Returns the editor view for the given document, creating it if needed
228
+ Returns an editor associated with the given document, creating it if needed
229
+
230
+ If _doc_ is not a document and no document for the file representing by the string
231
+ or URL _doc_ is open, it will be created.
232
+
233
+ Since there can be more than one editor associated with the document, the optional
234
+ _hints_ argument can be used to specify which one should be returned. If no editor
235
+ exists for the document, or none of the exisiting ones statisfy the @existing@
236
+ hint, a new one will be created, unless the @create_if_needed@ hint is *false*.
183
237
 
184
238
  @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
239
+ a relative path, it'll be considered relative to the current directory. If the
240
+ string is an Url, it'll be interpreted as such
241
+ @param [Hash] hints options to tailor the algorithm used to retrieve or create the editor
242
+
243
+ @option hints [Symbol] :existing (:always) when it is acceptable to return an already existing
244
+ editor. It can be:
245
+ * @:always@: if there's an existing editor, always use it
246
+ * @:never@: never use an exisiting editor
247
+ * @:current_tab@: use an exisiting editor only if it is in the current pane
248
+ @option hints [Boolean] :create_if_needed (true) whether or not a new editor for
249
+ the given document should be created if none exists. This is different from
250
+ passing @:always@ as the value of the @:existing@ option because the latter would
251
+ cause a new editor to be created in case no one already exists for the document
252
+ @option hints [Symbol, Array<Symbol>] :stategy ([:current, :current_tab, :first])
253
+ how to choose the exisiting editor to use in case there's more than one. If it
254
+ is an array, the strategies will be applied in turn until one has success.
255
+ If none of the given strategies succeed, @:first@ will be used. The possible
256
+ values are
257
+ * @:current@: if the current editor is associated with _doc_, use it
258
+ * @:current_tab@: if there are editors associated with _doc_ in the current
259
+ tab, the first of them will be used
260
+ * @:last_current_tab@: if there are editors associated with _doc_ in the current
261
+ tab, the first of them will be used
262
+ * @:next@: starting from the current tab, the first editor found associated
263
+ with _doc_ will be used
264
+ * @:previous@: the first editor associated with _doc_ found by looking in all
265
+ tabs from the current one in reverse order will be used (the current tab will
266
+ be the last one where the editor will be looked for)
267
+ * @:first@: the first editor associated with the document will be used
268
+ * @:last@: the last editor associated the document will be used
269
+
270
+ This option is only useful when using an existing editor
271
+ @option hints [Symbol,Integer,EditorView] :new (:new_tab) where to place the new editor, if
272
+ it needs to be created. It can have one of the following values:
273
+ * @:new_tab@: put the new editor as the single editor in a new tab
274
+ * @:current@: place the new editor in the pane obtained splitting the
275
+ current editor
276
+ * @:current_tab@: place the new editor in the pane obtained splitting the first
277
+ editor in the current tab
278
+ * an integer: place the new editor in the tab associated with that index (0-based),
279
+ in the pane obtained by splitting the first view
280
+ * an {EditorView}: place the new editor in the pane obtained splitting the
281
+ pane associated with the given editor
282
+
283
+ This option is only used when when creating a new editor
284
+ @option hints [Symbol] :split (:horizontal) the orientation in which an existing
285
+ editor should be split to accomodate the new editor. It can be @:horizontal@
286
+ or @:vertical@. This option is only used when when creating a new editor
287
+ @option hints [Boolean] :show (true) whether the new editor should be shown or
288
+ not. If it's *false*, the editor won't be placed in any pane. This option is
289
+ only used when when creating a new editor
290
+ @return [EditorView,nil] an editor associated with the document and *nil* if no
291
+ editor associated with the given document exists and the @:create_if_needed@ hint is
292
+ set to *false*.
293
+ @raise [ArgumentError] if _doc_ is a path or a @KDE::Url@ but the corresponding
294
+ file doesn't exist
191
295
  =end
192
- def editor_for! doc
296
+ def editor_for! doc, hints = DEFAULT_HINTS
297
+ hints = DEFAULT_HINTS.merge hints
298
+ docs = Ruber[:documents].documents
193
299
  unless doc.is_a? Document
194
- if doc.is_a? KDE::Url or doc.start_with? '/' then doc = Ruber[:documents].document doc
195
- else doc = Ruber[:documents].document_with_name doc
300
+ unless hints.has_key? :close_starting_document
301
+ hints[:close_starting_document] = docs.size == 1 &&
302
+ docs[0].extension(:ruber_default_document).default_document &&
303
+ docs[0].pristine?
196
304
  end
305
+ url = doc
306
+ if url.is_a? String
307
+ url = KDE::Url.new url
308
+ if url.relative?
309
+ path = File.expand_path url.path
310
+ url.path = path
311
+ end
312
+ end
313
+ doc = Ruber[:documents].document url
197
314
  end
198
315
  return unless doc
199
- create_editor_if_needed doc
316
+ @view_manager.without_activating{@view_manager.editor_for doc, hints}
200
317
  end
201
318
 
202
319
  =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*.
320
+ Returns an editor associated with the given document
321
+
322
+ It works mostly like {#editor_for!}, but it doesn't attempt to create the document
323
+ if it doesn't exist an always sets the @create_if_needed@ hint to *false*. See
324
+ {#editor_for!} for the values it can contain.
325
+
326
+ @param (see #editor_for!)
327
+ @return [EditorView,nil] an editor associated with the document and *nil* if no
328
+ editor associated with the given document exists or no document corresponds to _doc_
329
+ @raise [ArgumentError] if _doc_ is a path or a @KDE::Url@ but the corresponding
330
+ file doesn't exist
206
331
  =end
207
- def editor_for doc
208
- doc = Ruber[:documents][doc] if doc.is_a? String
332
+ def editor_for doc, hints = DEFAULT_HINTS
333
+ hints = DEFAULT_HINTS.merge hints
334
+ hints[:create_if_needed] = false
335
+ unless doc.is_a? Document
336
+ url = doc
337
+ if url.is_a? String
338
+ url = KDE::Url.new url
339
+ if url.relative?
340
+ path = File.expand_path url.path
341
+ url.path = path
342
+ end
343
+ end
344
+ doc = Ruber[:documents].document_for_url url
345
+ end
209
346
  return unless doc
210
- doc.view
347
+ @view_manager.editor_for doc, hints
211
348
  end
212
349
 
213
350
  =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.
351
+ The active editor
352
+
353
+ The active editor is the editor which has its GUI merged with the main window's.
354
+ This means it is the editor which last received focus and the one which would receive
355
+ focus when the tab widget does. If the focus already is in the tab widget, then
356
+ the active editor is the one whose @is_active_window@ method returns *true*.
219
357
 
220
- TODO: in all places this is called, change the name from <tt>current_view</tt> to
221
- <tt>active_editor</tt>.
358
+ @return [EditorView,nil] the active editor or *nil* if there's no active editor.
222
359
  =end
223
360
  def active_editor
224
- @active_editor
361
+ @view_manager.active_editor
225
362
  end
363
+ alias_method :current_editor, :active_editor
364
+
365
+ =begin rdoc
366
+ Activates an editor
367
+
368
+ If the editor is not in the current tab, the tab it belongs to becomes active.
369
+
370
+ Activating an editor means merging its GUI with the main window's GUI, changing
371
+ the title of the main window accordingly and telling the status bar to refer to
372
+ that view.
373
+
374
+ After activating the editor, the {#current_document_changed} and {#active_editor_changed}
375
+ signals are emitted.
376
+ @param [EditorView,nil] editor the editor to activate. If *nil*, then the currently active
377
+ editor will be deactivated
378
+ =end
379
+ def activate_editor editor
380
+ tab = @view_manager.tab editor
381
+ return unless tab
382
+ @tabs.current_widget = tab
383
+ return if active_editor == editor
384
+ @view_manager.make_editor_active editor
385
+ editor
386
+ end
226
387
 
227
388
  =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.
389
+ Replaces an editor with another
390
+
391
+ If the editor to be replaced is the only one associated witha given document and
392
+ the document is modified, the user is asked whether he wants to save it or not. If
393
+ he chooses to abort, the editor is not replaced, otherwise the document is closed.
394
+
395
+ The new editor is put in the same pane which contained the old one (without splitting
396
+ it).
397
+
398
+ @overload replace_editor old, new_ed
399
+ @param [EditorView] old the editor to replace
400
+ @param [EditorView] new_ed the editor to replace _old_ with
401
+ @return [EditorView,nil]
402
+ @overload replace_editor old, doc
403
+ @note whether or not the user needs to be asked about saving the document is determined
404
+ regardless of the value of _doc_. This means that the user may be asked
405
+ to save the document even if _doc_ is the same document associated with
406
+ the view (or the URL corresponding to it). To avoid this, create the replacement
407
+ editor before calling this method, using @editor_for doc, :existing => :never, :show => false@,
408
+ then use the overloaded version of this method which takes an editor
409
+ @param [EditorView] old the editor to replace
410
+ @param [Document,String,KDE::Url] doc the document to create the editor for or
411
+ the name of the corresponding file (absolute or relative to the current directory)
412
+ or the corresponding URL
413
+ @return [EditorView,nil]
414
+ @return [EditorView,nil] the new editor or *nil* if the user choose to abort
230
415
  =end
231
- def current_editor
232
- @views.current_widget
416
+ def replace_editor old, editor_or_doc
417
+ if old.document.views.size == 1
418
+ return unless old.document.query_close
419
+ close_doc = true
420
+ end
421
+ if editor_or_doc.is_a?(EditorView) then ed = editor_or_doc
422
+ else ed = editor_for! editor_or_doc, :existing => :never, :show => false
423
+ end
424
+ old.parent.replace_view old, ed
425
+ close_editor old, false
426
+ ed
233
427
  end
234
428
 
235
429
  =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.
430
+ The toplevel pane corresponding to the given index or editor
240
431
 
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.
432
+ @overload tab idx
433
+ @param [Integer] idx the index of the tab
434
+ @return [Pane] the toplevel pane in the tab number _idx_
435
+ @overload tab editor
436
+ @param [EditorView] editor the editor to retrieve the pane for
437
+ @return [Pane] the toplevel pane containing the given editor
245
438
  =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
439
+ def tab arg
440
+ @view_manager.tab arg
259
441
  end
260
442
 
261
443
  =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*.
444
+ The document associated with the active editor
445
+
446
+ This is a convenience method for @active_editor.document@ which takes care of the
447
+ case when there's no active editor.
448
+
449
+ @return [Document,nil] the document associated with the active editor or *nil*
450
+ if there's no active editor
267
451
  =end
268
452
  def current_document
269
- view = @views.current_widget
270
- view ? view.doc : nil
453
+ (ed = active_editor) ? ed.document : nil
271
454
  end
455
+ alias_method :active_document, :current_document
272
456
 
273
457
  =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).
458
+ Displays an editor for the given document
459
+
460
+ This method is similar to {#editor_for!} but, after retrieving the editor (creating
461
+ it and/or the document as needed), it activates and gives focus to it and moves
462
+ the cursor to the given position.
463
+
464
+ @see #editor_for! editor_for! for the possible entries in _hints_
465
+ @param (see #editor_for!)
466
+ @param [Integer,nil] line the line number to move the cursor to (0-based). Ignored if eiter
467
+ it or _col_ is *nil*
468
+ @param [Integer,nil] col the column number to move the cursor to (0-based). Ignored if eiter
469
+ it or _col_ is *nil*
470
+ @return [EditorView,nil] the editor which has been activated or *nil* if the
471
+ editor couldn't be found (or created)
277
472
  =end
278
- def display_doc doc, line = nil, col = 0
279
- ed = editor_for! doc
473
+ def display_doc doc, line = nil, col = nil, hints = DEFAULT_HINTS
474
+ ed = editor_for! doc, hints
280
475
  return unless ed
281
476
  activate_editor ed
282
477
  ed.go_to line, col if line and col
283
478
  ed.set_focus
479
+ ed
284
480
  end
285
- alias display_document display_doc
481
+ alias_method :display_document, :display_doc
286
482
 
287
483
  =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
484
+ Executes the given block without automatically activating an editor whenever the
485
+ current tab changes
486
+
487
+ This method should be used, for example, when more than one editor should be opened at
290
488
  once. Without this, every new editor would become (for a little time) the active
291
489
  one, with its gui merged with the main window and so on. This slows things down
292
490
  and should be avoided. To do so, you use this method:
293
- Ruber[:main_window].without_activating do
491
+
492
+ bc. Ruber[:main_window].without_activating do
294
493
  ed = nil
295
494
  files.each do |f|
296
495
  ed = Ruber[:main_window].editor_for! f
297
496
  end
298
497
  Ruber[:main_window].activate_editor ed
299
498
  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).
499
+
500
+ After calling this method, the focus widget of the current tab gets focus
501
+
502
+ @note automatical editor activation will be restored at the end of this
503
+ method (even if exceptions occur).
504
+ @yield the block which should be executed without automatically activating editors
505
+ @return [Object] the value returned by the block
305
506
  =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
507
+ def without_activating &blk
508
+ @view_manager.without_activating &blk
317
509
  end
318
510
 
319
511
  =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
512
+ Closes an editor view
513
+
514
+ If the editor to be closed is the last editor associated with the document the
515
+ document will be closed. If _ask_ is *true* and the document is modified, the user
516
+ will be asked whether to save or discard the changes and will have the possibility
517
+ of aborting closing the editor (and the document). If _ask_ is false, the document
518
+ will be closed without user interaction.
519
+
520
+ If there are other editors associated with the document besides the one to close,
521
+ the latter will be closed without affecting the document.
522
+
523
+ @param [EditorView] editor the editor to close
524
+ @param [Boolean] ask whether or not to ask confirmation from the user if the document
525
+ associated with _editor_ should be closed
526
+ @return [Boolean] *true* if the editor is closed and *false* if it isn't.
527
+ @note Always use this method to close an editor, rather than calling its {EditorView#close close}
528
+ method directly, unless you want to leave the corresponding document without a view
323
529
  =end
324
- def close_editor ed, ask = true
325
- ed.document.close_view ed, ask
530
+ def close_editor editor, ask = true
531
+ editor_tab = self.tab(editor)
532
+ has_focus = editor_tab.is_active_window if editor_tab
533
+ if has_focus
534
+ views = editor_tab.to_a
535
+ idx = views.index(editor)
536
+ new_view = views[idx-1] || views[idx+1]
537
+ end
538
+ doc = editor.document
539
+ if doc.views.size > 1
540
+ editor.close
541
+ true
542
+ else doc.close ask
543
+ end
544
+ focus_on_editor new_view if new_view
326
545
  end
327
546
 
328
547
  =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*.
548
+ Asks the user to save multiple documents
549
+
550
+ This method is meant to be called in situation where the user may want to save
551
+ a number of documents, for example when the application is quitting, as it avoids
552
+ displaying a dialog box for each modified document.
553
+
554
+ It displays a dialog where the user can choose, among the documents passed as
555
+ first argument, which ones he wants to save. The user has three choiches:
556
+ * save some (or all) the files, then proceed with the operation which has caused
557
+ the dialog to be shown (for example, quitting the application)
558
+ * don't save any file and go on with the operation
559
+ * abort the operation.
560
+
561
+ In the first case, this method attempts to perform save the selected files. If
562
+ any of them can't be saved, the dialog to choose the files to save will be
563
+ displayed again, with only those files which couldn't be saved (after informing
564
+ the user of the failure). The user can again chose which of those files this method
565
+ should attempt to save again, or whether to abort the operation or skip saving.
566
+ This will be repeated until all files have been saved or the user gives up
567
+
568
+ In the second and third cases, the method simply returns respectively *true* or
569
+ *false*.
570
+
571
+ @param [Array<Document>] docs the list of documents to save. If any document isn't
572
+ modified, it'll be ignored. If no document is mdified, nothing will be done
573
+ @return [Boolean] *true* if the operation which caused the dialog to be shown
574
+ can be carried on and *false* if the user chose to abort it
362
575
  =end
363
576
  def save_documents docs = nil
364
577
  docs ||= Ruber[:docs]
@@ -380,11 +593,12 @@ will ask whether to save modifications, otherwise it won't
380
593
  end
381
594
 
382
595
  =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
596
+ Override of {PluginLike#query_close}
597
+
598
+ It stores the session data retrieved by the component manager in case of session
599
+ saving and does nothing otherwise.
600
+
601
+ @return [TrueClass]
388
602
  =end
389
603
  def query_close
390
604
  if Ruber[:app].session_saving
@@ -394,18 +608,26 @@ a window
394
608
  end
395
609
 
396
610
  =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.
611
+ Opens a project, displaying amessage boxe in case of errors
612
+
613
+ This method provides a standard interface for creating a project from a project
614
+ file named, automatically handling the possible exceptions.
615
+
616
+ In particular, a message box will be displayed if the project file doesn't exist
617
+ or if it exists but it's not a valid project file.
618
+
619
+ If the project corresponding to the given file is already open, the behaviour
620
+ depends on the value of _allow_reuse_. If *false*, the user is warned with a message
621
+ box that the project is already open and nothing is done. If _allow_reuse_ is
622
+ *true*, the existing project is returned.
623
+
624
+ The new project will be made active and the existing one (if any) will be closed
625
+ @param [String] file the absolute path of the project file to open
626
+ @param [Boolean] allow_reuse what to do in case the project associated to _file_
627
+ is already open. If *true*, the existing project will be returned; if *false*,
628
+ the user will be warned and *nil* will be returned
629
+ @return [Project,nil] the project associated with _file_ or *nil* if an error occurs
630
+ (including the project being already open in case _allow_reuse_ is *false*)
409
631
  =end
410
632
  def safe_open_project file, allow_reuse = false
411
633
  prj = Ruber[:projects][file]
@@ -415,38 +637,39 @@ old active project (if any) will be closed.
415
637
  return nil
416
638
  elsif prj then return prj
417
639
  end
640
+ message = nil
418
641
  begin prj = Ruber[:projects].project file
419
642
  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
643
+ text = "%s isn't a valid project file. The error reported was:\n%s"
644
+ message = KDE.i18n(text) % [file, ex.message]
645
+ rescue LoadError then message = KDE.i18n(ex.message)
646
+ end
647
+ if prj
648
+ # The following two lines should be removed when we'll allow more than one project
649
+ # open at the same time
650
+ Ruber[:projects].current_project.close if Ruber[:projects].current_project
651
+ Ruber[:projects].current_project = prj
652
+ prj
653
+ else
654
+ KDE::MessageBox.sorry self, message
655
+ nil
429
656
  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
657
  end
436
658
 
437
659
  =begin rdoc
438
- Saves recent files and projects, the position of the splitters and the size of
660
+ Override of {PluginLike#save_settings}
661
+
662
+ It saves recent files and projects, the position of the splitters and the size of
439
663
  the window.
440
664
 
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.
665
+ @return [nil]
444
666
  =end
445
667
  def save_settings
446
668
  @workspace.store_sizes
447
- # TODO use Ruber[:config] as soon as it works
448
- # config = Ruber[:config].kconfig
449
- config = KDE::Global.config
669
+ # TODO see if the following line works. Otherwise, remove it and uncomment the one
670
+ # following it
671
+ config = Ruber[:config].kconfig
672
+ # config = KDE::Global.config
450
673
  recent_files = KDE::ConfigGroup.new config, 'Recent files'
451
674
  action_collection.action("file_open_recent").save_entries recent_files
452
675
  recent_projects = KDE::ConfigGroup.new config, 'Recent projects'
@@ -454,52 +677,71 @@ still necessary to store the window size.
454
677
  config.sync
455
678
  c = Ruber[:config][:main_window]
456
679
  c.window_size = rect.size
680
+ nil
457
681
  end
458
682
 
459
683
  =begin rdoc
460
- Loads the settings (in particular, the default script directory and the
461
- default project directory)
684
+ Override of {PluginLike#load_settings}
685
+ @return [nil]
462
686
  =end
463
687
  def load_settings
464
688
  c = Ruber[:config][:general]
465
689
  @default_script_dir = KDE::Url.from_path c.default_script_directory
466
690
  @default_project_dir = KDE::Url.from_path c.default_project_directory
691
+ @tabs.tabs_closable = Ruber[:config][:workspace, :close_buttons] if @tabs
692
+ nil
467
693
  end
468
694
 
469
695
  =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.
696
+ Gives focus to an editor view
697
+
698
+ Giving focus to the editor implies:
699
+ * bringing the tab containing it to the foreground
700
+ * activating the editor
701
+ * giving focus to the editor
702
+
703
+ @overload focus_on_editor
704
+ Gives focus to the active editor
705
+ @return [EditorView,nil]
706
+ @overload focus_on_editor editor
707
+ @param [EditorView] editor the editor to give focus to
708
+ @return [EditorView,nil]
709
+ @overload focus_on_editor doc, hints = DEFAULT_HINTS
710
+ @param [Document, String, KDE::Url] doc the document. If it is a string representing
711
+ a relative path, it'll be considered relative to the current directory. If the
712
+ string is an Url, it'll be interpreted as such
713
+ @param [Hash] hints options to tailor the algorithm used to retrieve or create the editor.
714
+ See {#editor_for!} for a description of the values it can contain
715
+ @return [EditorView,nil]
716
+ @return [EditorView,nil] the editor which was given focus or *nil* if no editor
717
+ received focus
476
718
  =end
477
- def focus_on_editor ed = nil
719
+ def focus_on_editor ed = nil, hints = DEFAULT_HINTS
478
720
  if ed
479
- ed = editor_for! ed unless ed.is_a? EditorView
480
- activate_editor ed if ed
721
+ ed = editor_for! ed, hints unless ed.is_a? EditorView
722
+ activate_editor ed
723
+ ed.set_focus
724
+ else active_editor.set_focus if active_editor
481
725
  end
482
- @active_editor.set_focus if @active_editor
726
+ active_editor
483
727
  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
-
728
+
492
729
  =begin rdoc
493
- The directory where to create new projects by default
730
+ @return [String] the default directory where to look for, and create, projects
494
731
  =end
495
732
  def projects_directory
496
733
  @default_project_dir
497
734
  end
498
735
 
499
736
  =begin rdoc
500
- Executes the action with name _action_ contained in the main window's action collection.
737
+ Executes a given action
738
+
739
+ @param [String] name the name by which the action has been registered in the
740
+ main window's @action_collection@
741
+ @param [Array] args a list of parameters to pass to the signal or slot associated
742
+ to the action
501
743
 
502
- See <tt>GuiPlugin#execute_action</tt> for more details
744
+ @see GuiPlugin#execute_action
503
745
  =end
504
746
  def execute_action name, *args
505
747
  data = plugin_description.actions[name.to_s]
@@ -518,6 +760,20 @@ See <tt>GuiPlugin#execute_action</tt> for more details
518
760
  else false
519
761
  end
520
762
  end
763
+
764
+ =begin rdoc
765
+ Settings widget for the workspace group
766
+ =end
767
+ class WorkspaceSettingsWidget < Qt::Widget
768
+ =begin rdoc
769
+ @param [Qt::Widget,nil] parent the parent widget
770
+ =end
771
+ def initialize parent = nil
772
+ super
773
+ @ui = Ui::WorkspaceSettingsWidgetBase.new
774
+ @ui.setup_ui self
775
+ end
776
+ end
521
777
 
522
778
  end
523
779