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.

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

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.

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

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.

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.

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, FindInFiles::ReplaceWidget

Defined Under Namespace

Classes: ActionList, ListView, Model, TableView, TreeView

Constant Summary

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, #initialize_states_handler, #register_action_handler, #remove_action_handler_for, #state

Signal Summary

Constructor Details

- (OutputWidget) initialize(parent = nil, opts = {})

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

- (ActionList) action_list (readonly, private)

The names of the action to use to build the menu

Separators are represented by nil entries. The default is ['copy', 'copy_selected', nil, 'clear'].

Returns:

  • (ActionList)

    the names of the action to use to build the menu

    Separators are represented by nil entries. The default is ['copy', 'copy_selected', nil, 'clear'].



238
239
240
# File 'lib/ruber/output_widget.rb', line 238

def action_list
  @action_list
end

- (Hash{String => KDE::Action}) actions (readonly, private)

The actions to insert in the menu

Each action is inserted using its object_name as key. By default, the hash contains the ‘copy’, ‘copy_selected’ and ‘clear’ actions.

Returns:

  • (Hash{String => KDE::Action})

    the actions to insert in the menu

    Each action is inserted using its object_name as key. By default, the hash contains the ‘copy’, ‘copy_selected’ and ‘clear’ actions.



246
247
248
# File 'lib/ruber/output_widget.rb', line 246

def actions
  @actions
end

- (Boolean) auto_scroll

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

- (Boolean) ignore_word_wrap_option

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

- (Qt::AbstractItemModel) model (readonly)

The model used by the Ruber::OutputWidget

Returns:



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

def model
  @model
end

- (Boolean) skip_first_file_in_title

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

Returns:



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

def skip_first_file_in_title
  @skip_first_file_in_title
end

- (Qt::AbstractItemView) view (readonly)

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

- (String) working_dir Also known as: working_directory

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

- (nil) clear_output

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

Slot Signature:

clear_output()

- (nil) copy (private)

Slot connected to the ‘Copy’ action.

It copies the content of all the items to the clipboard. The text is obtained from the items by calling #text_for_clipboard.

Returns:

  • (nil)


631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
# File 'lib/ruber/output_widget.rb', line 631

def copy
  items = []
  stack = []
  @model.row_count.times do |r|
    @model.column_count.times{|c| stack << @model.index(r, c)}
  end
  until stack.empty?
    it =  stack.shift
    items << it
    (@model.row_count(it)-1).downto(0) do |r|
      (@model.column_count(it)-1).downto(0){|c| stack.unshift it.child(r, c)}
    end
  end
  clp = KDE::Application.clipboard
  clp.text = text_for_clipboard items
  nil
end

Slot Signature:

copy()

- (nil) copy_selected (private)

Slot connected to the ‘Copy Selection’ action.

It copies the content of all the items to the clipboard. The text is obtained from the items by calling #text_for_clipboard.

Returns:

  • (nil)


656
657
658
659
# File 'lib/ruber/output_widget.rb', line 656

def copy_selected
  clp = KDE::Application.clipboard
  clp.text = text_for_clipboard @view.selection_model.selected_indexes
end

Slot Signature:

copy_selected()

- (nil) create_standard_actions (private)

Creates the default actions for the context menu

It also sets up the gui state handlers for them

Returns:

  • (nil)


609
610
611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/ruber/output_widget.rb', line 609

def create_standard_actions
  @actions['copy'] = KDE::Action.new(self){|a| a.text = '&Copy'}
  @actions['copy_selected'] = KDE::Action.new(self){|a| a.text = '&Copy Selection'}
  @actions['clear'] = KDE::Action.new(self){|a| a.text = 'C&lear'}
  register_action_handler @actions['copy'], '!no_text'
  register_action_handler @actions['copy_selected'], ['no_text', 'no_selection'] do |s|
    !(s['no_text'] || s['no_selection'])
  end
  register_action_handler  @actions['clear'], '!no_text'
  connect @actions['copy'], SIGNAL(:triggered), self, SLOT(:copy)
  connect @actions['copy_selected'], SIGNAL(:triggered), self, SLOT(:copy_selected)
  connect @actions['clear'], SIGNAL(:triggered), self, SLOT(:clear_output)
  nil
end

- (nil) create_widgets(view) (private)

Creates the layout and the view

Parameters:

  • view (AbstractItemView, Symbol)

    the view to use. Has the same meaning as the :view option to #initialize

Returns:

  • (nil)


585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# File 'lib/ruber/output_widget.rb', line 585

def create_widgets view
  self.layout = Qt::GridLayout.new(self)
  if view.is_a?(Qt::Widget)
    @view = view
    @view.parent = self
  else @view = self.class.const_get(view.to_s.capitalize + 'View').new self
  end
  @view.selection_mode = Qt::AbstractItemView::ExtendedSelection
  @pin_button = Qt::ToolButton.new self
  @pin_button.tool_tip = i18n("Don't hide the tool widget when clicking on a file name")
  @pin_button.auto_raise = true
  @pin_button.icon = Qt::Icon.new KDE::Global.dirs.find_resource('icon', 'pin.png')
  @pin_button.checkable = true
  layout.add_widget @view, 1, 0
  layout.add_widget @pin_button, 0, 0, 1, -1, Qt::AlignRight | Qt::AlignVCenter
  nil
end

- (nil) do_auto_scroll(parent, start_idx, end_idx) (private)

Note:

all indexes are considered relative to the model associated with the view, not to the model returned by #model. This doesn’ matter for Ruber::OutputWidget itself, but makes a difference in sublclasses where the two models are different (for example, FilteredOutputWidget)

Slot called whenever rows are inserted in the model

If autoscrolling is enabled, it scrolls so that the last row inserted is at the bottom of the widget. It does nothing if autoscrolling is disabled.

If the scrollbar slider is not at the bottom of the scroll bar, autoscrolling isn’t done, regardless of the option. This is because it’s likely that the user moved the slider, which may mean he’s looking at some particular lines of output and he wouldn’t like them to scroll away.

Parameters:

  • parent (Qt::ModelIndex)

    the parent index of the inserted rows

  • start_idx (Qt::ModelIndex)

    the index corresponding to the first inserted row (unused)

  • end_idx (Qt::ModelIndex)

    the index correspodnding to the last inserted row

Returns:

  • (nil)


534
535
536
537
538
539
540
# File 'lib/ruber/output_widget.rb', line 534

def do_auto_scroll parent, start_idx, end_idx
  if @auto_scroll
    scroll_bar = @view.vertical_scroll_bar
    scroll_to @view.model.index(end_idx, 0, parent) if scroll_bar.value == scroll_bar.maximum
  end
  nil
end

Slot Signature:

do_auto_scroll(QModelIndex, int, int)

- (nil) fill_menu (private)

Creates the context menu

The menu is created using the values returned by #action_list and #actions.

Before creating the menu, the #about_to_fill_menu signal is emitted. Connecting to this signal allows to do some last-minute changes to the actions which will be inserted in the menu.

Returns:

  • (nil)


552
553
554
555
556
557
558
559
# File 'lib/ruber/output_widget.rb', line 552

def fill_menu
  emit about_to_fill_menu
  @action_list.each do |a|
    if a then @menu.add_action @actions[a]
    else @menu.add_separator
    end
  end
end

- (Array(String,Integer)?) find_filename_in_index(idx) (private)

Searches in the display role of the given index for a file name

This method is used by #maybe_open_file to find out the name of the file to open (if any) when an item is activated.

The actual search for the file name is done by #find_filename_in_string. If it reports a success, this method makes sure the file actually exists, expanding it relative to #working_dir if it’s not an absolute path. If #working_dir is not set, the current directory will be used. However, you’re advised not to relay on this behaviour and always set the working directory.

If the given index is the title of the widget (see #title=) and #skip_first_file_in_title is true, all the text from the beginning of the title up to the first whitespace or colon is ignored. Since often the first word of the title is the name of the program being run (which may as well be compiled), it doesn’t make sense to attempt to open it. This behaviour allows the user to activate on a title like /usr/bin/ruby /path/to/script.rb and see the file /path/to/script.rb in the editor. Without it, /usr/bin/ruby would be opened instead.

Subclasses can override this method to extend or change its functionality. They have two choices on how to do this. The simplest is useful if they want to alter the string. In this case they can retrieve the text from the index, change it then call super passing the modified string as argument. The other way is to reimplement this method from scratch.

A subclass can also decide to completely disallow opening a file by activating the corresponding item by overriding this method to always return nil.

Parameters:

  • idx (Qt::ModelIndex, String)

    the index or string to search a file name in. The form which takes a string is usually used by subclasses which want to alter the string without reimplementing all the functionality. Note that if idx is a string, there’s no way to know whether it refers to the title or not, so #skip_first_file_in_title is ignored

Returns:

  • (Array(String,Integer), nil)

    if a file name is found (and the corresponding file exists) an array containing the filename and the line number (or 0 if no line number was found). If no suitable file is found, nil is returned



819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
# File 'lib/ruber/output_widget.rb', line 819

def find_filename_in_index idx
  str = if idx.is_a?(String) then idx
  elsif @skip_first_file_in_title and idx.data(IsTitleRole).to_bool
      idx.data.to_string.sub(/^[^\s:]+/, '')
  else idx.data.to_string
  end
  res = find_filename_in_string str
  return unless res
  res = Array res
  res << 0 if res.size == 1
  res[0].sub!(%r{^file://},'')
  unless res[0].match(%r{^.+://})
    path = Pathname.new(res[0])
    begin
      res[0] = path.realpath(@working_dir).to_s
      return nil unless File.file? res[0]
    rescue Errno::ENOENT 
      return nil
    end
  end
  res
end

- (Array(String,Integer), ...) find_filename_in_string(str) (private)

Searches the given string for the first occurrence of a file name

The file name can optionally be followed by a colon and a line number.

What is a file name and what isn’t is a bit arbitrary. Here’s what this method recognizes as a filename:

  • an absolute path not containing spaces and colons starting with ‘/’
  • an absolute path not containing spaces and colons starting with ‘~’ or ‘~user’ (they’re expanded using File.expand_path)
  • a relative path starting with ./ or ../ (either followed by a slash or not)
  • a relative path of the form .filename or .dirname/dir/file
  • absolute URLs with an authority component
  • any string not containing spaces or colons followed by a colon and a line number (in this case, the line number is required)

File names enclosed in quotes or parentheses are recognized.

Returns:

  • (Array(String,Integer), Array(String), nil)

    an array whose first element is the file name and whose second element is the line number (if found) or nil if no file name was found. Note that the file name can be relative



863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
# File 'lib/ruber/output_widget.rb', line 863

def find_filename_in_string str
  #This ensures that file names inside quotes or brackets are found. It's
  #easier replacing quotes and brackets with spaces than to modify the main
  #regexp to take them into account
  str = str.gsub %r|['"`<>\(\)\[\]\{\}]|, ' '
  matches = []
  attempts = [
    %r{(?:^|\s)([\w+.-]+:/{1,2}(?:/[^/:\s]+)+)(?::(\d+))?(?:$|[,.;:\s])}, #URLS
    %r{(?:^|\s)((?:/[^/\s:]+)+)(?::(\d+))?(?:$|[,.;:\s])}, #absolute files
    #absolute files referring to user home directory: ~/xyz or ~user/xyz
    %r{(?:^|\s)(~[\w_-]*(?:/[^/\s:]+)+)(?::(\d+))?(?:$|[,.;:\s])},
    #relative files starting with ./ and ../
    %r{(?:^|\s)(\.{1,2}(?:/[^/\s:]+)+)(?::(\d+))?(?:$|[,.;:\s])},
    #hidden files or directories (.filename or .dir/filename)
    %r{(?:^|\s)(\.[^/\s:]+(?:/[^/\s:]+)*)(?::(\d+))?(?:$|[,.;:\s])},
    #relative files containing, but not ending with a slash
    %r{(?:^|\s)([^/\s:]+/[^\s:]*[^\s/:])(?::(\d+))?(?:$|[,.;:\s])},
    #relative files not containing slashes but ending with the line number
    %r{(?:^|\s)([^/\s:]+):(\d+)(?:$|[,.;:\s])}
  ]
  attempts.each do |a|
    m = str.match a
    matches << [m.begin(0),[$1,$2]] if m
  end
  match = matches.sort_by{|i| i[0]}[0]
  return unless match
  file, line = *match[1]
  file = File.expand_path(file) if file.start_with? '~'
  res = [file]
  res << line.to_i if line
  res
end

- (Boolean) has_title?

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

- (Hash) hints (private)

The hints to pass to MainWindow#display_document

This method determines the hints to use according to the general/tool_open_files option. Derived classes may override this method to provide different hints. The values which can be used are the ones described for MainWindow#editor_for!. Note, however, that the :existing entry won’t be used.

Returns:



773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
# File 'lib/ruber/output_widget.rb', line 773

def hints
  hints = {:new => :new_tab}
  choice = Ruber[:config][:general, :tool_open_files]
  case choice
  when :split_horizontally then hints[:split] = :horizontal
  when :split_vertically then hints[:split] = :vertical
  end
  if hints[:split]
    env = Ruber[:world].active_environment
    view = env.active_editor
    n_views = view ? env.tab(view).views.count : 0
    if n_views == 1 then hints[:new] = :current_tab
    else hints.delete :split
    end
  end
  hints
end

- (nil) load_settings

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

Slot Signature:

load_settings()

- (EditorView?) maybe_open_file(idx) (private)

Attempts to display the file whose name is contained in the given index

Searches for a filename in the DisplayRole of the index using the #find_filename_in_index method. If a filename is found, an editor for it is displayed.

The behaviour of this method (which usually is only called via a signal-slot connection to the views’ activated(QModelindex) signal) changes according to the active keyboard modifiers and to whether the Pinned tool button is on or off:

  • if Ctrl or Shift are pressed and the view allows selection (that is, its selection mode is not NoSelection), then this method does nothing. The reason for this behaviour is that Ctrl and Shift are used to select items, so the user is most likely doing that, not requesting to open a file
  • if the Pinned button is pressed, then the tool widget won’t be closed (but the focus will be moved to the editor)
  • if Meta is pressed, then the file will be opened in a new editor, regardless of whether an editor for that file already exists

If a new editor should be created (either because the Meta key is pressed or because no editor exists for the given file), the hints returned by #hints are used. Unless the #hints method has been overloaded, this means that the general/tool_open_files option is used.

Parameters:

  • idx (Qt::ModelIndex)

    the index which could contain the file name

Returns:

  • (EditorView, nil)

    the editor for the filename contained in the index or nil if no file name was found or if either the Shift or Control modifiers were active

See Also:



749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'lib/ruber/output_widget.rb', line 749

def maybe_open_file idx
  modifiers = Application.keyboard_modifiers
  if @view.selection_mode != Qt::AbstractItemView::NoSelection
    return if Qt::ControlModifier & modifiers != 0 or Qt::ShiftModifier & modifiers != 0
  end
  file, line = find_filename_in_index idx
  return unless file
  line -= 1 unless line == 0
  existing = (Qt::MetaModifier & modifiers) == 0 ? :always : :never
  display_hints = hints.merge(:line =>  line, :existing => existing)
  ed = Ruber[:main_window].display_document file, display_hints
  hide_tool = pinned_down? and Qt::Application.mouse_buttons != Qt::MidButton
  Ruber[:main_window].hide_tool self if hide_tool
  ed.set_focus if ed
  ed
end

Slot Signature:

maybe_open_file(QModelIndex)

- (Boolean) pinned_down?

Returns:

  • (Boolean)


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

def pinned_down?
  @pin_button.checked?
end

- (nil) rows_changed (private)

Slot called whenever rows are added to or removed from the model

Turns the no_text gui state on or off depending on whether the model is empty or not

Returns:

  • (nil)


715
716
717
718
# File 'lib/ruber/output_widget.rb', line 715

def rows_changed
  change_state 'no_text', @model.row_count == 0
  nil
end

Slot Signature:

rows_changed()

- (nil) scroll_to(idx)

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

- (nil) selection_changed(sel, desel) (private)

Slot connected to the view’s selection model’s selectionChanged signal

Turns the no_selection gui state on or off according to whether the selection is empty or not.

Returns:

  • (nil)


703
704
705
706
# File 'lib/ruber/output_widget.rb', line 703

def selection_changed sel, desel
  change_state 'no_selection', !@view.selection_model.has_selection
  nil
end

Slot Signature:

selection_changed(QItemSelection, QItemSelection)

- (nil) set_color_for(name, color)

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

- (Symbol?) set_output_type(idx, type)

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

- (nil) setup_model(mod) (private)

Prepares the model to use

After a call to this method, the model will become a child of the Ruber::OutputWidget

Parameters:

  • mod (Qt::AbstractItemModel, nil)

    the model to use. If nil, a new instance of Model will be used

Returns:

  • (nil)


501
502
503
504
505
506
507
508
509
510
# File 'lib/ruber/output_widget.rb', line 501

def setup_model mod
  @model = mod || Model.new(self)
  @model.insert_column 0 if @model.column_count < 1
  @model.parent = @view
  @view.model = @model
  connect @model, SIGNAL('rowsInserted(QModelIndex, int, int)'), self, SLOT(:rows_changed)
  connect @model, SIGNAL('rowsRemoved(QModelIndex, int, int)'), self, SLOT(:rows_changed)
  connect @model, SIGNAL('rowsInserted(QModelIndex, int, int)'), self, SLOT('do_auto_scroll(QModelIndex, int, int)')
  nil
end

- (ni;) show_menu(pt) (private)

Shows the menu

If the menu hasn’t as yet been created, it creates it.

The menu is shown asynchronously. This means that this method doesn’t wait for the user to choose an action but returns immediately.

Parameters:

  • the (Qt::Point)

    point where the menu should be shown

Returns:

  • (ni;)


572
573
574
575
576
# File 'lib/ruber/output_widget.rb', line 572

def show_menu pt
  fill_menu if @menu.empty?
  @menu.popup pt
  nil
end

Slot Signature:

show_menu(QPoint)

- (String) text_for_clipboard(indexes) (private)

Retrieves the text to copy to the clipboard from the given indexes

The string is created by joining the text of toplevel items on the same row and different columns using tabs as separators. Different rows are separated with newlines. Child items are ignored.

Derived class can override this method (and, if they plan to put child items in the view, they’re advised to do so).

The reason the default behaviour ignores child items is that their meaning (and therefore the way their contents should be inserted into the string) depends very much on the specific content, so there’s no way to have a sensible default behaviour.

Parameters:

  • indexes (Array<Qt::ModelIndex>)

    the indexes to use to create the text

Returns:

  • (String)

    the text which should be put in the clipboard



679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
# File 'lib/ruber/output_widget.rb', line 679

def text_for_clipboard indexes
  indexes = indexes.select{|i| !i.parent.valid?}
  rows = indexes.group_by{|idx| idx.row}
  rows = rows.sort
  text = rows.inject("") do |res, r|
    idxs = r[1].sort_by{|i| i.column}
    idxs.each{|i| res << i.data.to_string << "\t"}
    # The above line gives \t as last character, while a \n is needed
    res[-1] = "\n"
    res
  end
  # The above block leaves a \n at the end of the string which shouldn't be
  # there
  text[0..-2]
end

- (nil) title=(text)

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

- (nil) update_index_color(idx) (private)

Updates the color of an index and its children so that it matches their output types

Parameters:

  • idx (Qt::ModelIndex)

    the index whose foreground color should be updated. Its children will also be updated

Returns:

  • (nil)


476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/ruber/output_widget.rb', line 476

def update_index_color idx
  type = idx.data(OutputTypeRole).to_string.to_sym rescue nil
  color = @colors[type]
  if color
    @model.set_data idx, Qt::Variant.from_value(color), Qt::ForegroundRole
  end
  if @model.has_children idx
    @model.row_count(idx).times do |r|
      @model.column_count(idx).times do |c|
        update_index_color idx.child(r, c)
      end
    end
  end
  nil
end

- (Object) with_auto_scrolling(val) { ... }

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

Signal Details

- about_to_fill_menu

Signal emitted immediately before the menu is created

You should connect to this signal if you want to add actions to the menu at the last possible time. Usually, however, you don’t need it, as actions are usually created in the constructor.