ruber 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. data/COPYING +339 -0
  2. data/INSTALL +137 -0
  3. data/LICENSE +8 -0
  4. data/bin/ruber +65 -0
  5. data/data/share/apps/ruber/core_components.yaml +31 -0
  6. data/data/share/apps/ruber/ruberui.rc +109 -0
  7. data/data/share/icons/ruber.png +0 -0
  8. data/data/share/pixmaps/ruby.png +0 -0
  9. data/icons/ruber-16.png +0 -0
  10. data/icons/ruber-32.png +0 -0
  11. data/icons/ruber-48.png +0 -0
  12. data/icons/ruber-8.png +0 -0
  13. data/lib/ruber/application/application.rb +288 -0
  14. data/lib/ruber/application/plugin.yaml +11 -0
  15. data/lib/ruber/component_manager.rb +899 -0
  16. data/lib/ruber/config/config.rb +82 -0
  17. data/lib/ruber/config/plugin.yaml +3 -0
  18. data/lib/ruber/document_project.rb +209 -0
  19. data/lib/ruber/documents/document_list.rb +416 -0
  20. data/lib/ruber/documents/plugin.yaml +4 -0
  21. data/lib/ruber/editor/document.rb +506 -0
  22. data/lib/ruber/editor/editor_view.rb +167 -0
  23. data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
  24. data/lib/ruber/exception_widgets.rb +245 -0
  25. data/lib/ruber/external_program_plugin.rb +397 -0
  26. data/lib/ruber/filtered_output_widget.rb +342 -0
  27. data/lib/ruber/gui_states_handler.rb +231 -0
  28. data/lib/ruber/kde_config_option_backend.rb +167 -0
  29. data/lib/ruber/kde_sugar.rb +249 -0
  30. data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
  31. data/lib/ruber/main_window/main_window.rb +524 -0
  32. data/lib/ruber/main_window/main_window_actions.rb +537 -0
  33. data/lib/ruber/main_window/main_window_internal.rb +239 -0
  34. data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
  35. data/lib/ruber/main_window/output_color_widget.rb +35 -0
  36. data/lib/ruber/main_window/plugin.yaml +58 -0
  37. data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
  38. data/lib/ruber/main_window/status_bar.rb +156 -0
  39. data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
  40. data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
  41. data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
  42. data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
  43. data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
  44. data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
  45. data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
  46. data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
  47. data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
  48. data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
  49. data/lib/ruber/main_window/workspace.rb +442 -0
  50. data/lib/ruber/output_widget.rb +1093 -0
  51. data/lib/ruber/plugin.rb +264 -0
  52. data/lib/ruber/plugin_like.rb +589 -0
  53. data/lib/ruber/plugin_specification.rb +106 -0
  54. data/lib/ruber/plugin_specification_reader.rb +451 -0
  55. data/lib/ruber/project.rb +493 -0
  56. data/lib/ruber/project_backend.rb +105 -0
  57. data/lib/ruber/projects/plugin.yaml +11 -0
  58. data/lib/ruber/projects/project_files_list.rb +314 -0
  59. data/lib/ruber/projects/project_files_widget.rb +301 -0
  60. data/lib/ruber/projects/project_list.rb +314 -0
  61. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
  62. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
  63. data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
  64. data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
  65. data/lib/ruber/qt_sugar.rb +673 -0
  66. data/lib/ruber/settings_container.rb +515 -0
  67. data/lib/ruber/settings_dialog.rb +244 -0
  68. data/lib/ruber/settings_dialog_manager.rb +503 -0
  69. data/lib/ruber/utils.rb +414 -0
  70. data/lib/ruber/yaml_option_backend.rb +159 -0
  71. data/outsider_files +15 -0
  72. data/plugins/autosave/autosave.rb +404 -0
  73. data/plugins/autosave/plugin.yaml +16 -0
  74. data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
  75. data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
  76. data/plugins/command/command.png +0 -0
  77. data/plugins/command/command.rb +74 -0
  78. data/plugins/command/plugin.yaml +11 -0
  79. data/plugins/find_in_files/find_in_files.rb +337 -0
  80. data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
  81. data/plugins/find_in_files/find_in_files_ui.rc +11 -0
  82. data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
  83. data/plugins/find_in_files/plugin.yaml +23 -0
  84. data/plugins/find_in_files/ui/config_widget.rb +58 -0
  85. data/plugins/find_in_files/ui/config_widget.ui +41 -0
  86. data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
  87. data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
  88. data/plugins/project_browser/plugin.yaml +10 -0
  89. data/plugins/project_browser/project_browser.rb +245 -0
  90. data/plugins/rake/plugin.yaml +39 -0
  91. data/plugins/rake/rake.png +0 -0
  92. data/plugins/rake/rake.rb +567 -0
  93. data/plugins/rake/rake_extension.rb +153 -0
  94. data/plugins/rake/rake_widgets.rb +615 -0
  95. data/plugins/rake/rakeui.rc +27 -0
  96. data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
  97. data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
  98. data/plugins/rake/ui/choose_task_widget.rb +77 -0
  99. data/plugins/rake/ui/choose_task_widget.ui +72 -0
  100. data/plugins/rake/ui/config_widget.rb +127 -0
  101. data/plugins/rake/ui/config_widget.ui +123 -0
  102. data/plugins/rake/ui/project_widget.rb +217 -0
  103. data/plugins/rake/ui/project_widget.ui +246 -0
  104. data/plugins/rspec/plugin.yaml +30 -0
  105. data/plugins/rspec/rspec.png +0 -0
  106. data/plugins/rspec/rspec.rb +945 -0
  107. data/plugins/rspec/rspec.svg +90 -0
  108. data/plugins/rspec/rspecui.rc +20 -0
  109. data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
  110. data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
  111. data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
  112. data/plugins/ruby_development/plugin.yaml +27 -0
  113. data/plugins/ruby_development/ruby_development.png +0 -0
  114. data/plugins/ruby_development/ruby_development.rb +453 -0
  115. data/plugins/ruby_development/ruby_developmentui.rc +19 -0
  116. data/plugins/ruby_development/ui/project_widget.rb +112 -0
  117. data/plugins/ruby_development/ui/project_widget.ui +108 -0
  118. data/plugins/ruby_runner/config_widget.rb +116 -0
  119. data/plugins/ruby_runner/plugin.yaml +26 -0
  120. data/plugins/ruby_runner/project_widget.rb +62 -0
  121. data/plugins/ruby_runner/ruby.png +0 -0
  122. data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
  123. data/plugins/ruby_runner/ruby_runner.rb +411 -0
  124. data/plugins/ruby_runner/ui/config_widget.rb +92 -0
  125. data/plugins/ruby_runner/ui/config_widget.ui +91 -0
  126. data/plugins/ruby_runner/ui/project_widget.rb +60 -0
  127. data/plugins/ruby_runner/ui/project_widget.ui +48 -0
  128. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
  129. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
  130. data/plugins/state/plugin.yaml +28 -0
  131. data/plugins/state/state.rb +520 -0
  132. data/plugins/state/ui/config_widget.rb +92 -0
  133. data/plugins/state/ui/config_widget.ui +89 -0
  134. data/plugins/syntax_checker/plugin.yaml +18 -0
  135. data/plugins/syntax_checker/syntax_checker.rb +662 -0
  136. data/ruber.desktop +10 -0
  137. data/spec/annotation_model_spec.rb +174 -0
  138. data/spec/common.rb +119 -0
  139. data/spec/component_manager_spec.rb +1259 -0
  140. data/spec/document_list_spec.rb +626 -0
  141. data/spec/document_project_spec.rb +373 -0
  142. data/spec/document_spec.rb +779 -0
  143. data/spec/editor_view_spec.rb +167 -0
  144. data/spec/external_program_plugin_spec.rb +676 -0
  145. data/spec/filtered_output_widget_spec.rb +642 -0
  146. data/spec/gui_states_handler_spec.rb +304 -0
  147. data/spec/kde_config_option_backend_spec.rb +214 -0
  148. data/spec/kde_sugar_spec.rb +101 -0
  149. data/spec/ktexteditor_wrapper_spec.rb +305 -0
  150. data/spec/output_widget_spec.rb +1703 -0
  151. data/spec/plugin_spec.rb +1393 -0
  152. data/spec/plugin_specification_reader_spec.rb +1765 -0
  153. data/spec/plugin_specification_spec.rb +401 -0
  154. data/spec/project_backend_spec.rb +172 -0
  155. data/spec/project_files_list_spec.rb +401 -0
  156. data/spec/project_list_spec.rb +511 -0
  157. data/spec/project_spec.rb +990 -0
  158. data/spec/qt_sugar_spec.rb +328 -0
  159. data/spec/settings_container_spec.rb +617 -0
  160. data/spec/settings_dialog_manager_spec.rb +773 -0
  161. data/spec/settings_dialog_spec.rb +419 -0
  162. data/spec/state_spec.rb +991 -0
  163. data/spec/utils_spec.rb +406 -0
  164. data/spec/workspace_spec.rb +869 -0
  165. data/spec/yaml_option_backend_spec.rb +246 -0
  166. 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