ruber 0.0.1.1
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/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
|