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.
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