ruber 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +25 -0
- data/bin/ruber +0 -0
- data/data/share/apps/ruber/ruberui.rc +15 -1
- data/data/share/icons/{ruber.png → ruber-old.pgn} +0 -0
- data/lib/ruber/application/application.rb +216 -73
- data/lib/ruber/application/plugin.yaml +2 -2
- data/lib/ruber/document_project.rb +25 -5
- data/lib/ruber/documents/document_list.rb +11 -15
- data/lib/ruber/editor/document.rb +106 -50
- data/lib/ruber/editor/editor_view.rb +4 -2
- data/lib/ruber/external_program_plugin.rb +8 -0
- data/lib/ruber/kde_config_option_backend.rb +12 -4
- data/lib/ruber/kde_sugar.rb +35 -1
- data/lib/ruber/main_window/choose_plugins_dlg.rb +10 -10
- data/lib/ruber/main_window/hint_solver.rb +263 -0
- data/lib/ruber/main_window/main_window.rb +462 -206
- data/lib/ruber/main_window/main_window_actions.rb +228 -62
- data/lib/ruber/main_window/main_window_internal.rb +169 -115
- data/lib/ruber/main_window/plugin.yaml +13 -3
- data/lib/ruber/main_window/save_modified_files_dlg.rb +1 -1
- data/lib/ruber/main_window/ui/choose_plugins_widget.rb +1 -1
- data/lib/ruber/main_window/ui/main_window_settings_widget.rb +1 -1
- data/lib/ruber/main_window/ui/new_project_widget.rb +1 -1
- data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +1 -1
- data/lib/ruber/main_window/ui/output_color_widget.rb +1 -1
- data/lib/ruber/main_window/ui/workspace_settings_widget.rb +51 -0
- data/lib/ruber/main_window/ui/workspace_settings_widget.ui +28 -0
- data/lib/ruber/main_window/view_manager.rb +418 -0
- data/lib/ruber/main_window/workspace.png +0 -0
- data/lib/ruber/output_widget.rb +43 -37
- data/lib/ruber/pane.rb +621 -0
- data/lib/ruber/plugin_specification_reader.rb +8 -1
- data/lib/ruber/projects/project_files_list.rb +6 -0
- data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +1 -1
- data/lib/ruber/projects/ui/project_files_widget.rb +1 -1
- data/lib/ruber/qt_sugar.rb +94 -4
- data/lib/ruber/utils.rb +16 -7
- data/lib/ruber/version.rb +2 -2
- data/plugins/autosave/autosave.rb +62 -1
- data/plugins/autosave/plugin.yaml +1 -0
- data/plugins/autosave/ui/autosave_config_widget.rb +37 -14
- data/plugins/autosave/ui/autosave_config_widget.ui +62 -12
- data/plugins/find_in_files/find_in_files_widgets.rb +1 -3
- data/plugins/find_in_files/ui/config_widget.rb +1 -1
- data/plugins/find_in_files/ui/find_in_files_widget.rb +1 -1
- data/plugins/rake/plugin.yaml +1 -1
- data/plugins/rake/ui/add_quick_task_widget.rb +1 -1
- data/plugins/rake/ui/choose_task_widget.rb +1 -1
- data/plugins/rake/ui/config_widget.rb +1 -1
- data/plugins/rake/ui/project_widget.rb +1 -1
- data/plugins/rspec/rspec.rb +14 -22
- data/plugins/rspec/ruber_rspec_formatter.rb +4 -1
- data/plugins/rspec/ui/rspec_project_widget.rb +1 -1
- data/plugins/ruby_development/plugin.yaml +7 -2
- data/plugins/ruby_development/ruby_development.rb +134 -13
- data/plugins/ruby_development/ui/config_widget.rb +66 -0
- data/plugins/ruby_development/ui/config_widget.ui +58 -0
- data/plugins/ruby_development/ui/project_widget.rb +1 -1
- data/plugins/ruby_runner/plugin.yaml +2 -2
- data/plugins/ruby_runner/ruby_runner.rb +15 -3
- data/plugins/ruby_runner/ui/config_widget.rb +1 -1
- data/plugins/ruby_runner/ui/project_widget.rb +1 -1
- data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +1 -1
- data/plugins/state/plugin.yaml +6 -2
- data/plugins/state/state.rb +305 -81
- data/plugins/state/ui/config_widget.rb +1 -1
- data/spec/common.rb +11 -3
- data/spec/document_list_spec.rb +8 -8
- data/spec/document_project_spec.rb +98 -25
- data/spec/document_spec.rb +178 -152
- data/spec/editor_view_spec.rb +26 -5
- data/spec/framework.rb +5 -0
- data/spec/hint_solver_spec.rb +450 -0
- data/spec/kde_sugar_spec.rb +73 -6
- data/spec/output_widget_spec.rb +172 -156
- data/spec/pane_spec.rb +1165 -0
- data/spec/plugin_specification_reader_spec.rb +37 -1
- data/spec/project_files_list_spec.rb +30 -20
- data/spec/qt_sugar_spec.rb +269 -0
- data/spec/state_spec.rb +566 -353
- data/spec/utils_spec.rb +1 -1
- data/spec/view_manager_spec.rb +71 -0
- metadata +16 -4
Binary file
|
data/lib/ruber/output_widget.rb
CHANGED
@@ -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
|
-
|
727
|
-
|
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
|
743
|
-
* a relative path starting with
|
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
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
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]
|
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 <<
|
791
|
+
res << line.to_i if line
|
786
792
|
res
|
787
793
|
end
|
788
794
|
|
data/lib/ruber/pane.rb
ADDED
@@ -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
|