Class SSHMenu::PrefsDialog
In: lib/sshmenu.rb  (Git)
Parent: Object

The SSHMenu::PrefsDialog class implements the main preferences dialog. Current config is read in from the ‘app.model’ object and written back if the user chooses to save a new config.

The dialog user interface comprises a treeview for the menu items and buttons for adding, removing, editing and reordering items.

Two additional tabs provide access to global option settings and the ‘About’ box.

Methods

Constants

ItemColumn = 0

Public Class methods

Constructor. Takes an application object for global constants and a config object for reading/writing the configuration.

[Source]

# File lib/sshmenu.rb, line 1818
    def initialize(app, config)
      @app       = app
      @config    = config
      @button    = { }
      @prev_path = nil
      @next_path = nil
    end

Public Instance methods

Called from make_hosts_pane to add each of the dialog buttons and hook up a signal handler for the ‘clicked’ event.

[Source]

# File lib/sshmenu.rb, line 2071
    def add_button(box, key, label, stock_id, sensitive)
      button =
        if stock_id
          @button[key] = Gtk::Button.new(stock_id)
        else
          @button[key] = Gtk::Button.new(label)
        end

      button.signal_connect('clicked') { send("btn_#{key}_pressed") }
      button.focus_on_click = false
      button.sensitive = sensitive
      box.pack_start(button, false, true, 0)
    end

Called from button handler routines to add items to the treeview. The new ‘item’ is inserted after an existing item identified by the iter ‘prev’.

[Source]

# File lib/sshmenu.rb, line 2089
    def add_item(prev, item)
      parent = nil
      iter =
        if prev.nil?
          @model.append(nil)
        elsif prev[ItemColumn].menu?
          parent = prev
          @model.append(prev)
        else
          parent = prev.parent
          @model.insert_after(parent, prev)
        end
      iter[ItemColumn] = item
      @view.expand_row(parent.path, false) if parent
      return iter
    end

Called independently of the preferences dialog to display a host edit dialog. On a successful edit, the new host will be appended to the main menu.

[Source]

# File lib/sshmenu.rb, line 2388
    def append_host(item)
      result = edit_host(item) or return
      @config.append_host(result)
    end

Handler method for the ‘Add Host’ button. Pops up the Edit Host dialog with all fields blank.

[Source]

# File lib/sshmenu.rb, line 2293
    def btn_add_pressed
      item = mapper.get_class('app.model.hostitem').new
      result = edit_host(item) or return
      iter = add_item(selected_iter, item)
      @view.selection.select_iter(iter)
    end

Handler method for the ‘Copy’ button. Pops up the Edit Host dialog with fields populated from the currently selected item.

[Source]

# File lib/sshmenu.rb, line 2282
    def btn_copy_pressed
      iter = selected_iter or return
      item = get_item(iter).dup
      result = edit_host(item) or return
      iter = add_item(iter, result)
      @view.selection.select_iter(iter)
    end

Handler method for the ‘Delete’ button. Removes the currently selected item from the treeview.

[Source]

# File lib/sshmenu.rb, line 2322
    def btn_del_pressed
      iter = selected_iter
      path = iter.path
      @model.remove(iter)
      @button['up'].sensitive   = false
      @button['down'].sensitive = false
      @button['edit'].sensitive = false
      @button['copy'].sensitive = false
      @button['del'].sensitive  = false
      if @model.get_iter(path).nil?
        if !path.prev!
          if !path.up!
            return
          end
        end
      end
      @view.selection.select_path(path)
    end

Handler method for the ‘Down’ button. Exchanges the current and next items in the treeview.

[Source]

# File lib/sshmenu.rb, line 2212
    def btn_down_pressed
      cur = selected_iter or return
      new_path = nil
      nxt_item = nil
      nxt = @model.get_iter(@next_path)
      if nxt
        nxt_item = nxt[ItemColumn] or return
      end
      path_after = cur.path
      path_after.next!
      if nxt_item and nxt_item.menu?                   # Move down into submenu
        parent = nxt
        nxt = nxt.first_child
        if nxt
          nxt = @model.insert_before(parent, nxt)
        else
          nxt = @model.append(parent)
        end
        @view.expand_row(parent.path, false)
        new_path = cur.path
        move_branch(cur, nxt)
        @model.remove(cur)
        new_path.down!
      elsif @next_path.to_str != path_after.to_str     # Move down out of submenu
        sibling = cur.parent
        parent  = sibling.parent
        nxt = @model.insert_after(parent, sibling)
        new_path = nxt.path
        move_branch(cur, nxt)
        @model.remove(cur)
      else                                             # Swap with preceding peer
        nxt = @model.get_iter(@next_path)
        new_path = @next_path
        @model.swap(cur, nxt)
      end
      sel = @view.selection
      sel.unselect_all
      sel.select_path(new_path)
      @view.scroll_to_cell(new_path, nil, false, 0, 0)
    end

Handler method for the ‘Edit’ button. Pops up the Edit Host dialog for the currently selected host item.

[Source]

# File lib/sshmenu.rb, line 2269
    def btn_edit_pressed
      iter = selected_iter or return
      item = get_item(iter)
      if item.host?
        edit_host(item)
      elsif item.menu?
        edit_menu(item)
      end
    end

Handler method for the ‘Add Submenu’ button. Pops up the Edit Menu Dialog with blank inputs.

[Source]

# File lib/sshmenu.rb, line 2312
    def btn_menu_pressed
      item = mapper.get_class('app.model.menuitem').new
      result = edit_menu(item) or return
      iter = add_item(selected_iter, item)
      @view.selection.select_iter(iter)
    end

Handler method for the ‘Add Separator’ button.

[Source]

# File lib/sshmenu.rb, line 2302
    def btn_sep_pressed
      item_class = mapper.get_class('app.model.item')
      item = item_class.new_from_hash( { 'type' => 'separator' } )
      iter = add_item(selected_iter, item)
      @view.selection.select_iter(iter)
    end

Handler method for the ‘Up’ button. Exchanges the current and previous items in the treeview.

[Source]

# File lib/sshmenu.rb, line 2169
    def btn_up_pressed
      return unless @prev_path
      cur = selected_iter or return
      new_path = nil
      path_before = cur.path
      path_before.prev!
      if cur.path.to_str =~ /:0$/                      # First child in submenu
        parent   = @model.get_iter(@prev_path).parent
        sibling  = @model.get_iter(@prev_path)
        new_iter = @model.insert_before(parent, sibling)
        move_branch(cur, new_iter)
        @model.remove(cur)
        new_path = new_iter.path
      elsif @prev_path.to_str != path_before.to_str    # Move up into submenu
        parent   = @model.get_iter(@prev_path).parent
        @view.expand_row(parent.path, false)
        sibling  = @model.get_iter(@prev_path)
        new_iter = @model.insert_after(parent, sibling)
        move_branch(cur, new_iter)
        @model.remove(cur)
        new_path = new_iter.path
      elsif @model.get_iter(@prev_path)[ItemColumn].menu?  # Move up into empty menu
        parent   = @model.get_iter(@prev_path)
        new_iter = @model.append(parent)
        @view.expand_row(parent.path, false)
        move_branch(cur, new_iter)
        @model.remove(cur)
        new_path = new_iter.path
      else                                             # Swap with following peer
        new_path = cur.path
        new_path.prev! or return
        prv = @model.get_iter(new_path)
        @model.swap(prv, cur)
      end
      sel = @view.selection
      sel.unselect_all
      sel.select_path(new_path)
      @view.scroll_to_cell(new_path, nil, false, 0, 0)
    end

Called from the invoke method to assemble the widgets in the dialog.

[Source]

# File lib/sshmenu.rb, line 1890
    def build_dialog
      dialog = Gtk::Dialog.new(
        "Preferences",
        nil,
        Gtk::Dialog::MODAL | Gtk::Dialog::DESTROY_WITH_PARENT | Gtk::Dialog::NO_SEPARATOR,
        [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
        [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT]
      )
      dialog.window_position = Gtk::Window::POS_MOUSE

      w = @config.get('width')  || 370
      h = @config.get('height') || 360
      dialog.resize(w, h)
      dialog.screen = @app.display

      notebook = Gtk::Notebook.new
      dialog.vbox.pack_start(notebook, true, true, 0)

      notebook.append_page(
        make_hosts_pane,
        Gtk::Label.new("_Hosts", true)
      )

      notebook.append_page(
        make_options_pane(),
        Gtk::Label.new("O_ptions", true)
      )

      if not @app.context_menu_active?
        notebook.append_page(
          @app.make_about_pane(),
          Gtk::Label.new("Abou_t", true)
        )
      end

      dialog.show_all

      return dialog
    end

Pops up the Edit Host dialog (‘app.dialog.host’ mapped to SSHMenu::HostDialog by default).

[Source]

# File lib/sshmenu.rb, line 2371
    def edit_host(item)
      dialog_class = mapper.get_class('app.dialog.host')
      return dialog_class.new(@app, item, @config).invoke
    end

Pops up the Edit Menu dialog (‘app.dialog.menu’ mapped to SSHMenu::MenuDialog by default).

[Source]

# File lib/sshmenu.rb, line 2379
    def edit_menu(item)
      dialog_class = mapper.get_class('app.dialog.menu')
      return dialog_class.new(@app, item).invoke
    end

Helper routine for SSHMenu::PrefsDialog#on_selection_changed. Locates item before and item following selected item and stores these values for calculating button sensitivity.

[Source]

# File lib/sshmenu.rb, line 2129
    def find_prev_next(target)
      @prev_path = nil
      @next_path = nil
      targ_path  = target.path
      found = false
      @model.each do |model, path, iter|
        if found
          if @next_path.nil?  and  !path.descendant?(targ_path)
            @next_path = path
          end
        elsif iter == target
          found = true
        else
          @prev_path = path
        end
      end

      if @next_path.nil? and !target.parent.nil?       # Target was last item
        if targ_path.up!
          @next_path = targ_path.next!
        end
      end
    end

Handler method for drag-and-drop reordering of items in the treeview. Tries to clean up after bad things that happen when items are dropped in unexpected places.

[Source]

# File lib/sshmenu.rb, line 2345
    def fix_dropped_items
      bad_path = nil
      @model.each do |model, path, iter|
        item = iter[ItemColumn]
        if !item.menu? and iter.has_child?
          bad_path = iter.path
        end
      end
      return unless bad_path
      iter = @model.get_iter(bad_path)
      item = iter[ItemColumn]
      child = iter.first_child or return
      target = child[ItemColumn]
      @model.remove(child)
      nxt = @model.insert_after(iter.parent, iter)
      nxt[ItemColumn] = target
      sel = @view.selection
      sel.unselect_all
      sel.select_iter(nxt)
      @view.scroll_to_cell(nxt.path, nil, false, 0, 0)
      return false
    end

Returns the menu item (separator, host or sub menu) identified by the specified iter.

[Source]

# File lib/sshmenu.rb, line 2156
    def get_item(iter)
      return iter[ItemColumn]
    end

Creates the main dialog, waits for it to be dismissed and saves the contents if ‘OK’ was clicked.

[Source]

# File lib/sshmenu.rb, line 1835
    def invoke
      dialog = build_dialog

      if dialog.run != Gtk::Dialog::RESPONSE_ACCEPT
        dialog.destroy
        return
      end

      (w, h) = dialog.size
      @config.set('width',  w)
      @config.set('height', h)

      save_menu_items
      save_options

      dialog.destroy

      @config.save
    end

Called from make_hosts_pane to construct the treeview widget and populate it with host and sub-menu items

[Source]

# File lib/sshmenu.rb, line 2022
    def make_hosts_list
      @model = Gtk::TreeStore.new(SSHMenu::Item)

      @view = Gtk::TreeView.new(@model)
      @view.rules_hint    = false
      @view.search_column = 0

      if GLib.check_binding_version?(0, 19, 0)
        @view.reorderable = true
      end

      renderer = Gtk::CellRendererText.new

      column = Gtk::TreeViewColumn.new("Host", renderer)
      @view.append_column(column)
      column.set_cell_data_func(renderer) do |tvc, renderer, model, iter|
        i = iter[ItemColumn]
        if i.separator?
          renderer.text = "_____________________________"
        else
          renderer.text = i.title
        end
      end

      @view.selection.signal_connect('changed') {
        on_selection_changed(@view.selection.selected)
      }

      path = {}
      @config.each_item() do |parents, i|
        key = i.object_id
        parent = nil
        parent = @model.get_iter(path[parents[-1].object_id]) if parents.length > 0
        row = @model.append(parent)
        row[ItemColumn] = i.dup
        path[key] = row.path
      end

      @view.signal_connect('drag-drop') do
        Gtk.timeout_add(50) { fix_dropped_items }
        false
      end

      return @view.collapse_all
    end

Called from build_dialog to construct the contents of the main tab.

[Source]

# File lib/sshmenu.rb, line 1932
    def make_hosts_pane
      pane = Gtk::HBox.new(false, 12)
      pane.set_border_width(8)

      list_box = Gtk::VBox.new(false, 8)
      pane.pack_start(list_box, true, true, 0)

      sw = Gtk::ScrolledWindow.new
      sw.set_shadow_type(Gtk::SHADOW_ETCHED_IN)
      sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
      list_box.pack_start(sw, true, true, 0)

      hlist = make_hosts_list()
      sw.add(hlist)
      hlist.signal_connect('row_activated') { btn_edit_pressed }

      arrows = Gtk::HBox.new(true, 10)
      list_box.pack_start(arrows, false, true, 0)

      buttons = Gtk::VBox.new(false, 10)
      pane.pack_start(buttons, false, true, 0)

      add_button(arrows,  'up',   '',               Gtk::Stock::GO_UP,   false)
      add_button(arrows,  'down', '',               Gtk::Stock::GO_DOWN, false)
      add_button(buttons, 'add',  '_Add Host',      nil,                 true)
      add_button(buttons, 'sep',  'Add _Separator', nil,                 true)
      add_button(buttons, 'menu', 'Add Sub_menu',   nil,                 true)
      add_button(buttons, 'edit', '_Edit',          nil,                 false)
      add_button(buttons, 'copy', 'Cop_y Host',     nil,                 false)
      add_button(buttons, 'del',  '_Remove',        nil,                 false)

      return pane
    end

Called from build_dialog to construct the contents of the global options tab.

[Source]

# File lib/sshmenu.rb, line 1969
    def make_options_pane
      table = Gtk::Table.new(1, 1, false)
      table.set_border_width(10)
      r = 0

      if @app.can_show_entry?
        @chk_show_entry = Gtk::CheckButton.new(
          'show text _entry next to menu button', true
        )
        @chk_show_entry.active = @config.show_entry?
        table.attach(
          @chk_show_entry, 0, 1, r, r+1, Gtk::EXPAND|Gtk::FILL, Gtk::FILL, 0, 0
        )
        r += 1
      end

      @chk_back_up_config = Gtk::CheckButton.new(
        '_back up config file on save', true
      )
      @chk_back_up_config.active = @config.back_up_config?
      table.attach(
        @chk_back_up_config, 0, 1, r, r+1, Gtk::EXPAND|Gtk::FILL, Gtk::FILL, 0, 0
      )
      r += 1

      @chk_tearoff = Gtk::CheckButton.new('enable tear-off _menus', true)
      @chk_tearoff.active = @config.menus_tearoff?
      table.attach(
        @chk_tearoff, 0, 1, r, r+1, Gtk::EXPAND|Gtk::FILL, Gtk::FILL, 0, 0
      )
      r += 1

      @chk_hide_border = Gtk::CheckButton.new('hide button _border', true)
      @chk_hide_border.active = @config.hide_border?
      table.attach(
        @chk_hide_border, 0, 1, r, r+1, Gtk::EXPAND|Gtk::FILL, Gtk::FILL, 0, 0
      )
      r += 1

      @chk_open_all = Gtk::CheckButton.new(
        'include "Open all _windows" selection', true
      )
      @chk_open_all.active = @config.menus_open_all?
      table.attach(
        @chk_open_all, 0, 1, r, r+1, Gtk::EXPAND|Gtk::FILL, Gtk::FILL, 0, 0
      )

      return table
    end

Accessor for the SSHMenu::ClassMapper singleton object

[Source]

# File lib/sshmenu.rb, line 1828
    def mapper
      ClassMapper.instance
    end

Helper method for moving a TreeStore row along with all its children.

[Source]

# File lib/sshmenu.rb, line 2255
    def move_branch(src, dst)
      dst[ItemColumn] = src[ItemColumn]
      child_src = src.first_child
      while child_src
        child_dst = @model.append(dst)
        move_branch(child_src, child_dst)
        child_src.next! or break
      end
      @view.expand_row(dst.path, false) if @view.row_expanded?(src.path)
    end

Handler for the ‘Changed’ signal from the host list treeview. Enables/disables buttons as appropriate.

[Source]

# File lib/sshmenu.rb, line 2109
    def on_selection_changed(iter)
      return unless iter
      @selected = iter.path
      item = get_item(iter)
      @button['edit'].sensitive = (item.host?  or  item.menu?)
      @button['copy'].sensitive = (item.host?)
      if iter[ItemColumn].menu? and iter.has_child?
        @button['del'].sensitive  = false
      else
        @button['del'].sensitive  = true
      end
      find_prev_next(iter)
      @button['up'].sensitive   = !@prev_path.nil?
      @button['down'].sensitive = !@next_path.nil?
    end

Helper routine which transfers the host items from the treeview widget back to the config object.

[Source]

# File lib/sshmenu.rb, line 1858
    def save_menu_items
      items = []

      @model.each do |model, path, iter|
        i = iter[ItemColumn]
        i.clear_items if i.menu?
        if path.depth > 1
          parent = iter.parent[ItemColumn]
          parent.append_item(i)
        else
          items.push i
        end
      end

      @config.set_items_from_array(items.collect { |i| i.to_h })
    end

Helper routine which transfers global option settings back to the config object.

[Source]

# File lib/sshmenu.rb, line 1878
    def save_options
      if @app.can_show_entry?
        @config.show_entry   = @chk_show_entry.active?
      end
      @config.hide_border    = @chk_hide_border.active?
      @config.menus_tearoff  = @chk_tearoff.active?
      @config.menus_open_all = @chk_open_all.active?
      @config.back_up_config = @chk_back_up_config.active?
    end

Returns an iter for the currently selected item.

[Source]

# File lib/sshmenu.rb, line 2162
    def selected_iter
      return @view.selection.selected
    end

[Validate]