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,397 @@
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 'ruber/plugin'
22
+
23
+ module Ruber
24
+
25
+ =begin rdoc
26
+ Base class for plugins whose main task is to run an external program and maybe
27
+ (not necessarily) to display the output in a tool widget.
28
+
29
+ Basically, this class is a wrapper for <tt>KDE::Process</tt> providing a more
30
+ integrated API for the most common functions. In particular, it provides the
31
+ following functionality:
32
+ * automatic creation of the process
33
+ * automatic cleanup at shutdown
34
+ * automatic managing of buffered output
35
+ * nicer API to read from standard output and standard error
36
+ * simplified API to start the process
37
+ * automatic display of standard output and standard error in a tool widget (if required)
38
+ * display of the command line and of the process status in a tool widget (if required)
39
+
40
+ To start the process, you simply call the <tt>run_process</tt> method, passing it
41
+ the name of the program to run, the working directory and the arguments.
42
+
43
+ When the process emits the <tt>readyReadStandardOutput()</tt> or the
44
+ <tt>readyReadStandardError()</tt>, the contents of the appropriate stream are read
45
+ and split into lines. The resulting array are passed to the <tt>process_standard_output</tt>
46
+ or <tt>process_standard_error</tt> method. Derived classes can override these methods
47
+ to do what they want with the output.
48
+
49
+ In general, you can't be sure whether the data contained read from standard output
50
+ or error contains complete lines. To deal with this issue, whenob reading from a
51
+ stream, only full lines (that is, lines which end in "\n") are passed to
52
+ <tt>process_standard_output</tt> or <tt>process_standard_error</tt>. If the last
53
+ line isn't complete, it is stored in a buffer. The next time characters are read
54
+ from the process, if they came from the same channel, the buffer is added at the
55
+ beginning of the new stream; if they came from the other channel, they're passed
56
+ to the appropriate <tt>process_standard_*</tt> method. This behaviour can be
57
+ changed by passing the appropriate arguments to the constructor.
58
+
59
+ Some methods in this class have the ability to insert data in a OutputWidget. They
60
+ will do so if the <tt>@output_widget</tt> instance variable (which is created in
61
+ the constructor, if it doesn't already exist) is not *nil*. Note that those methods
62
+ don't check if <tt>@output_widget</tt> actually is an OutputWidget or not. If it
63
+ is something else, you'll get errors. In all the following documentation it is
64
+ assumed that:
65
+ * the expression <i>the output widget</i> refers to the object the <tt>@output_widget</tt>
66
+ instance variable refers to, unless it's *nil*
67
+ * everything concerning the output widget will be ignored (without giving any error)
68
+ if the output widget doesn't exist
69
+
70
+ ===Signals
71
+ =====<tt>process_finished(int code, QString reason)</tt>
72
+ Signal emitted when the process finishes. _code_ is the exit code of the process,
73
+ while _reason_ is a string which can have one of the values "killed", "crash" or
74
+ be empty. If it's empty, it means that the program finished normally; "killed"
75
+ means that it was killed by the user and "crash" means that the program crashed.
76
+ =====<tt>process_started()</tt>
77
+ Signal emitted when the process has started
78
+ =====<tt>process_failed_to_start()</tt>
79
+ Signal emitted if the process couldn't be started (for example, because the given
80
+ program doesn't exist)
81
+ ===Slots
82
+ * <tt>slot_process_finished(int, QProcess::ExitStatus)</tt>
83
+ * <tt>stop_process()</tt>
84
+ =end
85
+ class ExternalProgramPlugin < GuiPlugin
86
+
87
+ slots 'slot_process_finished(int, QProcess::ExitStatus)', 'display_exit_message(int, QString)',
88
+ :stop_process
89
+
90
+ signals 'process_finished(int, QString)', :process_started, :process_failed_to_start
91
+
92
+ =begin rdoc
93
+ The <tt>KDE::Process</tt> used by the plugin
94
+ =end
95
+ attr_reader :process
96
+
97
+ =begin rdoc
98
+ Creates a new ExternalProgramPlugin.
99
+
100
+ _pdf_ is the plugin info object for the plugin. If <i>line_buffered</i> is *false*,
101
+ buffering won't be used (all the characters read from the process will be passed
102
+ to <tt>process_standard_output</tt> or <tt>process_standard_error</tt> even if they
103
+ don't end in a newline).
104
+
105
+ <b>Note:</b> the process' channel mode will be set to <tt>Qt::Process::SeparateChannels</tt>.
106
+ You can set it to any value you want later
107
+ =end
108
+ def initialize pdf, line_buffered = true
109
+ super pdf
110
+ @buffer = nil
111
+ @buffer_content_channel = nil
112
+ @line_buffered = line_buffered
113
+ @output_widget = nil unless defined? @output_widget
114
+ @process = KDE::Process.new self
115
+ @process.process_channel_mode = Qt::Process::SeparateChannels
116
+ @process.output_channel_mode = KDE::Process::SeparateChannels
117
+ @process.connect SIGNAL(:readyReadStandardOutput) do
118
+ do_stdout @process.read_all_standard_output.to_s
119
+ end
120
+ @process.connect SIGNAL(:readyReadStandardError) do
121
+ do_stderr @process.read_all_standard_error.to_s
122
+ end
123
+ connect @process, SIGNAL('finished(int, QProcess::ExitStatus)'), self, SLOT('slot_process_finished(int, QProcess::ExitStatus)')
124
+ connect self, SIGNAL('process_finished(int, QString)'), self, SLOT('display_exit_message(int, QString)')
125
+ @process.connect SIGNAL('error(QProcess::ProcessError)') do |e|
126
+ failed_to_start if e == Qt::Process::FailedToStart
127
+ end
128
+ connect @process, SIGNAL('started()'), self, SIGNAL('process_started()')
129
+ end
130
+
131
+ =begin rdoc
132
+ Starts the program.
133
+
134
+ _prog_ is the name of the program (you don't need to specify the absolute path
135
+ if it's in PATH). _args_ is an array containing the arguments. _dir_ is the working
136
+ directory.
137
+
138
+ _title_ is the string to display in the output widget. If it is an empty string, the name
139
+ of the program followed by its arguments will be used. If it is *nil* or *false*,
140
+ the title won't be set.
141
+ =end
142
+ def run_process prog, dir, args, title = ''
143
+ @buffer = nil
144
+ @buffer_content_channel = nil
145
+ @process.clear_program
146
+ @process.working_directory = dir
147
+ program = [prog] + args
148
+ @process.program = program
149
+ if @output_widget and title
150
+ title = program.join ' ' if title.empty?
151
+ @output_widget.title = title
152
+ end
153
+ @process.start
154
+ end
155
+
156
+ =begin rdoc
157
+ Stops the process.
158
+
159
+ It's a shortcut for <tt>process.kill</tt>
160
+ =end
161
+ def stop_process
162
+ @process.kill
163
+ end
164
+
165
+ =begin rdoc
166
+ Prepares the plugin to be unloaded by killing the process (no signal will be emitted
167
+ from the process or the plugin from now on).
168
+
169
+ If you reimplement this method, don't forget to call *super*. If you don't you
170
+ might cause a crash when Ruber is closed
171
+ =end
172
+ def shutdown
173
+ @process.block_signals true
174
+ @process.kill
175
+ super
176
+ end
177
+
178
+ private
179
+
180
+ =begin rdoc
181
+ Pre-processes the string _str_ then passes the resulting array to <tt>process_standard_output</tt>.
182
+
183
+ Pre-processing the string means:
184
+ * splitting it into lines
185
+ * emptying the buffer (by passing its contents to <tt>process_standard_error</tt>
186
+ if it refers to standard error or prepending it to _str_ if it refers to standard
187
+ output)
188
+ * putting the last line in the buffer if it doesn't end in a newline.
189
+
190
+ If the plugin is not buffered, then only the first step is done.
191
+
192
+ <b>Note:</b> if _str_ is empty, nothing will happen. If it consists only of newlines,
193
+ it will cause the buffer to be emptied (as described above) and nothing else.
194
+ <b>Note:</b> consecutive newlines will be treated as a single newline
195
+ =end
196
+ def do_stdout str
197
+ return if str.empty?
198
+ if @line_buffered and @buffer
199
+ buffer = @buffer
200
+ channel = @buffer_content_channel
201
+ @buffer = nil
202
+ @buffer_content_channel = nil
203
+ if channel == :stdout
204
+ str = buffer + str
205
+ return do_stdout str
206
+ else process_standard_error [buffer]
207
+ end
208
+ end
209
+ lines = str.split_lines
210
+ if @line_buffered and !str.end_with? "\n"
211
+ @buffer = lines.pop
212
+ @buffer_content_channel = :stdout
213
+ end
214
+ return if lines.empty?
215
+ process_standard_output lines
216
+ end
217
+
218
+ =begin rdoc
219
+ Pre-processes the string _str_ then passes the resulting array to <tt>process_standard_error</tt>.
220
+
221
+ Pre-processing the string means:
222
+ * splitting it into lines
223
+ * emptying the buffer (by passing its contents to <tt>process_standard_output</tt>
224
+ if it refers to standard output or prepending it to _str_ if it refers to standard
225
+ error)
226
+ * putting the last line in the buffer if it doesn't end in a newline.
227
+
228
+ If the plugin is not buffered, then only the first step is done.
229
+
230
+ <b>Note:</b> if _str_ is empty, nothing will happen. If it consists only of newlines,
231
+ it will cause the buffer to be emptied (as described above) and nothing else.
232
+ <b>Note:</b> consecutive newlines will be treated as a single newline
233
+ =end
234
+ def do_stderr str
235
+ return if str.empty?
236
+ if @line_buffered and @buffer
237
+ buffer = @buffer
238
+ channel = @buffer_content_channel
239
+ @buffer = nil
240
+ @buffer_content_channel = nil
241
+ if channel == :stderr
242
+ str = buffer + str
243
+ return do_stderr str
244
+ else process_standard_output [buffer]
245
+ end
246
+ end
247
+ lines = str.split_lines
248
+ if @line_buffered and !str.end_with? "\n"
249
+ @buffer = lines.pop
250
+ @buffer_content_channel = :stderr
251
+ end
252
+ return if lines.empty?
253
+ process_standard_error lines
254
+ end
255
+
256
+ =begin rdoc
257
+ Does something with the text written to standard output by the program.
258
+
259
+ The base class implementation of this method inserts the text at the end of the
260
+ output widget in the first column (one item per line) and sets the output type
261
+ to +:output+. Nothing is done if the output widget doesn't exist. Subclasses
262
+ can reimplement this method to do something else (in this case, you don't usually
263
+ want to call *super*)
264
+
265
+ _lines_ is an array where each entry corresponds to a line of output from the
266
+ program. If buffering is on, each entry is a complete line (or should be considered
267
+ such). If buffering is off, you'll have to take care of newlines by yourself.
268
+ =end
269
+ def process_standard_output lines
270
+ return unless @output_widget
271
+ mod = @output_widget.model
272
+ rc = mod.row_count
273
+ mod.insert_rows rc, lines.size
274
+ lines.each_with_index do |l, i|
275
+ idx = mod.index(i + rc, 0)
276
+ mod.set_data idx, Qt::Variant.new(l)
277
+ @output_widget.set_output_type idx, :output
278
+ end
279
+ end
280
+
281
+ =begin rdoc
282
+ Does something with the text written to standard error by the program.
283
+
284
+ The base class implementation of this method inserts the text at the end of the
285
+ output widget in the first column (one item per line) and sets the output type
286
+ to +:output+. Nothing is done if the output widget doesn't exist. Subclasses
287
+ can reimplement this method to do something else (in this case, you don't usually
288
+ want to call *super*)
289
+
290
+ _lines_ is an array where each entry corresponds to a line of output from the
291
+ program. If buffering is on, each entry is a complete line (or should be considered
292
+ such). If buffering is off, you'll have to take care of newlines by yourself.
293
+ =end
294
+ def process_standard_error lines
295
+ return unless @output_widget
296
+ mod = @output_widget.model
297
+ rc = mod.row_count
298
+ mod.insert_rows rc, lines.size
299
+ lines.each_with_index do |l, i|
300
+ idx = mod.index(i + rc, 0)
301
+ mod.set_data idx, Qt::Variant.new(l)
302
+ @output_widget.set_output_type idx, :error
303
+ end
304
+ end
305
+
306
+ =begin rdoc
307
+ Method called if the program fails to start (in response, but not connected to,
308
+ the process <tt>error(QProcess::ProcessError)</tt> signal if the argument is
309
+ <tt>Qt::Process::FailedToStart</tt>).
310
+
311
+ It emits the <tt>process_failed_to_start()</tt> signal and displays an appropriate
312
+ message to the output widget (using the <tt>:error1</tt> output type).
313
+
314
+ If <tt>@output_widget</tt> is not *nil* but you don't want the message to be
315
+ generated, you'll need to override this method. If you do so, don't forget to
316
+ emit the <tt>failed_to_start()</tt> signal.
317
+ =end
318
+ def failed_to_start
319
+ emit process_failed_to_start
320
+ if @output_widget
321
+ mod = @output_widget.model
322
+ rc = mod.row_count
323
+ mod.insert_row rc
324
+ cmd = @process.program
325
+ cmd_line = cmd.join " "
326
+ prog = cmd.shift
327
+ idx = mod.index(rc, 0)
328
+ mod.set_data idx, Qt::Variant.new("#{prog} failed to start. The command line was #{cmd_line}")
329
+ @output_widget.set_output_type idx, :error1
330
+ end
331
+ end
332
+
333
+ =begin rdoc
334
+ Slot called in response to the process' <tt>finished(int, QProcess::ExitStatus)</tt>
335
+ signal.
336
+
337
+ It emits the <tt>process_finished(int, QString)</tt> signal
338
+ =end
339
+ def slot_process_finished code, status
340
+ str = @process.read_all_standard_output.to_s
341
+ str << "\n" unless str.end_with?("\n")
342
+ do_stdout str
343
+ str = @process.read_all_standard_error.to_s
344
+ str << "\n" unless str.end_with?("\n")
345
+ do_stderr str
346
+ reason = ''
347
+ if status == Qt::Process::CrashExit
348
+ reason = code == 0 ? 'killed' : 'crash'
349
+ end
350
+ emit process_finished code, reason
351
+ end
352
+
353
+ =begin rdoc
354
+ Displays a message in the output widget describing the exit status of the program.
355
+
356
+ The message (output type message) has the format <i>program_name</i> <i>message</i>,
357
+ where message is:
358
+
359
+ * <tt>exited normally</tt> if _reason_ is an empty string
360
+ * <tt>was killed</tt> if _reason_ is 'killed'
361
+ * <tt>exited with code</tt> _code_ if _reason_ is 'crash'
362
+
363
+ This method is meant to be connected to the <tt>process_finished(int, QString)</tt>
364
+ signal. Its arguments match those of the signal.
365
+
366
+ If you want to change the message, override this method. If you don't want any
367
+ message and the <tt>@output_widget</tt> instance variable is not *nil*, you can
368
+ either disconnect this slot from the <tt>process_finished(int, QString)</tt>
369
+ signal or override this method without calling *super*
370
+ =end
371
+ def display_exit_message code, reason
372
+ return unless @output_widget
373
+ mod = @output_widget.model
374
+ rc = mod.row_count
375
+ mod.insert_row rc
376
+ idx = mod.index rc, 0
377
+ type = :message
378
+ text = "Process "
379
+ case reason
380
+ when ''
381
+ if code == 0 then text << 'exited normally'
382
+ else
383
+ text << "exited with code #{code}"
384
+ type = :message_bad
385
+ end
386
+ when 'killed' then text << 'killed'
387
+ when 'crash'
388
+ text << "crashed with code #{code}"
389
+ type = :message_bad
390
+ end
391
+ mod.set_data idx, Qt::Variant.new(text)
392
+ @output_widget.set_output_type idx, type
393
+ end
394
+
395
+ end
396
+
397
+ end
@@ -0,0 +1,342 @@
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 'ruber/output_widget'
22
+
23
+ module Ruber
24
+
25
+ =begin rdoc
26
+ OutputWidget which allows the user to filter the contents, displaying only the
27
+ items which match a given regexp.
28
+
29
+ This widget provides a line edit to enter the regexp, which is shown using a
30
+ 'Create filter' action from the context menu. When the user presses the Return
31
+ key from within the line edit, a filter is created from the current text and applied
32
+ (if the line edit is empty, any existing filter is removed). If the user doesn't
33
+ want to create the filter, the line edit is hidden by pressing ESC while within it.
34
+
35
+ The filter is implemented using a FilteredOutputWidget::FilterModel, which is a
36
+ slightly modified <tt>Qt::SortFilterProxyModel</tt>. Most of the times, however,
37
+ you don't need to worry about this, as all the methods provided by this class
38
+ still use indexes referred to the source model. This means that, most of the times,
39
+ adding filtering capability to an output widget is as simple as having it inherit
40
+ from FilteredOutputWidget rather than from OutputWidget.
41
+
42
+ Besides the 'Create Filter', this class adds two other entries to the context menu:
43
+ 'Clear Filter', which removes the existring filter, and a 'Ignore Filter' toggle
44
+ action, which allows to temporarily disable the filter without removing it. Also,
45
+ a new UI state, called 'no_filter' is defined: it's true if no filter is applied
46
+ and false otherwise
47
+
48
+ ===Slots
49
+ * <tt>create_filter_from_editor</tt>
50
+ * <tt>clear_filter</tt>
51
+ * <tt>ignore_filter(bool)</tt>
52
+ * <tt>show_editor</tt>
53
+ =end
54
+ class FilteredOutputWidget < OutputWidget
55
+
56
+ =begin rdoc
57
+ The model used to filter the contents of the output widget
58
+ =end
59
+ attr_reader :filter_model
60
+ alias :filter :filter_model
61
+ alias :source_model :model
62
+
63
+ slots :create_filter_from_editor, :clear_filter, 'ignore_filter(bool)',
64
+ :show_editor
65
+
66
+ =begin rdoc
67
+ Creates a new FilteredOutputWidget.
68
+
69
+ The arguments have the same meaning as in <tt>OutputWidget.new</tt>. The only
70
+ difference is that _opts_ can also contain a +:filter+ entry. If given, it is the
71
+ filter model to use (which should be derived from FilteredOutputWidget::FilterModel
72
+ or provide the same api); if this entry is missing, a new FilteredOutputWidget::FilterModel
73
+ will be used
74
+ =end
75
+ def initialize parent = nil, opts = {}
76
+ super parent, opts
77
+ @filter_model = opts[:filter] || FilterModel.new
78
+ @filter_model.parent = self
79
+ @filter_model.dynamic_sort_filter = true
80
+ @filter_model.source_model = model
81
+ view.model = @filter_model
82
+ disconnect @model, SIGNAL('rowsInserted(QModelIndex, int, int)'), self, SLOT('do_auto_scroll(QModelIndex, int, int)')
83
+ connect @filter_model, SIGNAL('rowsInserted(QModelIndex, int, int)'), self, SLOT('do_auto_scroll(QModelIndex, int, int)')
84
+ connect view.selection_model, SIGNAL('selectionChanged(QItemSelection, QItemSelection)'), self, SLOT('selection_changed(QItemSelection, QItemSelection)')
85
+ @editor = KDE::LineEdit.new self
86
+ connect @editor, SIGNAL('returnPressed(QString)'), self, SLOT(:create_filter_from_editor)
87
+ layout.add_widget @editor, 1, 0
88
+ def @editor.keyReleaseEvent e
89
+ super
90
+ hide if e.key == Qt::Key_Escape and e.modifiers == 0
91
+ end
92
+ @editor.hide
93
+ @editor.completion_object = KDE::Completion.new
94
+ @action_list.insert_before 1, nil, 'create_filter', 'ignore_filter', 'clear_filter'
95
+ create_standard_actions
96
+ set_state 'no_filter', true
97
+ end
98
+
99
+ =begin rdoc
100
+ Shows the line edit where the user can enter the filter regexp and gives it focus
101
+ =end
102
+ def show_editor
103
+ @editor.show
104
+ @editor.set_focus
105
+ end
106
+
107
+ =begin rdoc
108
+ Removes the existing filter (if any).
109
+
110
+ This means that, after calling this method, all items will be accepted
111
+ =end
112
+ def clear_filter
113
+ @filter_model.filter_reg_exp = ''
114
+ set_state 'no_filter', true
115
+ end
116
+
117
+ def scroll_to idx
118
+ case idx
119
+ when Numeric
120
+ rc = @filter_model.row_count
121
+ if idx >= rc then idx = rc -1
122
+ elsif idx < 0 and idx.abs < rc then idx = rc + idx
123
+ elsif idx < 0 then idx = 0
124
+ end
125
+ mod_idx = @filter_model.index idx, 0
126
+ @view.scroll_to mod_idx
127
+ when Qt::ModelIndex
128
+ idx = @filter_model.map_from_source idx unless idx.model == @filter_model
129
+ idx = @filter_model.index(@filter_model.row_count - 1, 0) unless idx.valid?
130
+ @view.scroll_to idx
131
+ when nil
132
+ @view.scroll_to @filter_model.index(@filter_model.row_count - 1, 0)
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ =begin rdoc
139
+ Creates the 'Create Filter', 'Ignore Filter' and 'Clear Filter' actions, their
140
+ state handlers and makes the necessary signal-slot connections
141
+ =end
142
+ def create_standard_actions
143
+ super
144
+ acts = []
145
+ acts << KDE::Action.new('Create Filter', self){self.object_name = 'create_filter'}
146
+ acts << KDE::Action.new('Clear Filter', self){self.object_name = 'clear_filter'}
147
+ acts << KDE::ToggleAction.new('Ignore Filter', self){self.object_name = 'ignore_filter'}
148
+ acts.each{|a| actions[a.object_name] = a}
149
+ connect actions['create_filter'], SIGNAL(:triggered), self, SLOT(:show_editor)
150
+ connect actions['clear_filter'], SIGNAL(:triggered), self, SLOT(:clear_filter)
151
+ connect actions['ignore_filter'], SIGNAL('toggled(bool)'), self, SLOT('ignore_filter(bool)')
152
+ register_action_handler actions['clear_filter'], '!no_filter'
153
+ register_action_handler actions['ignore_filter'], '!no_filter'
154
+ end
155
+
156
+ =begin rdoc
157
+ Instructs the filter model to ignore or not the filter according to _val_.
158
+ =end
159
+ def ignore_filter val
160
+ @filter_model.ignore_filter = val
161
+ end
162
+
163
+ =begin rdoc
164
+ Changes the filter accordig to the text in the line edit.
165
+
166
+ If the line edit is empty, the filter will be removed. Otherwise, it will be set
167
+ to the regexp corresponding to the text in the editor
168
+ =end
169
+ def create_filter_from_editor
170
+ text = @editor.text
171
+ if !text.empty?
172
+ mod = @editor.completion_object.add_item text
173
+ @filter_model.filter_reg_exp = text
174
+ set_state 'no_filter', false
175
+ else clear_filter
176
+ end
177
+ @editor.hide
178
+ end
179
+
180
+ =begin rdoc
181
+ Override of OutputWidget#copy which takes into account the filter. This means that
182
+ only the items in the filter model will be taken into account.
183
+
184
+ <b>Note:</b> the indexes passed to <tt>text_for_clipboard</tt> refer to the source
185
+ model, as in OutputWidget, not to the filter model.
186
+ =end
187
+ def copy
188
+ items = []
189
+ stack = []
190
+ @filter_model.row_count.times do |r|
191
+ @filter_model.column_count.times do |c|
192
+ stack << @filter_model.index(r, c)
193
+ end
194
+ end
195
+ until stack.empty?
196
+ it = stack.shift
197
+ items << @filter_model.map_to_source(it)
198
+ (@filter_model.row_count(it)-1).downto(0) do |r|
199
+ (@filter_model.column_count(it)-1).downto(0) do |c|
200
+ stack.unshift it.child(r, c)
201
+ end
202
+ end
203
+ end
204
+ clp = KDE::Application.clipboard
205
+ clp.text = text_for_clipboard items
206
+ end
207
+
208
+ =begin rdoc
209
+ Override of OutputWidget#copy_selected.
210
+
211
+ <b>Note:</b> the indexes passed to <tt>text_for_clipboard</tt> refer to the source
212
+ model, as in OutputWidget, not to the filter model.
213
+ =end
214
+ def copy_selected
215
+ clp = KDE::Application.clipboard
216
+ indexes = @view.selection_model.selected_indexes.map{|i| @filter_model.map_to_source i}
217
+ clp.text = text_for_clipboard indexes
218
+ end
219
+
220
+ =begin rdoc
221
+ Override of OutputWidget#maybe_open_file.
222
+
223
+ It converts the index _idx_ to the source model before passing it to *super*.
224
+ If _idx_ already refers to the source model, it is passed as it is to *super*.
225
+ =end
226
+ def maybe_open_file idx
227
+ idx = @filter_model.map_to_source idx if idx.model.same? @filter_model
228
+ super idx
229
+ end
230
+
231
+ =begin rdoc
232
+ Filter model derived from <tt>Qt::SortFilterProxyModel</tt> which better integrate
233
+ with FilteredOutputWidget.
234
+
235
+ The differences between this class and <tt>Qt::SortFilterProxyModel</tt> are the
236
+ following
237
+ * it has the ability to ignore the filter (see FilteredOutputWidget)
238
+ * it provides an easy way to always accept some kind of items (see +exclude+ and
239
+ <tt>exclude_from_filtering?</tt>)
240
+ * it emits a signal when the filter reg exp is changed
241
+
242
+ <b>Note:</b> this class is meant to be used with a regexp filter, not with a string
243
+ filter.
244
+
245
+ ===Signals
246
+ =====<tt>filter_changed(QString reg)</tt>
247
+ Signal emitted when the regexp used for filtering changes (including when the
248
+ filter is removed). _reg_ is a string containing the source of the regexp.
249
+ =end
250
+ class FilterModel < Qt::SortFilterProxyModel
251
+
252
+ signals 'filter_changed(QString)'
253
+
254
+ =begin rdoc
255
+ The kind of items to exclude from filtering. See <tt>exclude_from_filtering?</tt>
256
+ =end
257
+ attr_reader :exclude
258
+
259
+ =begin rdoc
260
+ Creates a new FilterModel.
261
+
262
+ _parent_ is the parent object, while _exclude_ is the initial value of the +exclude+
263
+ attribute (which can be changed later).
264
+ =end
265
+ def initialize parent = nil, exclude = nil
266
+ super parent
267
+ @exclude = exclude
268
+ @ignore_filter = false
269
+ end
270
+
271
+ =begin rdoc
272
+ Tells whether the object has been instructed to ignore the filter
273
+ =end
274
+ def filter_ignored?
275
+ @ignore_filter
276
+ end
277
+
278
+ =begin rdoc
279
+ Instructs the model to ignore or not the filter, according to _val_. This method
280
+ always invalidate the model
281
+ =end
282
+ def ignore_filter= val
283
+ @ignore_filter = val
284
+ invalidate
285
+ end
286
+
287
+ =begin rdoc
288
+ Override of <tt>Qt::SortFilterProxyModel#filter_reg_exp=</tt> which, after changing the regexp (
289
+ and invalidating the model) emits the <tt>filter_changed(QString)</tt> signal
290
+ =end
291
+ def filter_reg_exp= str
292
+ super
293
+ emit filter_changed(str)
294
+ end
295
+
296
+ def exclude= val
297
+ @exclude = val
298
+ invalidate_filter
299
+ end
300
+
301
+ protected
302
+
303
+ =begin rdoc
304
+ Override of <tt>Qt::SortFilterProxyModel#filterAcceptsRow</tt> which also takes
305
+ into account the setting for <tt>filter_ignored?</tt> and +exclude+. In particular,
306
+ if <tt>filter_ignored?</tt> is *true* or if <tt>exclude_from_filtering?</tt> returns
307
+ *true* for the given row and parent, then it will always return *true*. Otherwise,
308
+ it behaves like <tt>Qt::SortFilterProxyModel#filterAcceptsRow</tt>
309
+ =end
310
+ def filterAcceptsRow r, parent
311
+ return true if @ignore_filter
312
+ return true if exclude_from_filtering? r, parent
313
+ super
314
+ end
315
+
316
+ =begin rdoc
317
+ Tells the filter whether the filter should be applied to the given row and parent
318
+ or whether it should always be accepted, according to the value of +exclude+. In
319
+ particular:
320
+ * if +exclude+ is +:toplevel+, then the filter will be applied only to child items
321
+ (toplevel items will always be accepted)
322
+ * if +exclude+ is +:children+, then the filter will be applied only to toplevel items
323
+ (child items will always be accepted)
324
+ * any other value will cause the filter to be applied to all items
325
+
326
+ Derived classes can modify this behaviour by overriding this method. The arguments
327
+ have the same meaning as sin +filterAcceptsRow+
328
+ =end
329
+ def exclude_from_filtering? r, parent
330
+ case @exclude
331
+ when :toplevel then !parent.valid?
332
+ when :children then parent.valid?
333
+ else false
334
+ end
335
+ end
336
+
337
+ end
338
+
339
+ end
340
+
341
+
342
+ end