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
@@ -663,7 +663,7 @@ likely doing that, not requesting to open a file
663
663
  return unless file
664
664
  line = file[1]
665
665
  line -= 1 if line > 0
666
- Ruber[:main_window].display_document file[0], line
666
+ Ruber[:main_window].display_document file[0], line, 0
667
667
  Ruber[:main_window].hide_tool self if (Qt::MetaModifier & modifiers) == 0
668
668
  end
669
669
 
@@ -720,14 +720,21 @@ method with one which always returns *nil*.
720
720
  else idx.data.to_string
721
721
  end
722
722
  res = find_filename_in_string str
723
+ d res
723
724
  return unless res
724
725
  res = Array res
725
726
  res << 0 if res.size == 1
726
- unless Pathname.new(res[0]).absolute?
727
- res[0] = File.join @working_dir, res[0]
727
+ #if res[0] is an url with scheme file:, transform it into a regular file
728
+ #name by removing the scheme and the two following slash
729
+ res[0].sub! %r{^file://}, ''
730
+ if KDE::Url.file_url?(res[0]) then res
731
+ else
732
+ res[0] = File.join @working_dir, res[0] unless Pathname.new(res[0]).absolute?
733
+ if File.exist?(res[0]) and !File.directory?(res[0])
734
+ res
735
+ else nil
736
+ end
728
737
  end
729
- return nil unless File.exist?(res[0]) and !File.directory?(res[0])
730
- res
731
738
  end
732
739
 
733
740
  =begin rdoc
@@ -739,50 +746,49 @@ What is a file name and what isn't is a bit arbitrary. Here's what this method
739
746
  recognizes as a filename:
740
747
  * an absolute path not containing spaces and colons starting with '/'
741
748
  * an absolute path not containing spaces and colons starting with '~' or '~user'
742
- (they're expanded using <tt>File.expand_path</tt>)
743
- * a relative path starting with . or .. (either followed by a slash or not)
749
+ (they're expanded using @File.expand_path@)
750
+ * a relative path starting with @./@ or @../@ (either followed by a slash or not)
751
+ * a relative path of the form @.filename@ or @.dirname/dir/file@
744
752
  * any string not containing spaces or colons followed by a colon and a line number
753
+ * absolute URLs with an authority component
754
+
755
+ File names enclosed in quotes or parentheses are recognized.
745
756
 
746
757
  The first three entries of the previous list can be followed by a colon and a line
747
758
  number; for the last one they're mandatory
748
759
  =end
749
- def find_filename_in_string str\
760
+ def find_filename_in_string str
750
761
  #This ensures that file names inside quotes or brackets are found. It's
751
762
  #easier replacing quotes and brackets with spaces than to modify the main
752
763
  #regexp to take them into account
753
764
  str = str.gsub %r|['"`<>\(\)\[\]\{\}]|, ' '
754
- reg = %r{(?: #This is the grouping for the big or operator
755
- (?:\s|^) #Here starts the first alternative. The filename must either
756
- #come after a whitespace or at the beginning of the string
757
- ( #Here starts the capturing group for the filename
758
- (?: #We have to consider separately strings with a known start (the
759
- # first branch of the |) and files without a definite start
760
- # containing a slash (the second branch)
761
- (?:/|~|\.\.?) # This non-capturing group is for the beginning of
762
- # the file. It may start with a slash (absolute path),
763
- # a tilde (absolute path for the home directory)
764
- # or a dot (relative path)
765
- [^\s:]* # The start of the filename is followed by any number of
766
- # non-space, non-colon character
767
- |[^\s:/]+/[^\s:/]*) #here we deal with files starting with any other
768
- #character and containing at least one slash
769
- [^\s:/]) # and by at least one non-colon, non-space and non-slash
770
- # character (to attempt to avoid directories, which may
771
- # end with a slash)
772
- (?:\s|$|(?::(\d+)))) # the file name can be followed by either a
773
- # space, the end of the string or a colon and
774
- # some digits (the line number)
775
- |(?:([^\s]+):(\d+) # Here's the second alternative: anything except
776
- # a space followed by a colon and at least one
777
- # digit (the line number)
778
- ) #The end of the large group}x
779
- match = reg.match str
765
+ matches = []
766
+ attempts = [
767
+ %r{(?:^|\s)([\w+.-]+:/{1,2}(?:/[^/:\s]+)+)(?::(\d+))?(?:$|[,.;:\s])}, #URLS
768
+ %r{(?:^|\s)((?:/[^/\s:]+)+)(?::(\d+))?(?:$|[,.;:\s])}, #absolute files
769
+ #absolute files referring to user home directory: ~/xyz or ~user/xyz
770
+ %r{(?:^|\s)(~[\w_-]*(?:/[^/\s:]+)+)(?::(\d+))?(?:$|[,.;:\s])},
771
+ #relative files starting with ./ and ../
772
+ %r{(?:^|\s)(\.{1,2}(?:/[^/\s:]+)+)(?::(\d+))?(?:$|[,.;:\s])},
773
+ #hidden files or directories (.filename or .dir/filename)
774
+ %r{(?:^|\s)(\.[^/\s:]+(?:/[^/\s:]+)*)(?::(\d+))?(?:$|[,.;:\s])},
775
+ #relative files containing, but not ending with a slash
776
+ %r{(?:^|\s)([^/\s:]+/[^\s:]*[^\s/:])(?::(\d+))?(?:$|[,.;:\s])},
777
+ #relative files not containing slashes but ending with the line number
778
+ %r{(?:^|\s)([^/\s:]+):(\d+)(?:$|[,.;:\s])}
779
+ ]
780
+ attempts.each do |a|
781
+ m = str.match a
782
+ matches << [m.begin(0),[$1,$2]] if m
783
+ end
784
+ d str
785
+ d matches
786
+ match = matches.sort_by{|i| i[0]}[0]
780
787
  return unless match
781
- file = match[1] || match[3]
782
- ln = match[1] ? match[2] : match[4]
788
+ file, line = *match[1]
783
789
  file = File.expand_path(file) if file.start_with? '~'
784
790
  res = [file]
785
- res << ln.to_i if ln
791
+ res << line.to_i if line
786
792
  res
787
793
  end
788
794
 
@@ -0,0 +1,621 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'facets/boolean'
22
+
23
+ module Ruber
24
+
25
+ =begin rdoc
26
+ Container used to organize multiple editor views in a tab
27
+
28
+ A pane can either contain a single non-{Pane} widget (usually an {EditorView})
29
+ or multiple {Pane}s in a @Qt::Splitter@. In the first case, the {Pane} is said
30
+ to be in _single view mode_, while in the second it's said to be in _multiple view
31
+ mode_.
32
+
33
+ A {Pane} is said to be a direct child of (or directly contained in) another {Pane}
34
+ if it is one of the widgets contained in the second {Pane}'s splitter. A non-{Pane}
35
+ widget is said to be a direct child of (or directly contained in) a {Pane} if the
36
+ pane is in single view mode and contains that widget or if it is in multiple view
37
+ mode and one of the widgets in the splitter is in single view mode and contains
38
+ that widget.
39
+
40
+ The most important method provided by this class is {#split}, which allows a view
41
+ to be split either horizontally or vertically.
42
+
43
+ Whenever a view is closed, it is automatically removed from the pane, and panes
44
+ are automatically rearranged. The only situation you should care for this is when
45
+ the last view of a top-level pane is closed. In this case, the pane emits the {#closing_last_view}
46
+ signal.
47
+
48
+ *Note*: the pane containing a given view (or pane) can change without warning, so
49
+ never store them. To access the pane directly containing another pane, use {#parent_pane}
50
+ on the child pane; to access the pane directly containing another widget, call
51
+ the widget's @parent@ method.
52
+
53
+ *Note:* this class allows to access the splitter widget. This is only meant to
54
+ allow to resize it. It *must not* be used to add or remove widgets to or from it.
55
+ =end
56
+ class Pane < Qt::Widget
57
+
58
+ include Enumerable
59
+
60
+ =begin rdoc
61
+ @overload initialize(view, parent = nil)
62
+ Creates a {Pane} containing a single view
63
+ @param [EditorView] view the view to insert in the pane
64
+ @param [Qt::Widget,nil] parent the parent widget
65
+ @return [Pane]
66
+ @overload initialize(orientation, pane1, pane2, parent = nil)
67
+ Creates a {Pane} containing two other panes in a splitter widget
68
+ @param [Integer] orientation the orientation of the splitter. It can be @Qt::Horizontal@
69
+ or @Qt::Vertical@
70
+ @param [Pane] pane1 the pane to put in the first sector of the splitter
71
+ @param [Pane] pane2 the pane to put in the second sector of the splitter
72
+ @param [Qt::Widget,nil] parent the parent widget
73
+ @return [Pane]
74
+ =end
75
+ def initialize *args
76
+ case args.size
77
+ when 1..2
78
+ super args[1]
79
+ @view = args[0]
80
+ @view.parent = self
81
+ self.layout = Qt::VBoxLayout.new self
82
+ layout.add_widget @view
83
+ @splitter = nil
84
+ connect view, SIGNAL('closing(QWidget*)'), self, SLOT('remove_view(QWidget*)')
85
+ when 3..4
86
+ super args[3]
87
+ self.layout = Qt::VBoxLayout.new self
88
+ orientation, pane1, pane2 = args[0..3]
89
+ @splitter = Qt::Splitter.new orientation, self
90
+ layout.add_widget @splitter
91
+ insert_widget 0, pane1
92
+ insert_widget 1, pane2
93
+ @view = nil
94
+ end
95
+ margins = layout.contents_margins
96
+ margins.top = margins.left = margins.bottom = margins.right = 0
97
+ layout.contents_margins = margins
98
+ @label = Qt::Label.new '', self
99
+ @label.hide unless parent_pane
100
+ layout.add_widget @label
101
+ end
102
+
103
+ =begin rdoc
104
+ @return [Pane] this pane's containing pane
105
+ =end
106
+ def parent_pane
107
+ if parent.is_a?(Qt::Splitter)
108
+ pane = parent.parent
109
+ pane.is_a?(Pane) ? pane : nil
110
+ end
111
+ end
112
+
113
+ =begin rdoc
114
+ Whether the pane contains another pane or widget
115
+
116
+ @overload contain? widget
117
+ Whether the pane contains, directly or indirectly, another pane or widget
118
+ @param [Pane,Qt::Widget] widget the {Pane} or widget to look for
119
+ @return [Boolean] *true* if _widget_ is directly or indirectly contained in this
120
+ pane and *false* if it isn't contained in it
121
+
122
+ @overload contain? widget, :directly
123
+ Whether the pane directly contains another pane or widget
124
+ @param [Pane,Qt::Widget] widget the {Pane} or widget to look for
125
+ @return [Boolean] *true* if _widget_ is directly contained in this
126
+ pane and *false* if it isn't contained in it or it's contained indirectly
127
+ =end
128
+ def contain? widget, mode = nil
129
+ if mode
130
+ if @splitter then @splitter.any?{|w| w == widget}
131
+ else @view == widget
132
+ end
133
+ else find_children(Pane).include? widget
134
+ end
135
+ end
136
+
137
+ =begin rdoc
138
+ Signal emitted whenever the single view associated with the paned is about to be closed
139
+ @param [Pane] pane the pane which emitted the signal
140
+ =end
141
+ signals 'closing_last_view(QWidget*)'
142
+
143
+ =begin rdoc
144
+ Signal emitted whenever a view in the pane or one of its children has been removed
145
+
146
+ @param [Pane] pane the pane the view was child of
147
+ @param [Qt::Widget] view the view which was removed from the pane. Note that when
148
+ this signal is emitted, the view hasn't as yet been destroyed, but it has already
149
+ been removed from the pane (@view.parent@ returns *nil*)
150
+ =end
151
+ signals 'removing_view(QWidget*, QWidget*)'
152
+
153
+ =begin rdoc
154
+ Signal emitted after the pane has been split
155
+
156
+ @param [Pane] pane the pane which has been split
157
+ @param [Qt::Widget] old_view the view which has been split
158
+ @param [Qt::Widget] new_view the view which has been inserted
159
+ =end
160
+ signals 'pane_split(QWidget*, QWidget*, QWidget*)'
161
+
162
+ =begin rdoc
163
+ Signal emitted after a view has been replaced
164
+
165
+ @param [Pane] pane the pane the original view was child of
166
+ @param [Qt::Widget] old_view the view which was replaced. Note that when
167
+ this signal is emitted, the view hasn't been destroyed, but it has
168
+ been removed from the pane (@old_view.parent@ returns *nil*)
169
+ @param [Qt::Widget] replacement the view which replaced _old_view_. Note that at
170
+ the time this signal is emitted, the new view has already been made a child of
171
+ the pane
172
+ =end
173
+ signals 'view_replaced(QWidget*, QWidget*, QWidget*)'
174
+
175
+ =begin rdoc
176
+ @return [Qt::Splitter,nil] the splitter used by the pane or *nil* if the pane
177
+ contains a single view
178
+ =end
179
+ attr_reader :splitter
180
+
181
+ =begin rdoc
182
+ Whether the pane contains a single view or not
183
+
184
+ @return [Boolean] *true* if the pane contains a single view an *false* if it contains
185
+ a splitter with multiple panes
186
+ =end
187
+ def single_view?
188
+ @view.to_b
189
+ end
190
+
191
+ =begin rdoc
192
+ The view contained in the pane
193
+ @return [EditorView, nil] the view contained in the pane or *nil* if the pane contains
194
+ multiple panes
195
+ =end
196
+ def view
197
+ @view
198
+ end
199
+
200
+ =begin rdoc
201
+ The orientation in which the pane is split
202
+ @return [Integer,nil] @Qt::Horizontal@ or @Qt::Vertical@ according to the orientation
203
+ of the splitter or *nil* if the pane contains a single view
204
+ =end
205
+ def orientation
206
+ @splitter ? @splitter.orientation : nil
207
+ end
208
+
209
+ =begin rdoc
210
+ Splits the pane in two in correspondence to the given view
211
+
212
+ The place previously occupated by the view is divided bewtween it and another view,
213
+ _new_view_. If needed, other panes are created to accomodate them.
214
+
215
+ The view to split must already be contained in the pane. It can be contained directly,
216
+ as the only view of the pane or inserted in the splitter contained in the pane,
217
+ or indirectly, contained in one of the panes contained by this pane.
218
+
219
+ If _view_ is contained indirectly, the method call will be redirected to the correct
220
+ pane (not the one associated with the view but the pane containing the latter).
221
+
222
+ If _view_ is not contained in this pane, nothing is done.
223
+
224
+ _new_view_ must not be associated with a pane. When this method returns, a new
225
+ pane for it will have been created.
226
+
227
+ *Note:* after calling this method, the pane associated with _view_ may be changed.
228
+ @param [Ruber::EditorView] view the view to split
229
+ @param [Ruber::EditorView] new_view the view to insert alongside _view_
230
+ @param [Integer] orientation whether the view should be split horizontally or
231
+ vertically. It can be @Qt::Horizontal@ or @Qt::Vertical@
232
+ @param [Symbol] pos whether _new_view_ should be put after or before _view_. It
233
+ can be @:after@ or @:before@
234
+ @return [Array(Pane, Pane), nil] an array containing the panes associated with _view_
235
+ and with _new_view_. If _view_ wasn't contained in the pane, *nil* is returned
236
+ =end
237
+ def split view, new_view, orientation, pos = :after
238
+ idx = index_of_contained_view view
239
+ return split_recursive view, new_view, orientation, pos unless idx
240
+ new_pane = Pane.new(new_view)
241
+ multiple_view_mode orientation
242
+ old_pane = @splitter.widget idx
243
+ keeping_focus old_pane do
244
+ if @splitter.orientation == orientation
245
+ idx += 1 if pos == :after
246
+ insert_widget idx, new_pane
247
+ else
248
+ pane = old_pane.multiple_view_mode orientation
249
+ new_idx = pos == :after ? 1 : 0
250
+ old_pane.insert_widget new_idx, new_pane
251
+ old_pane = pane
252
+ end
253
+ end
254
+ emit pane_split self, view, new_view
255
+ [old_pane, new_pane]
256
+ end
257
+
258
+ =begin rdoc
259
+ Replaces a view with another
260
+
261
+ If the pane is in single view mode and its view is the same as _old_, the view is
262
+ replaced with _replacement_. If the view contained in the pane is different from
263
+ _old_, nothing is done.
264
+
265
+ If the pane is in multiple view mode, the method call will be propagated to all
266
+ child panes, until one is able to carry out the replacement.
267
+
268
+ @param [Qt::Widget] old the view to replace
269
+ @param [Qt::Widget] replacement the view to insert in the pane in place of _old_
270
+ @return [Boolean] *true* if the replacement was performed (that is, if the pane
271
+ or one of its children contained _old_) and *false* otherwise
272
+ =end
273
+ def replace_view old, replacement
274
+ if @view
275
+ return false unless @view == old
276
+ @view = replacement
277
+ replacement.parent = self
278
+ layout.insert_widget 0, replacement
279
+ disconnect old, SIGNAL('closing(QWidget*)'), self, SLOT('remove_view(QWidget*)')
280
+ connect replacement, SIGNAL('closing(QWidget*)'), self, SLOT('remove_view(QWidget*)')
281
+ layout.remove_widget old
282
+ old.parent = nil
283
+ emit view_replaced(self, old, replacement)
284
+ true
285
+ else each_pane.any?{|pn| pn.replace_view old, replacement}
286
+ end
287
+ end
288
+
289
+ =begin rdoc
290
+ Iterates on child panes
291
+
292
+ @overload each_pane
293
+ Iterates only on panes which are directly contained in this pane. Does nothing
294
+ if the pane is in single view mode.
295
+ @yieldparam [Pane] pane a child pane
296
+ @overload each_pane(:recursive)
297
+ Iterates on all contained panes (recursively). Does nothing if the pane is in
298
+ single view mode.
299
+ @yieldparam [Pane] pane a child pane
300
+ @return [Pane,Enumerator] if called with a block returns *self*, otherwise an Enumerator
301
+ which iterates on the contained panes, acting recursively or not according to
302
+ which of the two forms is called
303
+ =end
304
+ def each_pane mode = :flat, &blk
305
+ return to_enum(:each_pane, mode) unless block_given?
306
+ return self unless @splitter
307
+ if mode == :flat then @splitter.each{|w| yield w}
308
+ else
309
+ @splitter.each do |w|
310
+ yield w
311
+ w.each_pane :recursive, &blk
312
+ end
313
+ end
314
+ self
315
+ end
316
+
317
+ =begin rdoc
318
+ Changes the text of the label for the given view
319
+
320
+ If the pane is in single view mode and the view given as argument is the same
321
+ contained in it, the text of the label will be changed, otherwise nothing will be
322
+ done. If the pane is not a toplevel pane the label will be also made visible. If
323
+ the pane is a toplevel pane, the label won't be shown. The rationale for this
324
+ behaviour is that the label can be used to distinguish different widgets in the
325
+ same pane. If a top-level pane is in single view mode, however, it contains no
326
+ other views, so there's no need for a label to distinguish them.
327
+
328
+ If the text is an empty string, the label will be hidden.
329
+
330
+ If the pane is in multiple view mode, the method call will be propagated recursively
331
+ to child panes, until a pane containing the given view is found.
332
+
333
+ @param [Qt::Widget] view the view to change the label for
334
+ @param [String] text the new label
335
+ @return [Boolean] *true* if the label was changed either in this pane or in one
336
+ of its children and *false* if neither this pane nor any of its children contain
337
+ _view_
338
+ =end
339
+ def set_view_label view, text
340
+ if single_view?
341
+ return false unless @view == view
342
+ @label.text = text
343
+ @label.visible = !text.empty? if parent_pane
344
+ true
345
+ else each_pane.any? {|pn| pn.set_view_label view, text}
346
+ end
347
+ end
348
+
349
+ =begin rdoc
350
+ Changes the label of the view in the pane
351
+
352
+ If the pane is in multiple view mode, nothing is done.
353
+
354
+ If the text is empty the label is hidden. If the text is not empty, the label
355
+ is made visible, unless the pane is top-level.
356
+
357
+ This method is similar to {#set_view_label}, except that it doesn't allow to specify
358
+ the view to change the label for and always acts on the view contained in this pane.
359
+
360
+ @param [String] text the new label
361
+ =end
362
+ def label= text
363
+ set_view_label @view, text if @view
364
+ end
365
+
366
+ =begin rdoc
367
+ The text of label associated with the pane
368
+
369
+ @return [String,nil] the text of the label associated with the pane or *nil* if
370
+ the pane is in multiple view mode
371
+ =end
372
+ def label
373
+ # For some reason, Qt::Label#text returns nil if the text hasn't been set
374
+ # or is set to ''
375
+ @view ? (@label.text || '') : nil
376
+ end
377
+
378
+ =begin rdoc
379
+ Iterates on all views contained in the pane
380
+
381
+ This method always acts recursively, meaning that views indirectly contained in
382
+ the pane are returned.
383
+
384
+ If the pane is in single view mode, that only view is passed to the block.
385
+
386
+ @yieldparam [EditorView] view a view contained (directly) in the pane
387
+ @return [Pane,Enumerator] if a block is given then *self*, otherwise an Enumerator
388
+ which iterates on all the views
389
+ =end
390
+ def each_view &blk
391
+ return to_enum(:each_view) unless block_given?
392
+ if single_view? then yield @view
393
+ else
394
+ each_pane(:recursive) do |pn|
395
+ yield pn.view if pn.single_view?
396
+ end
397
+ end
398
+ self
399
+ end
400
+ alias_method :each, :each_view
401
+
402
+ =begin rdoc
403
+ @return [Array<Qt::Widget>] a list of all the views contained (directly or not)
404
+ in the pane
405
+ =end
406
+ def views
407
+ to_a
408
+ end
409
+
410
+ protected
411
+
412
+ =begin rdoc
413
+ Prepares the pane for a contained pane to be moved elsewhere
414
+
415
+ In practice, this makes the pane to remove parentless and disconnects the {#closing_last_view}
416
+ signal from *self*.
417
+ @param [Pane] the pane which will be removed
418
+ @return [nil]
419
+ =end
420
+ def take_pane pane
421
+ pane.parent = nil
422
+ pane.disconnect SIGNAL('closing_last_view(QWidget*)'), self, SLOT('remove_pane(QWidget*)')
423
+ pane.disconnect SIGNAL('pane_split(QWidget*, QWidget*, QWidget*)'), self, SIGNAL('pane_split(QWidget*, QWidget*, QWidget*)')
424
+ pane.disconnect SIGNAL('removing_view(QWidget*, QWidget*)'), self, SIGNAL('removing_view(QWidget*, QWidget*)')
425
+ pane.disconnect SIGNAL('view_replaced(QWidget*,QWidget*,QWidget*)'), self, SIGNAL('view_replaced(QWidget*,QWidget*,QWidget*)')
426
+ nil
427
+ end
428
+
429
+ =begin rdoc
430
+ Inserts a widget in the splitter
431
+
432
+ If the widget is not a pane, it'll be enclosed in the pane (and become a child of
433
+ it). The {#closing_last_view} signal of the pane will be connected with the {#remove_pane}
434
+ slot of *self*.
435
+
436
+ This method assumes that the pane is not in single view mode
437
+ @param [Integer] idx the index to insert the widget at
438
+ @param [Qt::Widget] widget the widget to insert. If it's not a {Pane}, it'll be
439
+ enclosed in a new pane
440
+ @return [Pane] the pane inserted in the splitter. It'll be _widget_ if it's already
441
+ a {Pane} or the new pane enclosing it if it isn't
442
+ =end
443
+ def insert_widget idx, widget
444
+ widget = Pane.new widget, @splitter unless widget.is_a? Pane
445
+ @splitter.insert_widget idx, widget
446
+ connect widget, SIGNAL('closing_last_view(QWidget*)'), self, SLOT('remove_pane(QWidget*)')
447
+ connect widget, SIGNAL('pane_split(QWidget*,QWidget*,QWidget*)'), self, SIGNAL('pane_split(QWidget*,QWidget*,QWidget*)')
448
+ connect widget, SIGNAL('removing_view(QWidget*, QWidget*)'), self, SIGNAL('removing_view(QWidget*, QWidget*)')
449
+ connect widget, SIGNAL('view_replaced(QWidget*,QWidget*,QWidget*)'), self, SIGNAL('view_replaced(QWidget*,QWidget*,QWidget*)')
450
+ widget
451
+ end
452
+
453
+ =begin rdoc
454
+ Switches the pane to multiple view mode
455
+
456
+ It disconnects the view's {#closing} signal from *self*, creates a new splitter
457
+ and inserts a new pane for the view in it.
458
+
459
+ It does nothing if the pane is already in multiple view mode
460
+ @param [Integer] orientation the orientation of the new splitter. It can be
461
+ @Qt::Vertical@ or @Qt::Horizontal@
462
+ @return [Pane] the new pane associated with the view
463
+ =end
464
+ def multiple_view_mode orientation
465
+ return if @splitter
466
+ keeping_focus @view do
467
+ layout.remove_widget @view
468
+ @label.hide
469
+ @view.disconnect SIGNAL('closing(QWidget*)'), self, SLOT('remove_view(QWidget*)')
470
+ @splitter = Qt::Splitter.new orientation, self
471
+ layout.insert_widget 0, @splitter
472
+ pane = insert_widget 0, @view
473
+ # For some reason, Qt::Label#text returns nil if the text hasn't been set
474
+ # or is set to ''
475
+ pane.label = @label.text || ''
476
+ @view = nil
477
+ pane
478
+ end
479
+ end
480
+
481
+ =begin rdoc
482
+ Switches the pane to single view mode
483
+
484
+ It disconnects the {#closing_last_view} signal of each pane in the splitter from
485
+ self, deletes the splitter then sets the given view as single view for *self*.
486
+
487
+ It does nothing if the splitter is already in single view mode
488
+ @param [EditorView] view the single view to insert in the pane
489
+ @param [String] label the label to assign to the pane
490
+ @return [Pane] self
491
+ =end
492
+ def single_view_mode view, label = ''
493
+ return unless @splitter
494
+ keeping_focus view do
495
+ @view = view
496
+ @view.parent = self unless @view.parent == self
497
+ @splitter.each{|w| w.disconnect SIGNAL('closing_last_view(QWidget*)'), self, SLOT('remove_pane(QWidget*)')}
498
+ layout.remove_widget @splitter
499
+ layout.insert_widget 0, @view
500
+ self.label = label
501
+ @label.visible = false unless parent_pane
502
+ connect @view, SIGNAL('closing(QWidget*)'), self, SLOT('remove_view(QWidget*)')
503
+ self
504
+ end
505
+ end
506
+
507
+
508
+ private
509
+
510
+ def keeping_focus *widgets
511
+ active_window = widgets.find{|w| w.is_active_window}
512
+ focus_widget = Ruber::Application.focus_widget if active_window
513
+ begin yield
514
+ ensure
515
+ if focus_widget and !focus_widget.disposed?
516
+ focus_widget.set_focus
517
+ end
518
+ end
519
+ end
520
+
521
+ =begin rdoc
522
+ Returns the index of the pane containing given view in the splitter
523
+
524
+ If the pane is in single view mode, it returns 0 if the single view is _view_
525
+ and *nil* otherwise.
526
+
527
+ If the pane is in multiple view mode, it returns the index of the pane directly
528
+ containing the view in the splitter or *nil* none of the panes in the splitter
529
+ directly contain _view_.
530
+
531
+ *Note:* this method is not recursive. This means that if the splitter of *self*
532
+ contains the pane @A@, whose splitter contains the pane @B@ which contains _view_,
533
+ this method returns *nil*.
534
+
535
+ @param [EditorView] view the view whose index should be returned
536
+ @return [Integer,nil] the index in the splitter corresponding to the pane containing
537
+ _view_ or *nil* if none of the panes contain it
538
+ =end
539
+ def index_of_contained_view view
540
+ if @splitter then @splitter.find_index{|w| w.view == view}
541
+ elsif @view == view then 0
542
+ else nil
543
+ end
544
+ end
545
+
546
+ =begin rdoc
547
+ Redirects the call to {#split} to the pane which actually contains the pane
548
+ containing the given view
549
+
550
+ It calls {#split} with the arguments passed to it to all panes contained in the splitter
551
+ until one of them returns non-nil, then stops
552
+ @param (see #split)
553
+ @return [Array(Pane, Pane),nil] an array containing the panes associated with _view_
554
+ and with _new_view_. If _view_ wasn't contained in any of the children panes, *nil*
555
+ is returned
556
+ =end
557
+ def split_recursive view, new_view, orientation, pos
558
+ return nil unless @splitter
559
+ @splitter.each do |w|
560
+ res = w.split view, new_view, orientation, pos
561
+ return res if res
562
+ end
563
+ nil
564
+ end
565
+
566
+ =begin rdoc
567
+ Slot called when the single view contained in the pane is closed
568
+
569
+ It emis the {#closing_last_view} signal passing *self* as argument, makes the
570
+ view parentless and schedules *self* for deletion.
571
+
572
+ *Note:* this method assumes the {Pane} is in single view mode
573
+ @param [EditorView] view the view which is being closed
574
+ @return [nil]
575
+ =end
576
+ def remove_view view
577
+ emit closing_last_view(self)
578
+ @view.parent = nil
579
+ emit removing_view self, view
580
+ delete_later
581
+ nil
582
+ end
583
+ slots 'remove_view(QWidget*)'
584
+
585
+ =begin rdoc
586
+ Removes the given pane from the pane
587
+
588
+ Depending on the situation, removing a pane causes different steps to be taken:
589
+ * the pane is always removed from the splitter
590
+ * if only one pane remains and it's in single view mode, it'll be deleted and the
591
+ widget it contain will be displayed in this pane, which will be switched to single
592
+ view mode
593
+ * if only one pane remains and it's in multiple view mode, the pane it contains
594
+ will be moved to this pane instead. The remaining one will be removed
595
+ *Note:* this method is usually called in response to the {#closing_last_view} signal
596
+ emitted by a chid pane, so it assumes the pane is in single view mode.
597
+ @param [Pane] the pane to remove
598
+ @return [nil]
599
+ =end
600
+ def remove_pane pane
601
+ pane.parent = nil
602
+ if @splitter.count == 1
603
+ remaining_pane = @splitter.widget(0)
604
+ if remaining_pane.single_view?
605
+ single_view_mode remaining_pane.view, remaining_pane.label
606
+ else
607
+ take_pane remaining_pane
608
+ remaining_pane.splitter.to_a.each_with_index do |w, i|
609
+ remaining_pane.take_pane w
610
+ insert_widget i, w
611
+ end
612
+ end
613
+ remaining_pane.delete_later
614
+ end
615
+ nil
616
+ end
617
+ slots 'remove_pane(QWidget*)'
618
+
619
+ end
620
+
621
+ end