ruber 0.0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +339 -0
- data/INSTALL +137 -0
- data/LICENSE +8 -0
- data/bin/ruber +65 -0
- data/data/share/apps/ruber/core_components.yaml +31 -0
- data/data/share/apps/ruber/ruberui.rc +109 -0
- data/data/share/icons/ruber.png +0 -0
- data/data/share/pixmaps/ruby.png +0 -0
- data/icons/ruber-16.png +0 -0
- data/icons/ruber-32.png +0 -0
- data/icons/ruber-48.png +0 -0
- data/icons/ruber-8.png +0 -0
- data/lib/ruber/application/application.rb +288 -0
- data/lib/ruber/application/plugin.yaml +11 -0
- data/lib/ruber/component_manager.rb +899 -0
- data/lib/ruber/config/config.rb +82 -0
- data/lib/ruber/config/plugin.yaml +3 -0
- data/lib/ruber/document_project.rb +209 -0
- data/lib/ruber/documents/document_list.rb +416 -0
- data/lib/ruber/documents/plugin.yaml +4 -0
- data/lib/ruber/editor/document.rb +506 -0
- data/lib/ruber/editor/editor_view.rb +167 -0
- data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
- data/lib/ruber/exception_widgets.rb +245 -0
- data/lib/ruber/external_program_plugin.rb +397 -0
- data/lib/ruber/filtered_output_widget.rb +342 -0
- data/lib/ruber/gui_states_handler.rb +231 -0
- data/lib/ruber/kde_config_option_backend.rb +167 -0
- data/lib/ruber/kde_sugar.rb +249 -0
- data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
- data/lib/ruber/main_window/main_window.rb +524 -0
- data/lib/ruber/main_window/main_window_actions.rb +537 -0
- data/lib/ruber/main_window/main_window_internal.rb +239 -0
- data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
- data/lib/ruber/main_window/output_color_widget.rb +35 -0
- data/lib/ruber/main_window/plugin.yaml +58 -0
- data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
- data/lib/ruber/main_window/status_bar.rb +156 -0
- data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
- data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
- data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
- data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
- data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
- data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
- data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
- data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
- data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
- data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
- data/lib/ruber/main_window/workspace.rb +442 -0
- data/lib/ruber/output_widget.rb +1093 -0
- data/lib/ruber/plugin.rb +264 -0
- data/lib/ruber/plugin_like.rb +589 -0
- data/lib/ruber/plugin_specification.rb +106 -0
- data/lib/ruber/plugin_specification_reader.rb +451 -0
- data/lib/ruber/project.rb +493 -0
- data/lib/ruber/project_backend.rb +105 -0
- data/lib/ruber/projects/plugin.yaml +11 -0
- data/lib/ruber/projects/project_files_list.rb +314 -0
- data/lib/ruber/projects/project_files_widget.rb +301 -0
- data/lib/ruber/projects/project_list.rb +314 -0
- data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
- data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
- data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
- data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
- data/lib/ruber/qt_sugar.rb +673 -0
- data/lib/ruber/settings_container.rb +515 -0
- data/lib/ruber/settings_dialog.rb +244 -0
- data/lib/ruber/settings_dialog_manager.rb +503 -0
- data/lib/ruber/utils.rb +414 -0
- data/lib/ruber/yaml_option_backend.rb +159 -0
- data/outsider_files +15 -0
- data/plugins/autosave/autosave.rb +404 -0
- data/plugins/autosave/plugin.yaml +16 -0
- data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
- data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
- data/plugins/command/command.png +0 -0
- data/plugins/command/command.rb +74 -0
- data/plugins/command/plugin.yaml +11 -0
- data/plugins/find_in_files/find_in_files.rb +337 -0
- data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
- data/plugins/find_in_files/find_in_files_ui.rc +11 -0
- data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
- data/plugins/find_in_files/plugin.yaml +23 -0
- data/plugins/find_in_files/ui/config_widget.rb +58 -0
- data/plugins/find_in_files/ui/config_widget.ui +41 -0
- data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
- data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
- data/plugins/project_browser/plugin.yaml +10 -0
- data/plugins/project_browser/project_browser.rb +245 -0
- data/plugins/rake/plugin.yaml +39 -0
- data/plugins/rake/rake.png +0 -0
- data/plugins/rake/rake.rb +567 -0
- data/plugins/rake/rake_extension.rb +153 -0
- data/plugins/rake/rake_widgets.rb +615 -0
- data/plugins/rake/rakeui.rc +27 -0
- data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
- data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
- data/plugins/rake/ui/choose_task_widget.rb +77 -0
- data/plugins/rake/ui/choose_task_widget.ui +72 -0
- data/plugins/rake/ui/config_widget.rb +127 -0
- data/plugins/rake/ui/config_widget.ui +123 -0
- data/plugins/rake/ui/project_widget.rb +217 -0
- data/plugins/rake/ui/project_widget.ui +246 -0
- data/plugins/rspec/plugin.yaml +30 -0
- data/plugins/rspec/rspec.png +0 -0
- data/plugins/rspec/rspec.rb +945 -0
- data/plugins/rspec/rspec.svg +90 -0
- data/plugins/rspec/rspecui.rc +20 -0
- data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
- data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
- data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
- data/plugins/ruby_development/plugin.yaml +27 -0
- data/plugins/ruby_development/ruby_development.png +0 -0
- data/plugins/ruby_development/ruby_development.rb +453 -0
- data/plugins/ruby_development/ruby_developmentui.rc +19 -0
- data/plugins/ruby_development/ui/project_widget.rb +112 -0
- data/plugins/ruby_development/ui/project_widget.ui +108 -0
- data/plugins/ruby_runner/config_widget.rb +116 -0
- data/plugins/ruby_runner/plugin.yaml +26 -0
- data/plugins/ruby_runner/project_widget.rb +62 -0
- data/plugins/ruby_runner/ruby.png +0 -0
- data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
- data/plugins/ruby_runner/ruby_runner.rb +411 -0
- data/plugins/ruby_runner/ui/config_widget.rb +92 -0
- data/plugins/ruby_runner/ui/config_widget.ui +91 -0
- data/plugins/ruby_runner/ui/project_widget.rb +60 -0
- data/plugins/ruby_runner/ui/project_widget.ui +48 -0
- data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
- data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
- data/plugins/state/plugin.yaml +28 -0
- data/plugins/state/state.rb +520 -0
- data/plugins/state/ui/config_widget.rb +92 -0
- data/plugins/state/ui/config_widget.ui +89 -0
- data/plugins/syntax_checker/plugin.yaml +18 -0
- data/plugins/syntax_checker/syntax_checker.rb +662 -0
- data/ruber.desktop +10 -0
- data/spec/annotation_model_spec.rb +174 -0
- data/spec/common.rb +119 -0
- data/spec/component_manager_spec.rb +1259 -0
- data/spec/document_list_spec.rb +626 -0
- data/spec/document_project_spec.rb +373 -0
- data/spec/document_spec.rb +779 -0
- data/spec/editor_view_spec.rb +167 -0
- data/spec/external_program_plugin_spec.rb +676 -0
- data/spec/filtered_output_widget_spec.rb +642 -0
- data/spec/gui_states_handler_spec.rb +304 -0
- data/spec/kde_config_option_backend_spec.rb +214 -0
- data/spec/kde_sugar_spec.rb +101 -0
- data/spec/ktexteditor_wrapper_spec.rb +305 -0
- data/spec/output_widget_spec.rb +1703 -0
- data/spec/plugin_spec.rb +1393 -0
- data/spec/plugin_specification_reader_spec.rb +1765 -0
- data/spec/plugin_specification_spec.rb +401 -0
- data/spec/project_backend_spec.rb +172 -0
- data/spec/project_files_list_spec.rb +401 -0
- data/spec/project_list_spec.rb +511 -0
- data/spec/project_spec.rb +990 -0
- data/spec/qt_sugar_spec.rb +328 -0
- data/spec/settings_container_spec.rb +617 -0
- data/spec/settings_dialog_manager_spec.rb +773 -0
- data/spec/settings_dialog_spec.rb +419 -0
- data/spec/state_spec.rb +991 -0
- data/spec/utils_spec.rb +406 -0
- data/spec/workspace_spec.rb +869 -0
- data/spec/yaml_option_backend_spec.rb +246 -0
- metadata +284 -0
@@ -0,0 +1,1093 @@
|
|
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 'pathname'
|
22
|
+
|
23
|
+
require 'ruber/gui_states_handler'
|
24
|
+
|
25
|
+
module Ruber
|
26
|
+
|
27
|
+
=begin rdoc
|
28
|
+
Widget meant to be used as tool widget to display the output of a program. It is
|
29
|
+
based on Qt Model/View classes and provides the following facitlities:
|
30
|
+
* an easy way to display in different colors items with different meaning (for
|
31
|
+
example, error message are displayed in a different color from output messages)
|
32
|
+
* a centralized way in which the user can choose the colors for different types
|
33
|
+
of items
|
34
|
+
* a context menu with some standard actions, which can be enhanced with custom
|
35
|
+
ones and is automatically (under certain conditions) shown to the user
|
36
|
+
* autoscrolling (which means that whenever new text is added, the view scrolls so
|
37
|
+
that the text becomes visible)
|
38
|
+
* a centralized way for the user to turn on and off word wrapping (which can be
|
39
|
+
ignored by widgets for which it doesn't make sense)
|
40
|
+
* a mechanism which allows the user to click on an entry containing a file name
|
41
|
+
in the widget to open the file in the editor. The mechanism can be customized
|
42
|
+
by plugins to be better tailored to their needs (and can also be turned off)
|
43
|
+
* a model class, derived from <tt>Qt::StandardItemModel</tt>, which provides a
|
44
|
+
couple of convenience methods to make text insertion even easier.
|
45
|
+
|
46
|
+
Note that OutputWidget is not (and doesn't derive from) one of the View classes.
|
47
|
+
Rather, it's a normal <tt>Qt::Widget</tt> which has the view as its only child.
|
48
|
+
You can add other widgets to the OutputWidget as you would with any other widget:
|
49
|
+
create the widget with the OutputWidget as parent and add it to the OutputWidget's
|
50
|
+
layout (which is a <tt>Qt::GridLayout</tt> where the view is at position 0,0).
|
51
|
+
|
52
|
+
<b>Note:</b> this class defines two new roles (<tt>OutputTypeRole</tt> and <tt>IsTitleRole</tt>),
|
53
|
+
which correspond to <tt>Qt::UserRole</tt> and <tt>Qt::UserRole+1</tt>. Therefore,
|
54
|
+
if you need to define other custom roles, please have them start from
|
55
|
+
<tt>Ruber::OutputWidget::IsTitleRole+1</tt>.
|
56
|
+
|
57
|
+
===Colors
|
58
|
+
The <i>output_type</i> of an entry in the model can be set using the <tt>set_output_type</tt>
|
59
|
+
method. This has two effects: first, the item will be displayed using the color
|
60
|
+
chosen by the user for that output type; second, the name of the output type will
|
61
|
+
be stored in that item under the custom role <tt>OutputTypeRole</tt>.
|
62
|
+
|
63
|
+
There are several predefined output types: +message+, <tt>message_good</tt>,
|
64
|
+
<tt>message_bad</tt>, +output+,
|
65
|
+
+output1+, +output2+, +warning+, +warning1+, +warning2+, +error+, +error1+ and +error2+.
|
66
|
+
The types ending with a number
|
67
|
+
can be used when you need different types with similar meaning. The +message+ type
|
68
|
+
(and its variations) are meant to display messages which don't come from the external
|
69
|
+
program but from Ruber itself (for example, a message telling that the external
|
70
|
+
problem exited successfully or exited with an error) Its good and bad version are
|
71
|
+
meant to display messages with good news and bad news respectively (for example:
|
72
|
+
"the program exited successfully" could be displayed using the <tt>message_good</tt>
|
73
|
+
type, while "the program crashed" could be displayed using the <tt>message_bad</tt>
|
74
|
+
type). The +output+ type is meant
|
75
|
+
to be used to show the text written by the external program on standard output,
|
76
|
+
while the +error+ type is used to display the text written on standard error. If
|
77
|
+
you can distinguish between warning and errors, you can use the +warning+ type
|
78
|
+
for the latter.
|
79
|
+
|
80
|
+
The colors for the default output types are chosen by the user from the configuration
|
81
|
+
dialog and are used whenever those output types are requested.
|
82
|
+
|
83
|
+
New output types (and their associated colors) can be make known to the output
|
84
|
+
widget by using the <tt>set_color_for</tt> method. There's no need to remove the
|
85
|
+
color, for example when the plugin is unloaded (indeed, there's no way to do so).
|
86
|
+
|
87
|
+
===The context menu
|
88
|
+
This widget automatically creates a context menu containing three actions: copy,
|
89
|
+
copy selected and clear. Copy and copy selected copy the text contained respectively
|
90
|
+
in all the items and in the selected items to the clipboard. The clear action
|
91
|
+
removes all the entries from the model.
|
92
|
+
|
93
|
+
You can add other actions to the menu by performing the following steps:
|
94
|
+
* add an entry in the appropriate position of the <tt>action_list</tt> array. Note
|
95
|
+
that it actually is an instance of ActionList, so it provides the <tt>insert_after</tt>
|
96
|
+
and <tt>insert_before</tt> methods which allow to easily put the actions in the
|
97
|
+
correct place. <tt>action_list</tt> should contain the <tt>object_name</tt> of
|
98
|
+
the actions (and *nil* for the separators), not the action themselves
|
99
|
+
* create the actions (setting their <tt>object_name</tt> to the values inserted
|
100
|
+
in <tt>action_list</tt>) and put them into the +actions+ hash, using the <tt>object_name</tt>
|
101
|
+
as keys. Of course, you also need to define the appropriate slots and connect
|
102
|
+
them to the actions' signals.
|
103
|
+
|
104
|
+
Note that actions can only be added _before_ the menu is first shown (usually, you
|
105
|
+
do that in the widget's constructor). The signal <tt>about_to_fill_menu</tt> is
|
106
|
+
emitted just before the menu is built: this is the last time you can add entries
|
107
|
+
to it.
|
108
|
+
|
109
|
+
OutputWidget mixes in the GuiStatesHandler module, which means you can define states
|
110
|
+
to enable and disable actions as usual. By default, two states are defined: <tt>no_text</tt>
|
111
|
+
and <tt>no_selection</tt>. As the names imply, the former is *true* when the model
|
112
|
+
is empty and *false* when there's at least one item; the second is *true* when no
|
113
|
+
item is selected and *false* when there are selected items.
|
114
|
+
|
115
|
+
For the menu to be displayed automatically, the view should have a <tt>context_menu_requested(QPoint)</tt>
|
116
|
+
signal. The menu will be displayed in response to that signal, at the point given
|
117
|
+
as argument. For convenience, there are three classes <tt>OutputWidget::ListView</tt>,
|
118
|
+
<tt>OutputWidget::TreeView</tt> and <tt>OutputWidget::TableView</tt>, derived
|
119
|
+
respectively from <tt>Qt::ListView</tt>, <tt>Qt::TreeView</tt> and <tt>Qt::TableView</tt>
|
120
|
+
which emit that signal from their <tt>contextMenuEvent</tt> method. If you use
|
121
|
+
one of these classes as view, the menu will work automatically.
|
122
|
+
|
123
|
+
===Autoscrolling
|
124
|
+
Whenever an item is added to the list, the view will be scrolled so that the added
|
125
|
+
item is visible. Plugins which don't want this feature can disable it using the
|
126
|
+
<tt>auto_scroll</tt> accessor. Note that auto scrolling won't happen if an item
|
127
|
+
is modified or removed
|
128
|
+
|
129
|
+
===Word Wrapping
|
130
|
+
If the user has enabled word wrapping for output widgets in the config dialog (
|
131
|
+
the general/wrap_output option), word wrapping will be automatically enabled for
|
132
|
+
all output widgets. If the user has disabled it, it will be disabled for all
|
133
|
+
widgets.
|
134
|
+
|
135
|
+
Subclasses can avoid the automatic behaviour by setting the <tt>ignore_word_wrap_option</tt>
|
136
|
+
attribute to *true* and managing word wrap by themselves. This is mostly useful
|
137
|
+
for output widgets for which word wrap is undesirable or meaningless.
|
138
|
+
|
139
|
+
===Opening files in the editor
|
140
|
+
Whenever the user activates an item, the text of the item is searched for a filename
|
141
|
+
(and optionally for a line number). If it's found, a new editor view is opened
|
142
|
+
and the file is displayed in it. This process uses three methods:
|
143
|
+
<tt>maybe_open_file</tt>::
|
144
|
+
the method connected to the view's <tt>activated(QModelIndex)</tt> signal. It
|
145
|
+
starts the search for the filename and, if successful, opens the editor view
|
146
|
+
<tt>find_filename_in_index</tt>::
|
147
|
+
performs the search of the filename. By default, it uses <tt>find_filename_in_string</tt>,
|
148
|
+
but subclasses can override it to change the behaviour
|
149
|
+
<tt>find_filename_in_string</tt>::
|
150
|
+
the method used by default by <tt>find_filename_in_index</tt> to find the
|
151
|
+
filename.
|
152
|
+
|
153
|
+
If a relative filename is found, it's considered relative to the directory contained
|
154
|
+
in the <tt>working_dir</tt> attribute.
|
155
|
+
|
156
|
+
===The <tt>OutputWidget::Model</tt> class
|
157
|
+
It behaves as a standard <tt>Qt::StandardItemModel</tt>, but it provides an +insert+
|
158
|
+
method and an <tt>insert_lines</tt> method which make easier adding items. You
|
159
|
+
don't need to use this model. If you don't, simply pass another one to the OutputWidget
|
160
|
+
constructor
|
161
|
+
|
162
|
+
===Signals
|
163
|
+
=====<tt>about_to_fill_menu()</tt>
|
164
|
+
Signal emitted immediately before the menu is created. You should connect to this
|
165
|
+
signal if you want to add actions to the menu at the last possible time. Usually,
|
166
|
+
however, you don't need it, as actions are usually created in the constructor.
|
167
|
+
===Slots
|
168
|
+
* <tt>show_menu(QPoint)</tt>
|
169
|
+
* <tt>selection_changed(QItemSelection, QItemSelection)</tt>
|
170
|
+
* <tt>rows_changed()</tt>
|
171
|
+
* <tt>do_auto_scroll(QModelIndex, int, int)</tt>
|
172
|
+
* <tt>copy()</tt>
|
173
|
+
* <tt>copy_selected()</tt>
|
174
|
+
* <tt>clear_output()</tt>
|
175
|
+
* <tt>maybe_open_file()</tt>
|
176
|
+
=end
|
177
|
+
class OutputWidget < Qt::Widget
|
178
|
+
|
179
|
+
include GuiStatesHandler
|
180
|
+
|
181
|
+
signals :about_to_fill_menu
|
182
|
+
|
183
|
+
slots 'show_menu(QPoint)', 'selection_changed(QItemSelection, QItemSelection)',
|
184
|
+
:rows_changed, 'do_auto_scroll(QModelIndex, int, int)', :copy, :copy_selected,
|
185
|
+
:clear_output, 'maybe_open_file(QModelIndex)', :load_settings
|
186
|
+
|
187
|
+
=begin rdoc
|
188
|
+
The role which contains a string with the output type of the index
|
189
|
+
=end
|
190
|
+
OutputTypeRole = Qt::UserRole
|
191
|
+
|
192
|
+
=begin rdoc
|
193
|
+
The role which contains whether an item is or not the title
|
194
|
+
=end
|
195
|
+
IsTitleRole = OutputTypeRole + 1
|
196
|
+
|
197
|
+
=begin rdoc
|
198
|
+
Whether auto scrolling should be enabled or not (default: *true*)
|
199
|
+
=end
|
200
|
+
attr_accessor :auto_scroll
|
201
|
+
|
202
|
+
=begin rdoc
|
203
|
+
Whether word wrapping should be enabled and disabled automatically according to
|
204
|
+
the general/wrap_output setting or not (default: *false*)
|
205
|
+
=end
|
206
|
+
attr_accessor :ignore_word_wrap_option
|
207
|
+
|
208
|
+
=begin rdoc
|
209
|
+
The directory used to resolve relative paths when opening a file (default *nil*)
|
210
|
+
=end
|
211
|
+
attr_accessor :working_dir
|
212
|
+
alias :working_directory :working_dir
|
213
|
+
alias :working_directory= :working_dir=
|
214
|
+
|
215
|
+
=begin rdoc
|
216
|
+
Whether or not to skip the first file name in the title if the user activates it
|
217
|
+
(see <tt>find_filename_in_index</tt>)
|
218
|
+
=end
|
219
|
+
attr_accessor :skip_first_file_in_title
|
220
|
+
|
221
|
+
=begin rdoc
|
222
|
+
An ActionList containing the names of the actions and the separators (represented
|
223
|
+
by *nil*) to use to build the menu. The default is ['copy', 'copy_selected', nil, 'clear'].
|
224
|
+
|
225
|
+
<b>Note:</b> this is private
|
226
|
+
=end
|
227
|
+
attr_reader :action_list
|
228
|
+
|
229
|
+
=begin rdoc
|
230
|
+
A hash having the names of actions to be inserted in the menu as keys and the actions
|
231
|
+
themselves as values. By default, it contains the 'copy', 'copy_selected' and
|
232
|
+
'clear' actions.
|
233
|
+
|
234
|
+
<b>Note:</b> this is private
|
235
|
+
=end
|
236
|
+
attr_reader :actions
|
237
|
+
|
238
|
+
=begin rdoc
|
239
|
+
The model used by the OutputWidget
|
240
|
+
=end
|
241
|
+
attr_reader :model
|
242
|
+
|
243
|
+
=begin rdoc
|
244
|
+
The view used by the OutputWidget
|
245
|
+
=end
|
246
|
+
attr_reader :view
|
247
|
+
|
248
|
+
|
249
|
+
private :action_list, :actions
|
250
|
+
|
251
|
+
=begin rdoc
|
252
|
+
Creates a new OutputWidget. _parent_ is the parent widget. _opts_ can contain
|
253
|
+
the following keys:
|
254
|
+
+:view+:: the view to use. It can be either a widget derived from <tt>Qt::AbstractItemView</tt>,
|
255
|
+
which will be used as view, or one of the symbols +:list+, +:tree+ or
|
256
|
+
+:table+. If it's a symbol, then the view will be a new instance of
|
257
|
+
OutputWidget::ListView, OutputWidget::TreeView or OutputWidget::TableView
|
258
|
+
respectively. Defaults to +:list+.
|
259
|
+
+:model+:: the model to use. If this option isn't given, then a new instance of
|
260
|
+
OutputWidget::Model will be used
|
261
|
+
<tt>use_default_font</tt>::
|
262
|
+
whether or not to use the application\'s default font in the view. If *false*
|
263
|
+
(the default), then the font chosen by the user for the general/output_font
|
264
|
+
option will be used.
|
265
|
+
|
266
|
+
<b>Note:</b> if a widget is specified as value of the +:view+ option, it will become
|
267
|
+
a child of the new OutputWidget.
|
268
|
+
=end
|
269
|
+
def initialize parent = nil, opts = {}
|
270
|
+
|
271
|
+
@ignore_word_wrap_option = false
|
272
|
+
@working_dir = nil
|
273
|
+
@skip_first_file_in_title = true
|
274
|
+
@use_default_font = opts[:use_default_font]
|
275
|
+
|
276
|
+
super parent
|
277
|
+
initialize_states_handler
|
278
|
+
create_widgets(opts[:view] || :list)
|
279
|
+
setup_model opts[:model]
|
280
|
+
connect @view.selection_model, SIGNAL('selectionChanged(QItemSelection, QItemSelection)'), self, SLOT('selection_changed(QItemSelection, QItemSelection)')
|
281
|
+
connect @view, SIGNAL('activated(QModelIndex)'), self, SLOT('maybe_open_file(QModelIndex)')
|
282
|
+
@auto_scroll = true
|
283
|
+
|
284
|
+
@colors = {}
|
285
|
+
@action_list = ActionList.new
|
286
|
+
@action_list << 'copy' << 'copy_selected' << nil << 'clear'
|
287
|
+
@actions = {}
|
288
|
+
|
289
|
+
connect @view, SIGNAL('context_menu_requested(QPoint)'), self, SLOT('show_menu(QPoint)')
|
290
|
+
|
291
|
+
@menu = Qt::Menu.new self
|
292
|
+
|
293
|
+
create_standard_actions
|
294
|
+
change_state 'no_text', true
|
295
|
+
change_state 'no_selection', true
|
296
|
+
end
|
297
|
+
|
298
|
+
=begin rdoc
|
299
|
+
Instructs the OutputWidget to use the <tt>Qt::Color</tt> _color_ to display items
|
300
|
+
whose output type is _name_. _name_ should be a symbol.
|
301
|
+
|
302
|
+
If a color had already been set for _name_, it will be overwritten
|
303
|
+
=end
|
304
|
+
def set_color_for name, color
|
305
|
+
@colors[name] = color
|
306
|
+
end
|
307
|
+
|
308
|
+
=begin rdoc
|
309
|
+
Scrolls the view so that the item corresponding to the index _idx_ is visible.
|
310
|
+
|
311
|
+
_idx_ can be:
|
312
|
+
* a <tt>Qt::ModelIndex</tt>
|
313
|
+
* a positive integer. In this case, the view will be scrolled so that the first
|
314
|
+
item toplevel item in the row _idx_ is visible.
|
315
|
+
* a negative integer. It works as for a positive integer except that the rows are
|
316
|
+
counted from the end (the same as passing a negative integer to <tt>Array#[]</tt>)
|
317
|
+
* *nil*. In this case, the view will be scrolled so that the first toplevel item
|
318
|
+
of the last row is visible.
|
319
|
+
=end
|
320
|
+
def scroll_to idx
|
321
|
+
case idx
|
322
|
+
when Numeric
|
323
|
+
rc = @model.row_count
|
324
|
+
if idx >= rc then idx = rc -1
|
325
|
+
elsif idx < 0 and idx.abs < rc then idx = rc + idx
|
326
|
+
elsif idx < 0 then idx = 0
|
327
|
+
end
|
328
|
+
mod_idx = @model.index idx, 0
|
329
|
+
@view.scroll_to mod_idx, Qt::AbstractItemView::PositionAtBottom
|
330
|
+
when Qt::ModelIndex
|
331
|
+
idx = @model.index(@model.row_count - 1, 0) unless idx.valid?
|
332
|
+
@view.scroll_to idx, Qt::AbstractItemView::PositionAtBottom
|
333
|
+
when nil
|
334
|
+
@view.scroll_to @model.index(@model.row_count - 1, 0),
|
335
|
+
Qt::AbstractItemView::PositionAtBottom
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
=begin rdoc
|
340
|
+
Sets the output type associated with the <tt>Qt::ModelIndex</tt> _idx_ to _type_
|
341
|
+
(a symbol).
|
342
|
+
|
343
|
+
If a color has been associated with _type_ (either because _type_ is one of the
|
344
|
+
standard types or because it's been set with <tt>set_color_for</tt>), the foreground
|
345
|
+
role of the index will be changed to that color and the +OutputTypeRole+ of the
|
346
|
+
index will be set to a string version of _type_. In this case, _type_ is returned.
|
347
|
+
|
348
|
+
If no color has been associated with _type_, this method does nothing and returns
|
349
|
+
*nil*.
|
350
|
+
=end
|
351
|
+
def set_output_type idx, type
|
352
|
+
color = @colors[type]
|
353
|
+
if color
|
354
|
+
@model.set_data idx, Qt::Variant.from_value(color), Qt::ForegroundRole
|
355
|
+
@model.set_data idx, Qt::Variant.new(type.to_s), OutputTypeRole
|
356
|
+
type
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
=begin rdoc
|
361
|
+
Executes the block with autoscrolling turned on or off according to _val_, without
|
362
|
+
permanently changing the autoscrolling setting.
|
363
|
+
=end
|
364
|
+
def with_auto_scrolling val
|
365
|
+
old = @auto_scroll
|
366
|
+
@auto_scroll = val
|
367
|
+
begin yield
|
368
|
+
ensure @auto_scroll = old
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
=begin rdoc
|
373
|
+
Gives a title to the widget.
|
374
|
+
|
375
|
+
A title is a toplevel entry at position 0,0 with output type +:message+ and has
|
376
|
+
the +IsTitleRole+ set to *true*. Of course, there can be only one item which is
|
377
|
+
a title.
|
378
|
+
|
379
|
+
If the item in position 0,0 is not a title, a new row is inserted at position 0,
|
380
|
+
its first element's DisplayRole is set to _text_ and its IsTitleRole is set to
|
381
|
+
true.
|
382
|
+
|
383
|
+
If the item in position 0,0 is a title, then its DisplayRole value is replaced
|
384
|
+
with _text_.
|
385
|
+
|
386
|
+
Usually, the title is created when the external program is started and changed
|
387
|
+
later if needed
|
388
|
+
=end
|
389
|
+
def title= text
|
390
|
+
idx = @model.index 0, 0
|
391
|
+
if idx.data(IsTitleRole).to_bool
|
392
|
+
@model.set_data idx, Qt::Variant.new(text)
|
393
|
+
else
|
394
|
+
@model.insert_column 0 if @model.column_count == 0
|
395
|
+
@model.insert_row 0
|
396
|
+
idx = @model.index 0, 0
|
397
|
+
@model.set_data idx, Qt::Variant.new(text)
|
398
|
+
@model.set_data idx, Qt::Variant.new(true), IsTitleRole
|
399
|
+
end
|
400
|
+
set_output_type idx, :message
|
401
|
+
end
|
402
|
+
|
403
|
+
=begin rdoc
|
404
|
+
Tells whether the toplevel 0,0 element is the title or not. See <tt>title=</tt>
|
405
|
+
for the meaning of the title
|
406
|
+
=end
|
407
|
+
def has_title?
|
408
|
+
@model.index(0,0).data(IsTitleRole).to_bool
|
409
|
+
end
|
410
|
+
|
411
|
+
=begin rdoc
|
412
|
+
Loads the settings from the configuration file.
|
413
|
+
=end
|
414
|
+
def load_settings
|
415
|
+
cfg = Ruber[:config]
|
416
|
+
colors = [:message, :message_good, :message_bad, :output, :output1, :output2, :error, :error1, :error2, :warning, :warning1, :warning2]
|
417
|
+
colors.each{|c| set_color_for c, cfg[:output_colors, c]}
|
418
|
+
@model.row_count.times do |r|
|
419
|
+
@model.column_count.times do |c|
|
420
|
+
update_index_color @model.index(r, c)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
@view.font = cfg[:general, :output_font] unless @use_default_font
|
424
|
+
unless @ignore_word_wrap_option
|
425
|
+
# Not all the views support word wrapping
|
426
|
+
begin @view.word_wrap = cfg[:general, :wrap_output]
|
427
|
+
rescue NoMethodError
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
=begin rdoc
|
433
|
+
Removes all the entries from the model
|
434
|
+
=end
|
435
|
+
def clear_output
|
436
|
+
@model.remove_rows 0, @model.row_count
|
437
|
+
end
|
438
|
+
|
439
|
+
protected
|
440
|
+
|
441
|
+
# def keyReleaseEvent e
|
442
|
+
# ed = Ruber[:main_window].active_editor
|
443
|
+
# return super unless ed
|
444
|
+
# Ruber[:main_window].activate_editor ed
|
445
|
+
# ed.set_focus
|
446
|
+
# # mod = e.modifiers
|
447
|
+
# # if mod == Qt::NoModifier or mod == Qt::ShiftModifier
|
448
|
+
# # ed.insert_text e.text
|
449
|
+
# # end
|
450
|
+
# nil
|
451
|
+
# end
|
452
|
+
|
453
|
+
private
|
454
|
+
|
455
|
+
=begin rdoc
|
456
|
+
Changes the foreground color of the Qt::ModelIndex _idx_ and of its children so
|
457
|
+
that it matches the color set for its output type.
|
458
|
+
=end
|
459
|
+
def update_index_color idx
|
460
|
+
type = idx.data(OutputTypeRole).to_string.to_sym rescue nil
|
461
|
+
color = @colors[type]
|
462
|
+
if color
|
463
|
+
@model.set_data idx, Qt::Variant.from_value(color), Qt::ForegroundRole
|
464
|
+
end
|
465
|
+
if @model.has_children idx
|
466
|
+
@model.row_count(idx).times do |r|
|
467
|
+
@model.column_count(idx).times do |c|
|
468
|
+
update_index_color idx.child(r, c)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
=begin rdoc
|
475
|
+
Creates the model (if needed) and makes some signal-slot connections
|
476
|
+
=end
|
477
|
+
def setup_model mod
|
478
|
+
@model = mod || Model.new(self)
|
479
|
+
@model.insert_column 0 if @model.column_count < 1
|
480
|
+
@model.parent = @view
|
481
|
+
@view.model = @model
|
482
|
+
connect @model, SIGNAL('rowsInserted(QModelIndex, int, int)'), self, SLOT(:rows_changed)
|
483
|
+
connect @model, SIGNAL('rowsRemoved(QModelIndex, int, int)'), self, SLOT(:rows_changed)
|
484
|
+
connect @model, SIGNAL('rowsInserted(QModelIndex, int, int)'), self, SLOT('do_auto_scroll(QModelIndex, int, int)')
|
485
|
+
end
|
486
|
+
|
487
|
+
=begin rdoc
|
488
|
+
Automatically scrolls to the first row of <i>end_idx</i> if auto scrolling is enabled.
|
489
|
+
|
490
|
+
Note: all parameters are considered relative to the model associated with the view,
|
491
|
+
not with the @model@ attribute (of course, this only matters in subclasses where
|
492
|
+
the two differ, such as {FilteredOutputWidget}).
|
493
|
+
=end
|
494
|
+
def do_auto_scroll parent, start_idx, end_idx
|
495
|
+
scroll_to @view.model.index(end_idx, 0, parent) if @auto_scroll
|
496
|
+
end
|
497
|
+
|
498
|
+
=begin rdoc
|
499
|
+
Creates the menu, according to the contents of the <tt>@action_list</tt> and
|
500
|
+
<tt>@actions</tt> instance variables.
|
501
|
+
|
502
|
+
Before creating the menu, it emits the <tt>about_to_fill_menu()</tt> signal. Connecting
|
503
|
+
to this signal allows to do some last-minute changes to the actions which will
|
504
|
+
be inserted in the menu.
|
505
|
+
=end
|
506
|
+
def fill_menu
|
507
|
+
emit about_to_fill_menu
|
508
|
+
@action_list.each do |a|
|
509
|
+
if a then @menu.add_action @actions[a]
|
510
|
+
else @menu.add_separator
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
=begin rdoc
|
516
|
+
Shows the menu (asynchronously) at the point _pt_.
|
517
|
+
|
518
|
+
If the menu hasn't as yet been created, it creates it.
|
519
|
+
=end
|
520
|
+
def show_menu pt
|
521
|
+
fill_menu if @menu.empty?
|
522
|
+
@menu.popup pt
|
523
|
+
end
|
524
|
+
|
525
|
+
=begin rdoc
|
526
|
+
Creates the layout and the view. _view_ has the same meaning as the <tt>:view</tt>
|
527
|
+
option in the constructor
|
528
|
+
=end
|
529
|
+
def create_widgets view
|
530
|
+
self.layout = Qt::GridLayout.new(self)
|
531
|
+
if view.is_a?(Qt::Widget)
|
532
|
+
@view = view
|
533
|
+
@view.parent = self
|
534
|
+
else @view = self.class.const_get(view.to_s.capitalize + 'View').new self
|
535
|
+
end
|
536
|
+
@view.selection_mode = Qt::AbstractItemView::ExtendedSelection
|
537
|
+
layout.add_widget @view, 0, 0
|
538
|
+
end
|
539
|
+
|
540
|
+
=begin rdoc
|
541
|
+
Creates the 'Copy', 'Copy selected' and 'Clear' actions and the correspongind
|
542
|
+
state handlers
|
543
|
+
=end
|
544
|
+
def create_standard_actions
|
545
|
+
@actions['copy'] = KDE::Action.new(self){|a| a.text = '&Copy'}
|
546
|
+
@actions['copy_selected'] = KDE::Action.new(self){|a| a.text = '&Copy Selection'}
|
547
|
+
@actions['clear'] = KDE::Action.new(self){|a| a.text = 'C&lear'}
|
548
|
+
register_action_handler @actions['copy'], '!no_text'
|
549
|
+
register_action_handler @actions['copy_selected'], ['no_text', 'no_selection'] do |s|
|
550
|
+
!(s['no_text'] || s['no_selection'])
|
551
|
+
end
|
552
|
+
register_action_handler @actions['clear'], '!no_text'
|
553
|
+
connect @actions['copy'], SIGNAL(:triggered), self, SLOT(:copy)
|
554
|
+
connect @actions['copy_selected'], SIGNAL(:triggered), self, SLOT(:copy_selected)
|
555
|
+
connect @actions['clear'], SIGNAL(:triggered), self, SLOT(:clear_output)
|
556
|
+
end
|
557
|
+
|
558
|
+
=begin rdoc
|
559
|
+
Slot connected to the 'Copy' action.
|
560
|
+
|
561
|
+
It copies the content of all the items to the clipboard. The text is obtained
|
562
|
+
from the items by calling <tt>text_for_clipboard</tt> passing it all the items.
|
563
|
+
=end
|
564
|
+
def copy
|
565
|
+
items = []
|
566
|
+
stack = []
|
567
|
+
@model.row_count.times do |r|
|
568
|
+
@model.column_count.times{|c| stack << @model.index(r, c)}
|
569
|
+
end
|
570
|
+
until stack.empty?
|
571
|
+
it = stack.shift
|
572
|
+
items << it
|
573
|
+
(@model.row_count(it)-1).downto(0) do |r|
|
574
|
+
(@model.column_count(it)-1).downto(0){|c| stack.unshift it.child(r, c)}
|
575
|
+
end
|
576
|
+
end
|
577
|
+
clp = KDE::Application.clipboard
|
578
|
+
clp.text = text_for_clipboard items
|
579
|
+
end
|
580
|
+
|
581
|
+
=begin rdoc
|
582
|
+
Slot connected to the 'Copy Selection' action.
|
583
|
+
|
584
|
+
It copies the content of all the items to the clipboard. The text is obtained
|
585
|
+
from the items by calling <tt>text_for_clipboard</tt> passing it the selected items.
|
586
|
+
=end
|
587
|
+
def copy_selected
|
588
|
+
clp = KDE::Application.clipboard
|
589
|
+
clp.text = text_for_clipboard @view.selection_model.selected_indexes
|
590
|
+
end
|
591
|
+
|
592
|
+
=begin rdoc
|
593
|
+
Method used by the +copy+ and <tt>copy_selected</tt> methods to obtain the text
|
594
|
+
to put in the clipboard from the indexes.
|
595
|
+
|
596
|
+
The default behaviour is to create a string which contains the content of all the
|
597
|
+
toplevel items on the same row separated by tabs and separate different rows by
|
598
|
+
newlines. Child items are ignored.
|
599
|
+
|
600
|
+
Derived class can override this method (and, if they plan to put child items in
|
601
|
+
the view, they're advised to do so). The method must accept an array of <tt>Qt::ModelIndex</tt>
|
602
|
+
as argument and return a string with the text to put in the clipboard.
|
603
|
+
|
604
|
+
The reason the default behaviour ignores child items is that their meaning (and
|
605
|
+
therefore the way their contents should be inserted into the string) depends
|
606
|
+
very much on the specific content.
|
607
|
+
=end
|
608
|
+
def text_for_clipboard indexes
|
609
|
+
indexes = indexes.select{|i| !i.parent.valid?}
|
610
|
+
rows = indexes.group_by{|idx| idx.row}
|
611
|
+
rows = rows.sort
|
612
|
+
text = rows.inject("") do |res, r|
|
613
|
+
idxs = r[1].sort_by{|i| i.column}
|
614
|
+
idxs.each{|i| res << i.data.to_string << "\t"}
|
615
|
+
# The above line gives \t as last character, while a \n is needed
|
616
|
+
res[-1] = "\n"
|
617
|
+
res
|
618
|
+
end
|
619
|
+
# The above block leaves a \n at the end of the string which shouldn't be
|
620
|
+
# there
|
621
|
+
text[0..-2]
|
622
|
+
end
|
623
|
+
|
624
|
+
=begin rdoc
|
625
|
+
Slot connected to the view's selection model's selectionChanged signal.
|
626
|
+
|
627
|
+
Turns the <tt>no_selection</tt> state on or off depending on whether the selection
|
628
|
+
is empty or not
|
629
|
+
=end
|
630
|
+
def selection_changed sel, desel
|
631
|
+
change_state 'no_selection', !@view.selection_model.has_selection
|
632
|
+
end
|
633
|
+
|
634
|
+
=begin rdoc
|
635
|
+
Turns the <tt>no_text</tt> state on or off depending on whether the model
|
636
|
+
is empty or not
|
637
|
+
=end
|
638
|
+
def rows_changed
|
639
|
+
change_state 'no_text', @model.row_count == 0
|
640
|
+
end
|
641
|
+
|
642
|
+
=begin rdoc
|
643
|
+
Searches for a filename in the DisplayRole of the <tt>Qt::ModelIndex</tt> idx (
|
644
|
+
using the <tt>find_filename_in_index</tt> method). If a filename is found, opens
|
645
|
+
a new editor view containing the file, scrolls it to the appropriate line and
|
646
|
+
hides the tool widget (*self*).
|
647
|
+
|
648
|
+
The behaviour of this method (which usually is only called via a signal-slot connection
|
649
|
+
to the views' <tt>activated(QModelindex) signal) changes according to the active
|
650
|
+
keyboard modifiers:
|
651
|
+
* if Ctrl or Shift are pressed and the view allows selection (that is, its selection
|
652
|
+
mode is not +NoSelection+), then this method does nothing. The reason for this
|
653
|
+
behaviour is that Ctrl and Shift are used to select items, so the user is most
|
654
|
+
likely doing that, not requesting to open a file
|
655
|
+
* if Meta is pressed, then the tool widget won't be closed
|
656
|
+
=end
|
657
|
+
def maybe_open_file idx
|
658
|
+
modifiers = Application.keyboard_modifiers
|
659
|
+
if @view.selection_mode != Qt::AbstractItemView::NoSelection
|
660
|
+
return if Qt::ControlModifier & modifiers != 0 or Qt::ShiftModifier & modifiers != 0
|
661
|
+
end
|
662
|
+
file = find_filename_in_index idx
|
663
|
+
return unless file
|
664
|
+
line = file[1]
|
665
|
+
line -= 1 if line > 0
|
666
|
+
Ruber[:main_window].display_document file[0], line
|
667
|
+
Ruber[:main_window].hide_tool self if (Qt::MetaModifier & modifiers) == 0
|
668
|
+
end
|
669
|
+
|
670
|
+
=begin rdoc
|
671
|
+
Method used by <tt>maybe_open_file</tt> to find out the name of the file to open
|
672
|
+
(if any) when an item is activated.
|
673
|
+
|
674
|
+
_idx_ can be either the <tt>Qt::ModelIndex</tt> corresponding to the activated
|
675
|
+
item or a string. The first form is used when this method is called from the
|
676
|
+
<tt>activated(QModelIndex)</tt> signal of the view; the string form is usually
|
677
|
+
called by overriding methods using *super*.
|
678
|
+
|
679
|
+
The actual work is done by <tt>find_filename_in_string</tt>, which returns the
|
680
|
+
first occurrence of what it considers a filename (possibly followed by a line
|
681
|
+
number).
|
682
|
+
|
683
|
+
If <tt>find_filename_in_string</tt> finds a filename, this method makes sure it
|
684
|
+
actually corresponds to an existing file and, if it's a relative path, expands
|
685
|
+
it, considering it relative to the <tt>working_dir</tt> attribute. If that
|
686
|
+
attribute is not set, the behaviour is undefined (most likely, an exception will
|
687
|
+
be raised).
|
688
|
+
|
689
|
+
If _idx_ is the title (see <tt>title=</tt>) and <tt>skip_first_file_in_title</tt>
|
690
|
+
is *true*, all the text from the beginning to the first space or colon is removed
|
691
|
+
from it before being passed to <tt>find_filename_in_string</tt>. The reason is
|
692
|
+
that often the title contains the command line of a program, for example:
|
693
|
+
|
694
|
+
/usr/bin/ruby /path/to/script.rb
|
695
|
+
|
696
|
+
In this case, when the user activates the title, he will most likely want to
|
697
|
+
open <tt>/path/to/script.rb</tt> rather than <tt>/usr/bin/ruby</tt> (which, being
|
698
|
+
an executable, couldn't even be correctly displayed). Nothing like this will
|
699
|
+
ever happen if _idx_ is a string.
|
700
|
+
|
701
|
+
Subclasses can override this method to extend or change its functionality. They
|
702
|
+
have two choices on how to do this. The simplest is useful if they want to alter
|
703
|
+
the string. In this case they can retrieve the text from the index, change it
|
704
|
+
then call *super* passing the modified string as argument. Otherwise, they should
|
705
|
+
reimplement all the functionality. In this case, the method should:
|
706
|
+
* take a <tt>Qt::ModelIndex</tt> or a string as argument
|
707
|
+
* return a string with the name of the file or an array containing a string and
|
708
|
+
the associated line number (if found) if a file name is found
|
709
|
+
* return *nil* if no file name is found
|
710
|
+
* convert relative file names to absolute (either using the <tt>working_dir</tt>
|
711
|
+
attribute or any other way they see fit)
|
712
|
+
|
713
|
+
A subclass can decide to completely disable this functionality by overriding this
|
714
|
+
method with one which always returns *nil*.
|
715
|
+
=end
|
716
|
+
def find_filename_in_index idx
|
717
|
+
str = if idx.is_a?(String) then idx
|
718
|
+
elsif @skip_first_file_in_title and idx.data(IsTitleRole).to_bool
|
719
|
+
idx.data.to_string.sub(/^[^\s:]+/, '')
|
720
|
+
else idx.data.to_string
|
721
|
+
end
|
722
|
+
res = find_filename_in_string str
|
723
|
+
return unless res
|
724
|
+
res = Array res
|
725
|
+
res << 0 if res.size == 1
|
726
|
+
unless Pathname.new(res[0]).absolute?
|
727
|
+
res[0] = File.join @working_dir, res[0]
|
728
|
+
end
|
729
|
+
return nil unless File.exist?(res[0]) and !File.directory?(res[0])
|
730
|
+
res
|
731
|
+
end
|
732
|
+
|
733
|
+
=begin rdoc
|
734
|
+
Searches the given string for the first occurrence of a file name (possibly followed by a colon and a line
|
735
|
+
number). If a file name is found, returns an array containing the file name and
|
736
|
+
the corresponding line number (if present). Returns *nil* if no file name was found.
|
737
|
+
|
738
|
+
What is a file name and what isn't is a bit arbitrary. Here's what this method
|
739
|
+
recognizes as a filename:
|
740
|
+
* an absolute path not containing spaces and colons starting with '/'
|
741
|
+
* 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)
|
744
|
+
* any string not containing spaces or colons followed by a colon and a line number
|
745
|
+
|
746
|
+
The first three entries of the previous list can be followed by a colon and a line
|
747
|
+
number; for the last one they're mandatory
|
748
|
+
=end
|
749
|
+
def find_filename_in_string str\
|
750
|
+
#This ensures that file names inside quotes or brackets are found. It's
|
751
|
+
#easier replacing quotes and brackets with spaces than to modify the main
|
752
|
+
#regexp to take them into account
|
753
|
+
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
|
780
|
+
return unless match
|
781
|
+
file = match[1] || match[3]
|
782
|
+
ln = match[1] ? match[2] : match[4]
|
783
|
+
file = File.expand_path(file) if file.start_with? '~'
|
784
|
+
res = [file]
|
785
|
+
res << ln.to_i if ln
|
786
|
+
res
|
787
|
+
end
|
788
|
+
|
789
|
+
=begin rdoc
|
790
|
+
Convenience class to use instead of <tt>Qt::StandardItem</tt> as model for OutputWidget.
|
791
|
+
|
792
|
+
It provides three methods which make easier to insert items in the widget: +insert+,
|
793
|
+
<tt>insert_lines</tt> and +set+. Besides, it allows to set the item flags globally,
|
794
|
+
using the <tt>global_flags</tt> attribute.
|
795
|
+
=end
|
796
|
+
class Model < Qt::StandardItemModel
|
797
|
+
|
798
|
+
=begin rdoc
|
799
|
+
The flags to use for all valid indexes (always converted to an integer). If this
|
800
|
+
is *nil*, then +flags+ will revert to <tt>Qt::StandardModel</tt> behaviour. The
|
801
|
+
default value is <tt>Qt::ItemIsEnabled|Qt::ItemIsSelectable</tt>
|
802
|
+
=end
|
803
|
+
attr_reader :global_flags
|
804
|
+
|
805
|
+
=begin rdoc
|
806
|
+
Creates a new instance. _widget_ is the output widget which will use the model.
|
807
|
+
_parent_ is the parent object
|
808
|
+
=end
|
809
|
+
def initialize widget, parent = nil
|
810
|
+
super parent
|
811
|
+
@output_widget = widget
|
812
|
+
@global_flags = (Qt::ItemIsEnabled | Qt::ItemIsSelectable).to_i
|
813
|
+
end
|
814
|
+
|
815
|
+
=begin rdoc
|
816
|
+
Sets the global flags to use. If _val_ is not *nil*, it will be converted to an
|
817
|
+
integer and will become the value the +flags+ method return for all valid indexes.
|
818
|
+
If _value_ is *nil*, +flags+ will behave as it does in <tt>Qt::StandardModel</tt>
|
819
|
+
=end
|
820
|
+
def global_flags= val
|
821
|
+
@global_flags = val.nil? ? nil : val.to_i
|
822
|
+
end
|
823
|
+
|
824
|
+
=begin rdoc
|
825
|
+
Override of <tt>Qt::StandardModel#flags</tt>.
|
826
|
+
|
827
|
+
If the <tt>global_flags</tt> attribute is not *nil*, returns its value if _idx_
|
828
|
+
is valid and <tt>Qt::NoItemFlags</tt> if it isn't vaid.
|
829
|
+
|
830
|
+
If <tt>global_flags</tt> is *nil*, this method behaves as <tt>Qt::StandardModel#flags</tt>.
|
831
|
+
=end
|
832
|
+
def flags idx
|
833
|
+
if @global_flags
|
834
|
+
idx.valid? ? @global_flags : Qt::NoItemFlags
|
835
|
+
else super
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
=begin rdoc
|
840
|
+
Changes content of the given element.
|
841
|
+
|
842
|
+
It creates a new Qt::StandardItem containing the text _text_, inserts it in the model,
|
843
|
+
sets the output type of the corresponding index to _type_ and changes its flags
|
844
|
+
to make it enabled and selectabled.
|
845
|
+
|
846
|
+
_row_ is an integer corresponding to the row where the item should be put. If _opts_
|
847
|
+
contains the +:col+ entry, it represents the colun of the new item (if this option
|
848
|
+
is missing, the column is 0). If _opts_ contains
|
849
|
+
the +:parent+ entry, it is the parent item (not index) of the new one. If _row_
|
850
|
+
and/or the +:col+ entry are negative, they're counted from backwards.
|
851
|
+
|
852
|
+
Note that, if an item with the given row, column and parent already exist, it is
|
853
|
+
replaced by the new item.
|
854
|
+
|
855
|
+
Returns the new item.
|
856
|
+
=end
|
857
|
+
def set text, type, row, opts = {}
|
858
|
+
col = opts[:col] || 0
|
859
|
+
parent = opts[:parent]
|
860
|
+
it = Qt::StandardItem.new(text)
|
861
|
+
row = (parent || self).row_count + row if row < 0
|
862
|
+
col = (parent || self).column_count + col if col < 0
|
863
|
+
if parent then parent.set_child row, col, it
|
864
|
+
else set_item row, col, it
|
865
|
+
end
|
866
|
+
@output_widget.set_output_type it.index, type
|
867
|
+
it
|
868
|
+
end
|
869
|
+
|
870
|
+
=begin rdoc
|
871
|
+
Inserts a new row in the model and sets the output type of its elements.
|
872
|
+
|
873
|
+
_opts_ can contain two keys:
|
874
|
+
+:parent+:: the <tt>Qt::StandardItem</tt> the new row should be child of. If not
|
875
|
+
given, the new row will be a top-level row
|
876
|
+
+:col+:: the column where to put the text (default: 0). It has effect only if
|
877
|
+
_text_ is a string (see below)
|
878
|
+
|
879
|
+
_text_ represents the contents of the new row and can be either a string or an
|
880
|
+
array containing strings and <b>nil</b>s.
|
881
|
+
|
882
|
+
If _text_ is an array, each entry of the array will become a column in the new row.
|
883
|
+
with a text, while *nil* entries will produce empty items (that is items
|
884
|
+
without text. Calling <tt>item.text</tt> on these items will give *nil*. The
|
885
|
+
associated indexes, however, are valid).
|
886
|
+
|
887
|
+
If _text_ is a string, the resulting row will have all the elements
|
888
|
+
from column 0 to the one before the +:col+ entry set to empty element (as described
|
889
|
+
above). The column +:col+ has text _text_. Of course, if +:col+ is 0 (or is missing)
|
890
|
+
no empty items are created.
|
891
|
+
|
892
|
+
_type_ is the output type to give to the items in the new row. It can be either
|
893
|
+
a symbol or an array of symbols. If it is a symbol, it will be the output type
|
894
|
+
of all elements in the new row. If it is an array, each entry will be the type
|
895
|
+
of the corresponding non-empty item (that is of the item in _text_ which has the
|
896
|
+
same index after removing all *nil* elements from _text_). If _type_ is longer
|
897
|
+
than the _text_ array, the entries in excess are ignored (if _text_ is a string,
|
898
|
+
it behaves as an array of size 1 in this regard). If _type_ is shorter than the
|
899
|
+
_text_ array, the entries in excess won't have their output type set.
|
900
|
+
|
901
|
+
_row_ is the index where the new row should be put. If *nil*, the new row will
|
902
|
+
be appended to the model.
|
903
|
+
|
904
|
+
If _row_ or +:col+ are negative, they're counted from the end. That is, the actual
|
905
|
+
row index if _row_ is negative is <tt>row_count+row</tt>. The same happens with
|
906
|
+
+:col+.
|
907
|
+
|
908
|
+
If _row_ is greater than <tt>row_count</tt> (or negative and its absolute value
|
909
|
+
is greater than <tt>row_count</tt>), +IndexError+ is raised. This is because
|
910
|
+
<tt>Qt::StandardItemModel</tt> doesn't allow for a row to be inserted after the
|
911
|
+
last one. This doesn't happen for the columns, which are added automatically.
|
912
|
+
|
913
|
+
This method returns an array containing all the non-empty items of the new row.
|
914
|
+
=end
|
915
|
+
def insert text, type, row, opts = {}
|
916
|
+
parent = opts[:parent] || self
|
917
|
+
rc = parent.row_count
|
918
|
+
row ||= rc
|
919
|
+
row = rc + row if row < 0
|
920
|
+
col = opts[:col] || 0
|
921
|
+
cc = parent.column_count
|
922
|
+
col = cc + col if col < 0
|
923
|
+
if row < 0 or row > rc
|
924
|
+
raise IndexError, "Row index #{row} is out of range. The allowed values are from 0 to #{rc}"
|
925
|
+
end
|
926
|
+
text = Array.new(col) << text unless text.is_a? Array
|
927
|
+
items = text.map do |i|
|
928
|
+
i ? Qt::StandardItem.new(i) : Qt::StandardItem.new
|
929
|
+
end
|
930
|
+
parent.insert_row row, items
|
931
|
+
items.delete_if{|i| i.text.nil?}
|
932
|
+
type = Array.new(items.size, type) unless type.is_a? Array
|
933
|
+
items.each_with_index do |it, i|
|
934
|
+
@output_widget.set_output_type it.index, type[i]
|
935
|
+
end
|
936
|
+
items
|
937
|
+
end
|
938
|
+
|
939
|
+
=begin rdoc
|
940
|
+
Similar to +insert+, but inserts each line of _text_ in a different item, one
|
941
|
+
below the other, starting at the position given by the _row_ and _opts_ argument.
|
942
|
+
|
943
|
+
_row_ and _opts_ have the same meaning as in +insert+.
|
944
|
+
|
945
|
+
_text_ can be either a string or an array of strings. If it is a string, it will
|
946
|
+
be split into lines, while if it is an array, each entry of the array will be considered
|
947
|
+
a single line (even if it contains newlines). In both cases, a single string is
|
948
|
+
passed to +insert+ for each line.
|
949
|
+
|
950
|
+
_type_ is the output type to assign to each item and should be a symbol. The
|
951
|
+
same type is used for all lines.
|
952
|
+
=end
|
953
|
+
def insert_lines text, type, row, opts = {}
|
954
|
+
lines = text.is_a?(Array) ? text : text.split("\n")
|
955
|
+
lines.each do |l|
|
956
|
+
insert l, type, row, opts
|
957
|
+
row += 1 if row
|
958
|
+
end
|
959
|
+
end
|
960
|
+
|
961
|
+
end
|
962
|
+
|
963
|
+
=begin rdoc
|
964
|
+
Convenience class to be used instead of <tt>Qt::ListView</tt> in an OutputWidget.
|
965
|
+
|
966
|
+
The only difference from Qt::ListVew is that it defines a <tt>context_menu_requested(QPoint)</tt>
|
967
|
+
signal and emits it from its +contextMenuEvent+ method
|
968
|
+
=end
|
969
|
+
class ListView < Qt::ListView
|
970
|
+
|
971
|
+
signals 'context_menu_requested(QPoint)'
|
972
|
+
|
973
|
+
=begin rdoc
|
974
|
+
Works as in the superclass but also emits the <tt>context_menu_requested(QPoint)</tt>
|
975
|
+
signal
|
976
|
+
=end
|
977
|
+
def contextMenuEvent e
|
978
|
+
super e
|
979
|
+
emit context_menu_requested(e.global_pos)
|
980
|
+
end
|
981
|
+
|
982
|
+
end
|
983
|
+
|
984
|
+
=begin rdoc
|
985
|
+
Convenience class to be used instead of <tt>Qt::TreeView</tt> in an OutputWidget.
|
986
|
+
|
987
|
+
The only difference from Qt::TreeVew is that it defines a <tt>context_menu_requested(QPoint)</tt>
|
988
|
+
signal and emits it from its +contextMenuEvent+ method
|
989
|
+
=end
|
990
|
+
class TreeView < Qt::TreeView
|
991
|
+
|
992
|
+
signals 'context_menu_requested(QPoint)'
|
993
|
+
|
994
|
+
=begin rdoc
|
995
|
+
Works as in the superclass but also emits the <tt>context_menu_requested(QPoint)</tt>
|
996
|
+
signal
|
997
|
+
=end
|
998
|
+
def contextMenuEvent e
|
999
|
+
super e
|
1000
|
+
emit context_menu_requested(e.global_pos)
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
=begin rdoc
|
1006
|
+
Convenience class to be used instead of <tt>Qt::TableView</tt> in an OutputWidget.
|
1007
|
+
|
1008
|
+
The only difference from Qt::TableVew is that it defines a <tt>context_menu_requested(QPoint)</tt>
|
1009
|
+
signal and emits it from its +contextMenuEvent+ method
|
1010
|
+
=end
|
1011
|
+
class TableView < Qt::TableView
|
1012
|
+
|
1013
|
+
signals 'context_menu_requested(QPoint)'
|
1014
|
+
|
1015
|
+
=begin rdoc
|
1016
|
+
Works as in the superclass but also emits the <tt>context_menu_requested(QPoint)</tt>
|
1017
|
+
signal
|
1018
|
+
=end
|
1019
|
+
def contextMenuEvent e
|
1020
|
+
super e
|
1021
|
+
emit context_menu_requested(e.global_pos)
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
=begin rdoc
|
1027
|
+
Array of actions and separators (represented by nil) which allows to easily
|
1028
|
+
insert an entry before or after another one
|
1029
|
+
=end
|
1030
|
+
class ActionList < Array
|
1031
|
+
|
1032
|
+
=begin rdoc
|
1033
|
+
:call-seq: list.insert_before entry, name1 [, name2, ...]
|
1034
|
+
|
1035
|
+
Adds one or more actions before a given one
|
1036
|
+
=====Arguments
|
1037
|
+
_entry_:: the name of the action before which to insert the new ones. If this
|
1038
|
+
an integer _n_, then the actions will be added before the _n_th
|
1039
|
+
separator. If the entry doesn't exist, or the number of separators is
|
1040
|
+
less than _n_, the new entries will be added at the end of the list
|
1041
|
+
_namei_:: the names of the actions to add (or +nil+ for separators)
|
1042
|
+
=end
|
1043
|
+
def insert_before entry, *names
|
1044
|
+
insert_after_or_before entry, :before, names
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
=begin rdoc
|
1048
|
+
:call-seq: list.insert_after entry, name1 [, name2, ...]
|
1049
|
+
|
1050
|
+
Adds one or more actions after a given one
|
1051
|
+
=====Arguments
|
1052
|
+
_entry_:: the name of the action after which to insert the new ones. If this
|
1053
|
+
an integer _n_, then the actions will be added after the _n_th
|
1054
|
+
separator. If the entry doesn't exist, or the number of separators is
|
1055
|
+
less than _n_, the new entries will be added at the end of the list
|
1056
|
+
_namei_:: the names of the actions to add (or +nil+ for separators)
|
1057
|
+
=end
|
1058
|
+
def insert_after entry, *names
|
1059
|
+
insert_after_or_before entry, :after, names
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
private
|
1063
|
+
|
1064
|
+
=begin rdoc
|
1065
|
+
Helper method used by <tt>insert_after</tt> and <tt>insert_before</tt> which performs
|
1066
|
+
the actual insertion of the elements.
|
1067
|
+
|
1068
|
+
_entry_ has the same meaning as in <tt>insert_after</tt> and <i>insert_before</tt>,
|
1069
|
+
while _names_ has the same meaning as the optional arguments of those methods.
|
1070
|
+
_where_ can be +:after+ or +:before+ and tells whether the new elements should be
|
1071
|
+
inserted after or before the position.
|
1072
|
+
=end
|
1073
|
+
def insert_after_or_before entry, where, names
|
1074
|
+
idx = if entry.is_a? Integer
|
1075
|
+
# The - 1 is needed because entry is the number of separators, but indexes start
|
1076
|
+
# at 0
|
1077
|
+
(self.each_with_index.find_all{|a, i| a.nil?}[entry - 1] || [])[1]
|
1078
|
+
else self.index(entry)
|
1079
|
+
end
|
1080
|
+
if idx and where == :after then idx += 1
|
1081
|
+
elsif !idx then idx = size
|
1082
|
+
end
|
1083
|
+
#Using reverse_each, we don't need to care increasing idx as we add items (the
|
1084
|
+
#first item should be at position idx, the second at idx+1 and so on. With reverse_each)
|
1085
|
+
#this happens automatically. The +1 is needed to insert the items after idx
|
1086
|
+
names.reverse_each{|n| insert idx, n}
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
end
|