Class: Ruber::OutputWidget

Inherits:
Qt::Widget
  • Object
show all
Includes:
GuiStatesHandler
Defined in:
lib/ruber/output_widget.rb

Overview

Widget meant to be used as tool widget to display the output of a program. It is based on Qt Model/View classes and provides the following facitlities:

  • an easy way to display in different colors items with different meaning (for example, error message are displayed in a different color from output messages)

  • a centralized way in which the user can choose the colors for different types of items

  • a context menu with some standard actions, which can be enhanced with custom ones and is automatically (under certain conditions) shown to the user

  • autoscrolling (which means that whenever new text is added, the view scrolls so that the text becomes visible)

  • a centralized way for the user to turn on and off word wrapping (which can be ignored by widgets for which it doesn’t make sense)

  • a mechanism which allows the user to click on an entry containing a file name in the widget to open the file in the editor. The mechanism can be customized by plugins to be better tailored to their needs (and can also be turned off)

  • a model class, derived from Qt::StandardItemModel, which provides a couple of convenience methods to make text insertion even easier.

Note that OutputWidget is not (and doesn’t derive from) one of the View classes. Rather, it’s a normal @Qt::Widget@ which has the view as its only child. You can add other widgets to the OutputWidget as you would with any other widget: create the widget with the OutputWidget as parent and add it to the OutputWidget’s layout (which is a @Qt::GridLayout@ where the view is at position 0,0).

Note: this class defines two new roles (@OutputTypeRole@ and @IsTitleRole@), which correspond to @Qt::UserRole@ and @Qt::UserRole+1@. Therefore, if you need to define other custom roles, please have them start from @Ruber::OutputWidget::IsTitleRole+1@.

h3. Output types

The @output_type@ of an entry in the model can be set using the #set_output_type method. This has two effects: first, the item will be displayed using the color chosen by the user for that output type; second, the name of the output type will be stored in that item under the custom role @OutputTypeRole@.

There are several predefined output types: @message@, @message_good@, @message_bad@, @output@, @output1@, @output2@, @warning@, @warning1@, @warning2@, @error@, @error1@ and @error2@. The types ending with a number can be used when you need different types with similar meaning.

The @message@ type (and its variations) are meant to display messages which don’t come from the external program but from Ruber itself (for example, a message telling that the external problem exited successfully or exited with an error). Its good and bad version are meant to display messages with good news and bad news respectively (for example: “the program exited successfully” could be displayed using the @message_good@ type, while “the program crashed” could be displayed using the @message_bad@ type).

The @output@ type is meant to be used to show the text written by the external program on standard output, while the @error@ type is used to display the text written on standard error. If you can distinguish between warning and errors, you can use the @warning@ type for the latter.

The colors for the default output types are chosen by the user from the configuration dialog and are used whenever those output types are requested.

New output types (and their associated colors) can be make known to the output widget by using the @set_color_for@ method. There’s no need to remove the color, for example when the plugin is unloaded (indeed, there’s no way to do so).

h3. The context menu

This widget automatically creates a context menu containing three actions: Copy, Copy Selected and Clear. Copy and Copy Selected copy the text contained respectively in all the items and in the selected items to the clipboard. The clear action removes all the entries from the model.

You can add other actions to the menu by performing the following steps:

  • add an entry in the appropriate position of #action_list. Note that this is an instance of ActionList, so it provides the insert_after and insert_before methods which allow to easily put the actions in the correct place. #action_list contains the @object_name@ of the actions (and nil for the separators), not the action themselves

  • create the actions (setting their @object_name@ to the values inserted in #action_list) and put them into the #actions hash, using the @object_name@ as keys. Of course, you also need to define the appropriate slots and connect them to the actions’ signals.

Note that actions can only be added before the menu is first shown (usually, you do that in the widget’s constructor). The #about_to_fill_menu signal is emitted just before the menu is built: this is the last time you can add entries to it.

OutputWidget mixes in the GuiStatesHandler module, which means you can define states to enable and disable actions as usual. By default, two states are defined: @no_text@ and @no_selection@. As the names imply, the former is true when the model is empty and false when there’s at least one item; the second is true when no item is selected and false when there are selected items.

For the menu to be displayed automatically, the view should have a @context_menu_requested(QPoint)@ signal. The menu will be displayed in response to that signal, at the point given as argument. For convenience, there are three classes ListView, TreeView and TableView, derived respectively from @Qt::ListView@, @Qt::TreeView@ and @Qt::TableView@ which emit that signal from their @contextMenuEvent@ method. If you use one of these classes as view, the menu will work automatically.

h3. Autoscrolling

Whenever an item is added to the list, the view will be scrolled so that the added item is visible. Plugins which don’t want this feature can disable it by setting #auto_scroll to false. Note that auto scrolling won’t happen if an item is modified or removed

h3. Word wrapping

If the user has enabled word wrapping for output widgets in the config dialog (the @general/wrap_output@ option), word wrapping will be automatically enabled for all output widgets. If the user has disabled it, it will be disabled for all widgets.

Subclasses can avoid the automatic behaviour by setting the #ignore_word_wrap_option attribute to true and managing word wrap by themselves. This is mostly useful for output widgets for which word wrap is undesirable or meaningless.

h3. Opening files in the editor

Whenever the user activates an item, the text of the item is searched for a filename (and optionally for a line number). If it’s found, a new editor view is opened and the file is displayed in it. The editor can be an already existing editor or a new one created by splitting the current editor or in a new tab, according to the @general/tool_open_files@ option.

This process uses four methods:

  • #maybe_open_file:= the method connected to the view’s @activated(QModelIndex)@ signal. It starts the search for the filename and, if successful, opens the editor view =:

  • #find_filename_in_index:= performs the search of the filename. By default, it uses #find_filename_in_string, but subclasses can override it to change the behaviour=:

  • #find_filename_in_string:= the method used by default by #find_filename_in_index to find the filename.=:

  • #display_file:= opens the file in an editor. By default uses the @general/tool_open_files@ to decide how the editor should be created, but this behaviour can be overridden by subclasses.

If a relative filename is found, it’s considered relative to the directory contained in the #working_dir attribute.

h3. Model

The Model class behaves as a standard @Qt::StandardItemModel@, but provides two methods, insert and insert_lines which make easier adding items. You aren’t forced to use this model, however: if you want to use another class, pass it to the constructor.

Direct Known Subclasses

FilteredOutputWidget

Defined Under Namespace

Classes: ActionList, ListView, Model, TableView, TreeView

Constant Summary collapse

OutputTypeRole =

The role which contains a string with the output type of the index

Qt::UserRole
IsTitleRole =

The role which contains whether an item is or not the title

OutputTypeRole + 1

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from GuiStatesHandler

#change_state, included, #register_action_handler, #remove_action_handler_for, #state

Constructor Details

#initialize(parent = nil, opts = {}) ⇒ OutputWidget

Returns a new instance of OutputWidget.

Parameters:

  • the (Qt::Widget, nil)

    parent widget

  • opts (Hash) (defaults to: {})

    fine-tune the new widget

Options Hash (opts):

  • :view (Qt::AbstractItemView, Symbol) — default: :list

    the view to use for the widget. If it is an instance of a subclass of @Qt::AbstractItemView@, it’ll be used as it is (and the new widget will become a child of the output widget). If it is a symbol, it can be either @:list@, @:tree@ or @:table@. In this case, a new instance respectively of ListView, TreeView or TableView will be created

  • :model (Qt::AbstractItemModel, nil) — default: nil

    the model the output widget should use. If nil, a new instance of Model will be used

  • :use_default_font (Boolean) — default: false

    whether or not the application’s default font should by used for the output widget. By default, the font used is the one the user set in the @general/output_font@ option



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/ruber/output_widget.rb', line 275

def initialize parent = nil, opts = {}

  @ignore_word_wrap_option = false
  @working_dir = nil
  @skip_first_file_in_title = true
  @use_default_font = opts[:use_default_font]
  
  super parent
  initialize_states_handler
  create_widgets(opts[:view] || :list)
  setup_model opts[:model]
  connect @view.selection_model, SIGNAL('selectionChanged(QItemSelection, QItemSelection)'), self, SLOT('selection_changed(QItemSelection, QItemSelection)')
  connect @view, SIGNAL('activated(QModelIndex)'), self, SLOT('maybe_open_file(QModelIndex)')
  @auto_scroll = true
  
  @colors = {}
  @action_list = ActionList.new 
  @action_list << 'copy' << 'copy_selected' << nil << 'clear'
  @actions = {}
  
  connect @view, SIGNAL('context_menu_requested(QPoint)'), self, SLOT('show_menu(QPoint)')
  
  @menu = Qt::Menu.new self
  
  create_standard_actions
  change_state 'no_text', true
  change_state 'no_selection', true
end

Instance Attribute Details

#auto_scrollBoolean

Returns whether auto scrolling should be enabled or not (default: true).

Returns:

  • (Boolean)

    whether auto scrolling should be enabled or not (default: true)



212
213
214
# File 'lib/ruber/output_widget.rb', line 212

def auto_scroll
  @auto_scroll
end

#ignore_word_wrap_optionBoolean

Returns whether or not word wrapping should respect the @general/wrap_output@ option (default: false).

Returns:

  • (Boolean)

    whether or not word wrapping should respect the @general/wrap_output@ option (default: false)



217
218
219
# File 'lib/ruber/output_widget.rb', line 217

def ignore_word_wrap_option
  @ignore_word_wrap_option
end

#modelQt::AbstractItemModel (readonly)

Returns the model used by the Ruber::OutputWidget.

Returns:



251
252
253
# File 'lib/ruber/output_widget.rb', line 251

def model
  @model
end

#skip_first_file_in_titleBoolean

Returns whether or not to #find_filename_in_index should skip the first file name in the title.

Returns:

  • (Boolean)

    whether or not to #find_filename_in_index should skip the first file name in the title



230
231
232
# File 'lib/ruber/output_widget.rb', line 230

def skip_first_file_in_title
  @skip_first_file_in_title
end

#viewQt::AbstractItemView (readonly)

Returns the view used by the OutputWidget.

Returns:

  • (Qt::AbstractItemView)

    the view used by the OutputWidget



256
257
258
# File 'lib/ruber/output_widget.rb', line 256

def view
  @view
end

#working_dirString Also known as: working_directory

Returns the directory used to resolve relative paths when opening a file (default nil).

Returns:

  • (String)

    the directory used to resolve relative paths when opening a file (default nil)



222
223
224
# File 'lib/ruber/output_widget.rb', line 222

def working_dir
  @working_dir
end

Instance Method Details

#clear_outputnil

Removes all the entries from the model

Returns:

  • (nil)


459
460
461
462
# File 'lib/ruber/output_widget.rb', line 459

def clear_output
  @model.remove_rows 0, @model.row_count
  nil
end

#has_title?Boolean

Whether or not the output widget has a title

See #title= for what is meant here by title

Returns:

  • (Boolean)

    whether or not the output widget has a title



426
427
428
# File 'lib/ruber/output_widget.rb', line 426

def has_title?
  @model.index(0,0).data(IsTitleRole).to_bool
end

#load_settingsnil

Loads the settings from the configuration file.

Returns:

  • (nil)


435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/ruber/output_widget.rb', line 435

def load_settings
  cfg = Ruber[:config]
  colors = [:message, :message_good, :message_bad, :output, :output1, :output2, :error, :error1, :error2, :warning, :warning1, :warning2]
  colors.each{|c| set_color_for c, cfg[:output_colors, c]}
  @model.row_count.times do |r|
    @model.column_count.times do |c|
      update_index_color @model.index(r, c)
    end
  end
  @view.font = cfg[:general, :output_font] unless @use_default_font
  unless @ignore_word_wrap_option
    # Not all the views support word wrapping
    begin @view.word_wrap = cfg[:general, :wrap_output] 
    rescue NoMethodError
    end
  end
  nil
end

#pinned_down?Boolean

Returns:

  • (Boolean)


464
465
466
# File 'lib/ruber/output_widget.rb', line 464

def pinned_down?
  @pin_button.checked?
end

#scroll_to(idx) ⇒ nil

Scrolls the view so that the item corresponding the given index is visible

Parameters:

  • idx (Qt::ModelIndex, Integer, nil)

    the item to make visible. If it’s a @Qt::ModelIndex@, it’s the index to make visible. If it is a positive integer. the view will be scrolled so that the first toplevel item in the row idx is visible; if it’s a negative integer, the rows are counted from the end. If nil the first toplevel item of the last row will become visible

Returns:

  • (nil)


329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/ruber/output_widget.rb', line 329

def scroll_to idx
  case idx
  when Numeric
    rc = @model.row_count
    if idx >= rc then idx = rc -1
    elsif idx < 0 and idx.abs < rc then idx = rc + idx
    elsif idx < 0 then idx = 0
    end
    mod_idx = @model.index idx, 0
    @view.scroll_to mod_idx, Qt::AbstractItemView::PositionAtBottom
  when Qt::ModelIndex
    idx = @model.index(@model.row_count - 1, 0) unless idx.valid?
    @view.scroll_to idx, Qt::AbstractItemView::PositionAtBottom
  when nil
    @view.scroll_to @model.index(@model.row_count - 1, 0), 
        Qt::AbstractItemView::PositionAtBottom
  end
  nil
end

#set_color_for(name, color) ⇒ nil

Associates a color with an output type

If a color had already been associated with the given output type, it’ll be overwritten.

This method is useful to define new output types.

Parameters:

  • name (Symbol)

    the name of the output type

  • color (Qt::Color)

    the color to associate with the given output type

Returns:

  • (nil)


314
315
316
317
# File 'lib/ruber/output_widget.rb', line 314

def set_color_for name, color
  @colors[name] = color
  nil
end

#set_output_type(idx, type) ⇒ Symbol?

Changes the output type of a given index

If a color has been associated with that output type, the foreground role and the output type role of that index are updated accordingly.

If no color has been associated with the output type, nothing is done

Parameters:

  • idx (Qt::ModelIndex)

    the index to set the output type for

  • type (Symbol)

    the new output type to associate with the index

Returns:

  • (Symbol, nil)

    type if a color was associated with it and nil otherwise



360
361
362
363
364
365
366
367
# File 'lib/ruber/output_widget.rb', line 360

def set_output_type idx, type
  color = @colors[type]
  if color
    @model.set_data idx, Qt::Variant.from_value(color), Qt::ForegroundRole
    @model.set_data idx, Qt::Variant.new(type.to_s), OutputTypeRole
    type
  end
end

#title=(text) ⇒ nil

Gives a title to the widget.

A title is a toplevel entry at position 0, 0 with output type @:message@ and has the @IsTitleRole@ set to true. Of course, there can be only one item which is a title.

If the item in position 0, 0 is not a title, a new row with title role and the given text is inserted.

If the item in position 0,0 is a title, then its display role is replaced with the given text.

Usually, the title is created when the external program is started and changed later if needed.

Parameters:

  • text (String)

    the text of title

Returns:

  • (nil)


405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/ruber/output_widget.rb', line 405

def title= text
  idx = @model.index 0, 0
  if idx.data(IsTitleRole).to_bool
    @model.set_data idx, Qt::Variant.new(text)
  else
    @model.insert_column 0 if @model.column_count == 0
    @model.insert_row 0
    idx = @model.index 0, 0
    @model.set_data idx, Qt::Variant.new(text)
    @model.set_data idx, Qt::Variant.new(true), IsTitleRole
  end
  set_output_type idx, :message
  nil
end

#with_auto_scrolling(val) { ... } ⇒ Object

Executes a block while temporarily turning autoscrolling on or off

After the block has been executed, autoscrolling returns to the original state.

Parameters:

  • val (Boolean)

    whether to turn autoscrolling on or off

Yields:

  • the block to execute with autoscroll turned on or off

Returns:

  • (Object)

    the value returned by the block



378
379
380
381
382
383
384
# File 'lib/ruber/output_widget.rb', line 378

def with_auto_scrolling val
  old = @auto_scroll
  @auto_scroll = val
  begin yield
  ensure @auto_scroll = old
  end
end