# # $Id: menu.icn,v 1.5 2004/05/15 19:17:29 rparlett Exp $ # # This file is in the public domain. # # Author: Robert Parlett (parlett@dial.pipex.com) # package gui link graphics $include "guih.icn" # # This class encapsulates a drop down menu, or a sub-menu. # # The left, centre and right labels/images of the elements # within it are formatted within the menu automatically. # class Menu : SubMenu( w, # h, # max_label_left_w, # max_label_mid_w, # max_label_right_w, # temp_win, # which_open, # which_highlight, # children # ) method set_parent_component(x) self.MenuComponent.set_parent_component(x) every (!self.children).set_parent_component(x) return end # # Add the given component to the Menu. # # @param c The {Component} to add. # @param i The index to add at; if omitted then the new component # @ is appended to the end. # method add(c, i) if /i then put(self.children, c) else self.children := self.children[1 : i] ||| [c] ||| self.children[i : 0] c.set_parent(self) end method get_which_open() return self.which_open end method resize() cw := self.parent_component.cwin self.h := self.max_label_left_w := self.max_label_right_w := self.max_label_mid_w := 0 dy := BORDER_WIDTH every m := !self.children do { # # Set x, y of label position for sub items. # m.set_label_pos(self.x + BORDER_WIDTH, self.y + dy) # # Set their label size. # m.size_label() # # Increment height; compute maximum label element widths. # self.h +:= m.label_h self.max_label_left_w <:= m.label_left_w self.max_label_right_w <:= m.label_right_w self.max_label_mid_w <:= m.label_mid_w dy +:= m.label_h } # # Height of object is height of total labels within, plus top and bottom border widths. # self.h +:= 2 * BORDER_WIDTH # # Width is total of all maximum label elements, plus left and right border widths. # self.w := self.max_label_left_w + self.max_label_mid_w + self.max_label_right_w + 2 * BORDER_WIDTH # # Compute x, y positions of sub-menus and descend recursively. # every m := !self.children do if m.is_sub_menu() then { m.set_abs_coords(self.x + self.w, m.label_y - BORDER_WIDTH) m.resize() } end # # Deduce which label is under pointer, if any # # @p method which_item() every m := !self.children do { if /m.is_shaded_flag & (m.label_y <= &y < m.label_y + m.label_h) then return m } end method display() if /self.temp_win then { # # Open a temporary area for the menu and copy. # self.temp_win := WOpen("canvas=hidden", "size=" || self.w || "," || self.h) CopyArea(self.parent_component.cwin, self.temp_win, self.x, self.y, self.w, self.h, 0, 0) } cw := self.parent_component.cbwin # # Clear area and draw rectangle around whole # EraseRectangle(cw, self.x, self.y, self.w, self.h) DrawRaisedRectangle(cw, self.x, self.y, self.w, self.h) # # Draw individual items, with rectangle around open sub-item # every c := !self.children do { c.display_label(self.max_label_left_w, self.max_label_mid_w, self.max_label_right_w) if c === \self.which_highlight then DrawRaisedRectangle(cw, c.label_x, c.label_y, self.w - 2 * BORDER_WIDTH, c.label_h) } CopyArea(cw, self.parent_component.cwin, self.x, self.y, self.w, self.h, self.x, self.y) end # # If the presently selected item is a sub-menu, don't unselect it; # just close any non-sub-menu in it by calling recursively. # # @p method drag_off() if \self.which_open then return self.which_open.drag_off() else return self.set_which_highlight() end # # Go to the first non-shaded item. # method cursor_on() local m every m := !children do { if not(m.is_shaded()) then { set_which_highlight(m) return } } end # # Set the open sub-menu to x # # @p method set_which_open(x) if (self.which_open ~=== x) | (self.which_highlight ~=== x) then { # Hide any existing open submenu (\self.which_open).hide() self.which_highlight := self.which_open := x # If it's a open submenu, display it (\self.which_open).display() self.display() } return x end # # Set the present highlight to x, which_open to null # # @p method set_which_highlight(x) # # Do nothing if x presently open # if (self.which_highlight ~=== x) | \self.which_open then { (\self.which_open).hide() self.which_highlight := x self.which_open := &null self.display() } return x end # # Test whether pointer within label area. Top and bottom borders are outside this region. # # @p method in_button_region() return (self.x <= &x < self.x + self.w) & (self.y + BORDER_WIDTH <= &y < self.y + self.h - BORDER_WIDTH) end method handle_key_up(e) local m, last if \self.which_open then { self.which_open.handle_event(e) return } every m := !children do { if \last & m === which_highlight then { set_which_highlight(last) return } m.is_shaded() | (last := m) } set_which_highlight(\last) end method handle_key_down(e) local m, t, first if \self.which_open then { self.which_open.handle_event(e) return } every m := !children do { if not(m.is_shaded()) then { if /self.which_highlight | \t then { set_which_highlight(m) return } /first := m } if m === which_highlight then t := m } set_which_highlight(\first) end method handle_key_right(e) if \self.which_open then self.which_open.handle_event(e) else if /self.which_highlight then self.cursor_on() | parent_component.go_right() else if self.which_highlight.is_sub_menu() then { set_which_open(self.which_highlight) self.which_open.cursor_on() } else parent_component.go_right() end method handle_key_left(e) if \self.which_open then self.which_open.handle_event(e) else if /parent then parent_component.go_left() else parent.set_which_highlight(self) end method handle_key_escape(e) if \self.which_open then self.which_open.handle_event(e) else if /parent then parent_component.make_partial() else parent.set_which_highlight(self) end method handle_key_return(e) if \self.which_open then self.which_open.handle_event(e) else if \self.which_highlight then { if self.which_highlight.is_sub_menu() then { set_which_open(self.which_highlight) self.which_open.cursor_on() } else self.which_highlight.succeed(e) } end method handle_press(e) local m if in_button_region() then { # # Mouse press in region. Open the item where the pointer is # or clear highlight (eg if over a shaded item). # if m := self.which_item() then { if m.is_sub_menu() then self.set_which_open(m) else self.set_which_highlight(m) } else self.set_highlight() } else { if \self.which_open then # # Try open sub-menu. # self.which_open.handle_event(e) else # # Fail completely, pass on event to other objects. # close_all() } end method handle_release(e) if in_button_region() then { # # Mouse released over region # if \self.which_highlight then { # # If item selected and not a sub-menu, return its selected # event. If sub-menu, this will just stay open. # if self.which_highlight.is_sub_menu() then set_which_open(self.which_highlight) else self.which_highlight.succeed(e) } else # # Close completely, don't pass on event. # close_all(1) } else { if \self.which_open then # # Try open sub-menu. # self.which_open.handle_event(e) else # # Fail completely, don't pass on event. # close_all(1) } end method handle_drag(e) if in_button_region() then { # # Drag over region. # if t := self.which_item() then { if t === self.which_highlight then { # # Drag over label of open sub-item. If a menu unselect any # items, except sub menus. # if \self.which_open then t.drag_off() } else { # # Open/highlight the selected item. # if t.is_sub_menu() then self.set_which_open(t) else self.set_which_highlight(t) } } else # # Not over an item (eg shaded item). Clear selection. # self.set_which_highlight() } else { if \self.which_open then # # Pass event on to open sub menu. # self.which_open.handle_event(e) else # # Clear present selection, except sub-menus. # self.drag_off() } end method handle_default(e) local m if \self.which_open then self.which_open.handle_event(e) else { if m := find_key(e) then { if m.is_sub_menu() then { set_which_open(m) m.cursor_on() } else m.succeed(e) } } end method find_key(k) local m every m := !children do { if m.accel === k & not(m.is_shaded()) then return m } end method handle_event(e) if e === (&lpress | &rpress | &mpress) then handle_press(e) else if e === (&lrelease | &rrelease | &mrelease) then handle_release(e) else if e === (-12 | &ldrag | &rdrag | &mdrag) then handle_drag(e) else { case e of { Key_Up: handle_key_up(e) Key_Down: handle_key_down(e) Key_Right: handle_key_right(e) Key_Left: handle_key_left(e) "\e" : handle_key_escape(e) "\r" | " " : handle_key_return(e) default: handle_default(e) } } end # # Close this menu. # # @p method hide() # # Recursively close any open sub-menu. # set_which_open() # # Restore window area. # cw := self.parent_component.cwin EraseRectangle(cw, self.x, self.y, self.w, self.h) CopyArea(self.temp_win, self.parent_component.cwin, 0, 0, self.w, self.h, self.x, self.y) WClose(self.temp_win) self.temp_win := &null end initially(a[]) self.SubMenu.initially() self.children := [] self.set_img_right(img_style("arrow_right")) set_fields(a) end