ruber 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|