# module TkMenuSpec

frozen_string_literal: false

tk/menuspec.rb

    Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)

based on tkmenubar.rb :

    Copyright (C) 1998 maeda shugo. All rights reserved.
    This file can be distributed under the terms of the Ruby.

The format of the menu_spec is:

    [ menubutton_info, menubutton_info, ... ]

The format of the menubutton_info is:

    [ menubutton_info, entry_info, entry_info, ... ]

And each format of *_info is:

    [
      [text, underline, configs], # menu button/entry (*1)
      [label, command, underline, accelerator, configs],   # command entry
      [label, TkVar_obj, underline, accelerator, configs], # checkbutton entry
      [label, [TkVar_obj, value],
                         underline, accelerator, configs], # radiobutton entry
      [label, [[...menu_info...], [...menu_info...], ...],
                         underline, accelerator, configs], # cascade entry (*2)
      '---', # separator
      ...
    ]

A menu_info is an array of menu entries:

    [ entry_info, entry_info, ... ]

underline, accelerator, and configs are optional parameters. Hashes are OK instead of Arrays. Then the entry type ('command', 'checkbutton', 'radiobutton' or 'cascade') is given by 'type' key (e.g. :type=>'cascade'). When type is 'cascade', an array of menu_info is acceptable for 'menu' key (then, create sub-menu).

If the value of underline is true instead of an integer, check whether the text/label string contains a '&' character. When includes, the first '&' is removed and its following character is converted the corresponding 'underline' option (first '&' is removed). Else if the value of underline is a String or a Regexp, use the result of label.index(underline) as the index of underline (don't remove matched substring).

NOTE: (*1)

    If you want to make special menus (*.help for UNIX, *.system for Win,
    and *.apple for Mac), append 'menu_name'=>name (name is 'help' for UNIX,
    'system' for Win, and 'apple' for Mac) option to the configs hash of
    menu button/entry information.

NOTE: (*2)

    If you want to configure a cascade menu, add :menu_config=>{...configs..}
    to the configs of the cascade entry.

### Constants

MENUSPEC_OPTKEYS

### Private Instance Methods

_create_menu_for_menubar(parent) click to toggle source

    # File lib/tk/menuspec.rb, line 224
    def _create_menu_for_menubar(parent)
      #unless (mbar = parent.menu).kind_of?(TkMenu)
      # --> use current TkMenu class
      mbar = parent.menu
      unless mbar.kind_of?(Tk::Menu) || mbar.kind_of?(TkMenu)
        #mbar = Tk::Menu.new(parent, :tearoff=>false)
        mbar = TkMenu.new(parent, :tearoff=>false)
        parent.menu(mbar)
      end
      mbar
    end

_create_menubar(parent, menu_spec, tearoff = false, opts = nil) click to toggle source

    # File lib/tk/menuspec.rb, line 433
    def _create_menubar(parent, menu_spec, tearoff = false, opts = nil)
      if tearoff.kind_of?(Hash)
        opts = tearoff
        tearoff = false
      end
      tearoff = false unless tearoff # nil --> false
      menu_spec.each{|menu_info|
        _create_menubutton(parent, menu_info, tearoff, opts)
      }
      parent
    end

_create_menubutton(parent, menu_info, tearoff=false, default_opts = {}) click to toggle source

    # File lib/tk/menuspec.rb, line 237
    def _create_menubutton(parent, menu_info, tearoff=false, default_opts = {})
      btn_info = menu_info[0]

      if tearoff.kind_of?(Hash)
        default_opts = tearoff
        tearoff = false
      end

      if default_opts.kind_of?(Hash)
        default_opts = _symbolkey2str(default_opts)

        if default_opts.has_key?('layout_proc')
          layout_proc = default_opts.delete('layout_proc')
        end

        _vertical_mbar_bind_proc = proc{|m, dir|
          Tk::Menu::TkInternalFunction.next_menu(m, dir) rescue nil
          # ignore error when the internal function doesn't exist
        }

        case layout_proc
        when :vertical, 'vertical', :vertical_left, 'vertical_left'
          layout_proc = proc{|_parent, _mbtn|
            _mbtn.direction :right
            _mbtn.pack(:side=>:top, :fill=>:x)

            menu = _mbtn.menu
            menu.bind('Tab', _vertical_mbar_bind_proc, :widget, 'forward')
            menu.bind('Alt-Tab', _vertical_mbar_bind_proc, :widget, 'backward')
          }
        when :vertical_right, 'vertical_right'
          layout_proc = proc{|_parent, _mbtn|
            _mbtn.direction :left
            _mbtn.pack(:side=>:top, :fill=>:x)

            menu = _mbtn.menu
            menu.bind('Tab', _vertical_mbar_bind_proc, :widget, 'forward')
            menu.bind('Alt-Tab', _vertical_mbar_bind_proc, :widget, 'backward')
          }
        when :horizontal, 'horizontal'
          layout_proc = proc{|_parent, _mbtn| _mbtn.pack(:side=>:left)}
        else
          # do nothing
        end
      end

      keys = (default_opts)? default_opts.dup: {}

      tearoff = keys.delete('tearoff') if keys.key?('tearoff')
      tearoff = false unless tearoff # nil --> false

      if _use_menubar?(parent) && ! layout_proc
        # menubar by menu entries
        mbar = _create_menu_for_menubar(parent)

        menu_name = nil

        if btn_info.kind_of?(Hash)
          keys.update(_symbolkey2str(btn_info))
          menu_name = keys.delete('menu_name')
          keys['label'] = keys.delete('text') || ''

          case keys['underline']
          when String, Regexp
            if idx = keys['label'].index(keys['underline'])
              keys['underline'] = idx
            else
              keys['underline'] = -1
            end
          when true
            if idx = keys['label'].index('&')
              keys['label'] = keys['label'].dup
              keys['label'][idx] = ''
              keys['underline'] = idx
            else
              keys['underline'] = -1
            end
          end

        elsif btn_info.kind_of?(Array)
          keys['label'] = btn_info[0] if btn_info[0]

          case btn_info[1]
          when Integer
            keys['underline'] = btn_info[1]
          when String, Regexp
            if idx = keys['label'].index(btn_info[1])
              keys['underline'] = idx
            else
              keys['underline'] = -1
            end
          when true
            if idx = keys['label'].index('&')
              keys['label'] = keys['label'].dup
              keys['label'][idx] = ''
              keys['underline'] = idx
            else
              keys['underline'] = -1
            end
          end

          if btn_info[2]&&btn_info[2].kind_of?(Hash)
            keys.update(_symbolkey2str(btn_info[2]))
            menu_name = keys.delete('menu_name')
          end

        else
          keys = {:label=>btn_info}
        end

        menu = _create_menu(mbar, menu_info[1..-1], menu_name,
                            tearoff, default_opts)
        menu.tearoff(tearoff)

        keys['menu'] = menu
        mbar.add('cascade', keys)

        [mbar, menu]

      else
        # menubar by menubuttons
        #mbtn = Tk::Menubutton.new(parent)
        # --> use current TkMenubutton class
        mbtn = TkMenubutton.new(parent)

        menu_name = nil

        if btn_info.kind_of?(Hash)
          keys.update(_symbolkey2str(btn_info))
          menu_name = keys.delete('menu_name')
          keys['text'] = keys.delete('label') || ''
          case keys['underline']
          when String, Regexp
            if idx = keys['text'].index(keys['underline'])
              keys['underline'] = idx
            else
              keys['underline'] = -1
            end
          when true
            if idx = keys['text'].index('&')
              keys['text'] = keys['text'].dup
              keys['text'][idx] = ''
              keys['underline'] = idx
            else
              keys['underline'] = -1
            end
          end
          mbtn.configure(keys)

        elsif btn_info.kind_of?(Array)
          case btn_info[1]
          when String, Regexp
            if btn_info[0] && (idx = btn_info[0].index(btn_info[1]))
              btn_info[1] = idx
            else
              btn_info[1] = -1
            end
          when true
            if btn_info[0] && (idx = btn_info[0].index('&'))
              btn_info[0] = btn_info[0].dup
              btn_info[0][idx] = ''
              btn_info[1] = idx
            else
              btn_info[1] = -1
            end
          end
          mbtn.configure('text', btn_info[0]) if btn_info[0]
          mbtn.configure('underline', btn_info[1]) if btn_info[1]
          # mbtn.configure('accelerator', btn_info[2]) if btn_info[2]
          if btn_info[2]&&btn_info[2].kind_of?(Hash)
            keys.update(_symbolkey2str(btn_info[2]))
            menu_name = keys.delete('menu_name')
            mbtn.configure(keys)
          end

        else
          mbtn.configure('text', btn_info)
        end

        menu = _create_menu(mbtn, menu_info[1..-1], menu_name,
                            tearoff, default_opts)
        mbtn.menu(menu)

        if layout_proc.kind_of?(Proc) || layout_proc.kind_of?(Method)
          # e.g. make a vertical menubar
          #  :layout_proc => proc{|parent, btn| btn.pack(:side=>:top, :fill=>:x)}
          layout_proc.call(parent, mbtn)
        else
          mbtn.pack('side' => 'left')
        end

        [mbtn, menu]
      end
    end

_get_cascade_menus(menu) click to toggle source

    # File lib/tk/menuspec.rb, line 446
    def _get_cascade_menus(menu)
      menus = []
      (0..(menu.index('last'))).each{|idx|
        if menu.menutype(idx) == 'cascade'
          submenu = menu.entrycget(idx, 'menu')
          menus << [submenu, _get_cascade_menus(submenu)]
        end
      }
      menus
    end

_use_menubar?(parent) click to toggle source

    # File lib/tk/menuspec.rb, line 212
    def _use_menubar?(parent)
      use_menubar = false
      if parent.kind_of?(Tk::Root) || parent.kind_of?(Tk::Toplevel)
        true
      elsif parent.current_configinfo.has_key?('menu')
        true
      else
        false
      end
    end