#
# $Id: dialog.icn,v 1.23 2010-12-07 19:09:10 jeffery Exp $
#
# This file is in the public domain.
#
# Author: Robert Parlett (parlett@dial.pipex.com)
#

package gui
link graphics

$include "guih.icn"

#
#
# This is the parent class of a dialog window.
#
class Dialog : Component(
   win,                     # The dialog's window.
   is_open,                 # Flag indicates whether window is open
   owning_dialog,
   child_dialogs,
   focus,                   # Component with current focus
   unique_flag,             # Flag indicates whether in unique processing mode
   re_process_flag,         # Flag indicates whether to distribute last
                            # Icon event during unique mode
   buffer_win,              # Buffer window for double buffering
   min_width,               # Minimum size of window.
   min_height,              #
   click_count,             # Variables controlling multiple clicks
   double_click_delay,
   repeat_delay,            # Repeat event delays
   repeat_rate,
   prev_x,
   prev_y,
   prev_time,
   prev_event,
   is_blocked_flag,
   resize_attrib,
   drag_gesture_x,
   drag_gesture_y,
   curr_drag,
   tried_drag,
   pointer_component,
   pointer_stack,
   invokes,
   all_valid,
   tooltip_ticker,
   tooltip_last_obj,
   tooltip_state,
   tooltip_start_time,
   tooltip_end_time,
   tooltip_start_delay,
   tooltip_end_delay,
   tooltip_continue_time,
   tooltip_continue_delay,
   tooltip_win,
   tooltip_x,
   tooltip_y,
   tooltip_w,
   tooltip_h,
   abs_pos_flag         # set if set_pos() has been called
   )

   method invoke_validate()
      if \self.all_valid then
         return
      if \self.unique_flag then
         self.unique_flag.validate()
      else
         self.validate()
      self.all_valid := 1
   end

   method needs_validate()
      return /self.all_valid
   end

   method is_shaded()
      return \self.is_shaded_flag
   end

   method is_unshaded()
      return /self.is_shaded_flag
   end

   method is_hidden()
      return /self.is_open
   end

   method is_unhidden()
      return \self.is_open
   end

   method block()
      self.is_blocked_flag := 1
      self.resize_attrib := WAttrib(self.win, "resize")
      WAttrib(self.win, "resize=off")
   end

   method unblock()
      self.is_blocked_flag := &null
      WAttrib(self.win, "resize=" || self.resize_attrib)
   end

   #
   # Raise the window, by using Icon's Raise() function.
   #
   method raise()
      Raise(self.win)
   end

   #
   # Returns the number of mouse clicks that have occurred
   # consecutively, with each click in the sequence being less
   # than {double_click_delay} milliseconds apart.  That variable is by default 500
   # milliseconds, but it may be configured with {set_double_click_delay().}
   #
   method get_click_count()
      return self.click_count
   end

   method compute_absolutes()
      /self.x := 0
      /self.y := 0
      self.w := WAttrib(\self.win, "width")
      self.h := WAttrib(\self.win, "height")
   end

   #
   # Change pointer, saving current pointer on a stack for restoration.
   #
   method change_pointer(s)
      push(pointer_stack, WAttrib(self.win, "pointer"))
      WAttrib(self.win, "pointer=" || s)
   end

   #
   # Restore pointer, from the stack of pointers.
   #
   method restore_pointer()
      WAttrib(self.win, "pointer=" || pop(pointer_stack))
   end

   #
   # This is a variation on the conventional modal and modeless
   # methods.  The dialog is opened, input to other windows is not blocked, but
   # the call does not return until the window is closed.
   # @param d   The parent dialog, if specified, is blocked until
   # @ the window is closed.
   #
   method show_child(d)
      self.show()
      dispatcher.add(self)
      if \d then {
         insert(d.child_dialogs, self)
         self.owning_dialog := d
         d.block()
         dispatcher.message_loop(self)
         d.unblock()
      } else
         dispatcher.message_loop(self)
   end

   #
   # Displays the dialog as a modeless dialog.  This
   # means that window events are processed by this dialog
   # and other open dialogs concurrently.  The call to
   # {show_modeless()} opens the dialog and returns immediately.
   #
   # @param d   This optional parameter specifies the parent dialog.
   # @ When a parent dialog is closed, its child dialogs are automatically closed.
   #
   method show_modeless(d)
      self.show()
      dispatcher.add(self)
      if \d then {
         insert(d.child_dialogs, self)
         self.owning_dialog := d
         self.is_blocked_flag := d.is_blocked_flag
      }
   end

   #
   # Displays the dialog as a modal dialog.  In other
   # words, window events to any other open dialogs are blocked
   # until the dialog is closed.  This method doesn't return
   # until the dialog is closed.
   # @param d   The parent dialog.  It will not normally be
   # @ needed.
   #
   method show_modal(d)
      local l
      self.show()
      if \d then {
         insert(d.child_dialogs, self)
         self.owning_dialog := d
      }
      l := dispatcher.list_unblocked()
      every (!l).block()
      dispatcher.add(self)
      dispatcher.message_loop(self)
      every (!l).unblock()
   end

   #
   # Returns the Icon window associated with the dialog.
   #
   method get_win()
      return self.win
   end

   method resize_win(w, h)
      WAttrib(self.win, "size=" || w || "," || h)
      Enqueue(self.win, &resize)
   end

   method init()
      self.parent_dialog := self
      self.cwin := Clone(self.win)
      self.cbwin := Clone(self.buffer_win)
      every (!self.children).init()
   end

   method open_win()
      self.win := (WOpen ! (["inputmask=mc"] ||| self.attribs)) | fail
      self.buffer_win := (WOpen ! (["canvas=hidden"] ||| self.attribs)) | fail
      Raise(self.win)
      return
   end

   method close_win()
      WClose(self.buffer_win)
      return WClose(self.win)
   end

   #
   # Sets the minimum dimensions for a window.  The user will not
   # be able to resize the window below this size.
   #
   method set_min_size(w, h)
      self.min_width := w
      self.min_height := h
      return
   end

   method get_buffer_win()
      return self.buffer_win
   end

   method set_unique(c)
      # Get the display up to date before we go into
      # unique mode...
      invoke_validate()
      /self.unique_flag := c | stop("internal error")
      return
   end

   method clear_unique(x)
      self.re_process_flag := x
      self.unique_flag := &null
      self.all_valid := &null
      return
   end

   #
   # Sets keyboard focus to the given component.  This method
   # should only be invoked after the dialog has been displayed.
   # To give a component the initial keyboard focus,
   # invoke this method from within {init_dialog()}
   #
   method set_focus(c, e)
      if \self.focus === c then
         return

      (\self.focus).lost_focus(e)
      self.focus := c
      self.focus.got_focus(e)

      return
   end

   #
   # Clear the keyboard focus.
   #
   method clear_focus(e)
      (\self.focus).lost_focus(e)
      self.focus := &null
      return
   end

   #
   # Display all components
   #
   # @p
   method display(buffer_flag)
      if \buffer_flag then {
         EraseArea(buffer_win, 0, 0, get_w_reference(), get_h_reference())
         self.Component.display(1)
         CopyArea(buffer_win, win, 0, 0, get_w_reference(), get_h_reference(), 0, 0)
      } else {
         EraseArea(win, 0, 0, get_w_reference(), get_h_reference())
         self.Component.display()
      }
   end

   #
   # This method is invoked just after the dialog is displayed
   # for the first time. It is typically overridden to supply
   # dynamic initial default values for various components.
   #
   method init_dialog()
   end

   #
   # This empty method may be overridden to add components to the
   # dialog.  Alternatively, components may be added in the dialog's
   # {initially} method.
   #
   method component_setup()

   end

   #
   # This empty method may be overridden.  It is invoked just
   # before the dialog window is closed.
   #
   method end_dialog()
   end

   method show()
   local i
      self.component_setup()

      # open a window, die if not successful. one weird special case is fonts.
      # if we don't get the font we want, try a common size of "fixed" before
      # giving up and aborting the whole program.

      if (not self.open_win()) &
         ((not match("font=",attribs[i:=1 to *\attribs])) |
            ((attribs[i] :=
  "font=-misc-fixed-medium-r-semicondensed--13-100-100-100-c-60-iso8859-1") &
             (not self.open_win()))) then
         fatal("couldn't open/show window")

      self.init()
      self.is_open := 1
      self.resize()
      move_dialog()
      self.firstly()
      self.validate()
      self.tooltip_init()
      self.init_dialog()
   end

   method bevel_dispose()
      BevelDisposeAll()
   end

   method dispose()
      self.end_dialog()
      every (!child_dialogs).dispose()
      self.finally()
      self.bevel_dispose()
      self.tooltip_dispose()
      if \ (is_open) then
         self.close_win()
      self.is_open := &null
      dispatcher.del(self)
      delete((\owning_dialog).child_dialogs, self)
      fire(DIALOG_CLOSED_EVENT)
   end

   method consume_same(e)
      while *Pending(self.win) > 0 & Pending(self.win)[1] === e do {
         e := ::Event(self.win)
      }
   end

   method process_event(e)
      if e === 1000 then {
         do_invoke()
      }

      if e === -11 then {
         fire(CLOSE_BUTTON_EVENT, e)
      }

$ifdef _MACOS
      #
      # On Macs, substitute control-click for right-click and shift-click
      # for middle-click.  Should we do this everywhere, for consistency?
      #
      if &control & (e === (&lpress | &ldrag | &lrelease)) then e -:= 2
      else if &shift & (e === (&lpress | &ldrag | &lrelease)) then e -:= 1
$endif

      if e === (&lpress | &rpress | &mpress) then {
         check_click_count(e)
      }

      if not(e === (&ldrag | &rdrag | &mdrag | -12)) then {
         if tooltip_state = 2 then
            hide_tooltip()
         tooltip_state := 5
      }

      if e === &resize then {
         handle_resize(e)
      }

      if \self.unique_flag then {
         process_unique(e)

         if /self.is_open then
            return
      }

      if /self.unique_flag & /self.re_process_flag then {
         if /self.curr_drag then
            check_dnd(e)

         if \self.curr_drag then
            process_dnd(e)
         else
            process_normal(e)

         if /self.is_open then
            return
      }

      self.re_process_flag := &null
   end

   #
   # Normal event processing - not dnd or unique
   # @p
   method process_normal(e)
      local c

      if e === (&lpress | &rpress | &mpress) then {
         if c := self.find_focus() then
            self.set_focus(c, e)
      }

      if &meta & type(e) == "string" then {
         if c := self.find_accel(e) then
            c.handle_accel(e)
      }

      do_handle_event(e)

      if (e === ("\t" | Key_Right | Key_Down)) &
         ( /self.focus | not(self.focus.keeps(e))) then {
         if c := find_next_focus() then
            self.set_focus(c, e)
         }
      else if (e === (Shift_Tab | Key_Left | Key_Up)) &
              ( /self.focus | not(self.focus.keeps(e))) then {
         if c := find_previous_focus() then
            self.set_focus(c, e)
         }

      synch_pointer()
   end

   #
   # Initialize the tooltip state and ticker.
   # @p
   method tooltip_init()
      tooltip_state := 5
      tooltip_ticker.start(200)
   end

   #
   # Cleanup the tooltip settings.
   # @p
   method tooltip_dispose()
      tooltip_ticker.stop()
      if tooltip_state = 2 then
         hide_tooltip()
   end

   #
   # Configure the delay (in ms) before a tooltip appears over a component.
   #
   # The default is 750.
   #
   # @param x   The starting delay in ms.
   #
   method set_tooltip_start_delay(x)
      tooltip_start_delay := x
   end

   #
   # Configure the delay (in ms) before a tooltip is removed.
   #
   # The default is 3000.
   #
   # @param x   The delay in ms.
   #
   method set_tooltip_end_delay(x)
      tooltip_end_delay := x
   end

   #
   # Configure the tooltip continue delay (in ms).  If the cursor moves
   # between two components within this time, the tooltip on the second
   # shows immediately.
   #
   # The default is 1500.
   #
   # @param x   The delay in ms.
   #
   method set_tooltip_continue_delay(x)
      tooltip_continue_delay := x
   end

   #
   # Show the tooltip at the given x,y position.
   #
   # @param obj   maybe which component's tooltip we are showing right now.
   #
   method show_tooltip(x, y, obj)
      local cw, ww, wh, fh
      local lines, multiline, numlines

      ww := WAttrib(self.win, "width")
      wh := WAttrib(self.win, "height")
      if not (cw := Clone(self.cwin, "bg=pale yellow", "font=sans,14", "fg=black")) then
         # try again with default font
         if not (cw := Clone(self.cwin, "bg=pale yellow", "fg=black")) then
            if not (cw := Clone(self.cwin, "bg=pale yellow", "fg=black",
                                "font=-misc-fixed-medium-r-semicondensed--13-100-100-100-c-60-iso8859-1")) then
               fail
      fh := WAttrib(cw, "fheight")

      if find("\n", obj.tooltip) then multiline := 1

      lines := []
      # Find the widest line, and calculate how many lines.
      numlines := 1
      tooltip_w := 0
      obj.tooltip ? {
         while line := tab(find("\n")) do {
            put(lines, line)
            ="\n"
            numlines +:= 1
            tooltip_w <:= TextWidth(line) + 4
            }
         if line := tab(0) & *line > 0 then {
            put(lines, line)
            tooltip_w <:= TextWidth(line) + 4
            }
         }
      tooltip_h := fh*numlines +  6

      tooltip_x := x + 8
      if tooltip_x + tooltip_w > ww then
         tooltip_x := 0 <= ww - tooltip_w

      tooltip_y := y + 8   #self.y + self.h + 3
      if tooltip_y + tooltip_h > wh then
         tooltip_y := 0 <= wh - tooltip_h

      tooltip_win := WOpen("canvas=hidden", "size=" ||
                                tooltip_w || "," || tooltip_h)
      CopyArea(cwin, tooltip_win, tooltip_x, tooltip_y, tooltip_w, tooltip_h,
                     0, 0)
      EraseRectangle(cw, tooltip_x, tooltip_y, tooltip_w, tooltip_h)
      Rectangle(cw, tooltip_x, tooltip_y, tooltip_w, tooltip_h)

      every i := 1 to *lines do {
         center_string(cw, tooltip_x + tooltip_w / 2,
                       tooltip_y + 3 + fh * (i-1) + fh/2, lines[i], obj.accel)
         }
      Uncouple(cw)
   end

   #
   # Hide the tooltip label.
   # @p
   method hide_tooltip()
      CopyArea(self.tooltip_win, cwin, 0, 0, self.tooltip_w, self.tooltip_h, self.tooltip_x, self.tooltip_y)
      WClose(self.tooltip_win)
      self.tooltip_win := &null
   end

   #
   # Tooltip processing.
   # @p
   method process_tooltip()
      local obj, t

      #
      # Don't process ticks whilst unique mode on
      #
      if \self.unique_flag then
         return

      #
      # Search for a component with a tooltip
      #
      if /self.is_blocked_flag then
         obj := find_tooltip()

      t := dispatcher.curr_time_of_day()

      if obj ~=== self.tooltip_last_obj then {
         if /obj then
            self.tooltip_state := if self.tooltip_state = 2 then 3 else 4
         else {
            if self.tooltip_state = 2 then
               hide_tooltip()
            self.tooltip_start_time := t
            if self.tooltip_state = 5 then
               self.tooltip_start_time +:= self.tooltip_start_delay
            self.tooltip_state := 1
            self.tooltip_end_time := self.tooltip_start_time + self.tooltip_end_delay
         }
         self.tooltip_last_obj := obj
      }

      if \obj then
         self.tooltip_continue_time := t + self.tooltip_continue_delay

      if self.tooltip_state = 1 & t >= self.tooltip_start_time then {
         #
         # Change label.
         #
         show_tooltip(WAttrib(self.win, "pointerx"),
                      WAttrib(self.win, "pointery"),
                      obj)
         self.tooltip_state := 2
      }
      if self.tooltip_state = 2 & t >= self.tooltip_end_time then
         self.tooltip_state := 3
      if self.tooltip_state = 3 then {
         hide_tooltip()
         self.tooltip_state := 4
      }
      if self.tooltip_state = 4 & t >= self.tooltip_continue_time then
         self.tooltip_state := 5
   end

   #
   # Find the component which may show a tooltip, if any.
   # @p
   method find_tooltip()
      local c, x1, y1

      x1 := WAttrib(self.win, "pointerx")
      y1 := WAttrib(self.win, "pointery")
      every c := self.generate_components() do {
         if \c.tooltip & c.is_unhidden() & c.in_region(x1, y1) then
            return c
      }
   end

   #
   # Synch the mouse cursor pointer with the component it's over.
   #
   method synch_pointer()
      local ps

      #
      # Do nothing if the dialog isn't open, or is in unique mode.
      #
      if /self.is_open | \self.unique_flag then
         return

      ps := find_pointer_component()

      if (ps ~=== self.pointer_component) |
         ((\ps).pointer ~== WAttrib(self.win, "pointer")) then {
         if \self.pointer_component then
            restore_pointer()
         self.pointer_component := ps
         if \self.pointer_component then
            change_pointer(self.pointer_component.pointer)
      }
   end

   #
   # Find the component with a custom pointer under the given
   # pointer location.
   # @p
   method find_pointer_component()
      local c, x1, y1

      x1 := WAttrib(self.win, "pointerx")
      y1 := WAttrib(self.win, "pointery")
      every c := self.generate_components() do {
         if \c.pointer & c.is_unhidden() & c.in_region(x1, y1) & c.is_unshaded() then
            return c
      }
   end

   #
   # Drag & drop mode processing
   # @p
   method process_dnd(e)
      local c
      if e === (&ldrag | &rdrag | &mdrag) then {
         if self.invoke_drag_event(self.curr_drag) then
            WAttrib(self.win, "pointer=hand2")
         else
            WAttrib(self.win, "pointer=exchange")
      } else if e === (&lrelease | &rrelease | &mrelease) then {
         if c := self.invoke_can_drop(self.curr_drag) then
            self.curr_drag.get_source().invoke_end_drag(self.curr_drag, c)
         self.curr_drag := &null
         self.invoke_drag_reset()
         restore_pointer()
      } else if e === "\e" then {
         self.curr_drag := &null
         self.invoke_drag_reset()
         restore_pointer()
      }
   end

   #
   # Process a unique-mode event
   # @p
   method process_unique(e)
      self.unique_flag.do_handle_event(e)
   end

   #
   # Check whether we should start a dnd (by setting curr_drag)
   # @p
   method check_dnd(e)
      if e === (&ldrag | &rdrag | &mdrag) then {
         # Note the position of the start of a drag
         /self.drag_gesture_x := &x
         /self.drag_gesture_y := &y
         if /self.tried_drag &
            (abs(&x - self.drag_gesture_x) > 3 |
             abs(&y - self.drag_gesture_y) > 3 ) then {
                # Try to begin a drag.
                self.curr_drag := self.invoke_can_drag(e)
                self.tried_drag := 1
                if \self.curr_drag then {
                   change_pointer("exchange")
                }
             }
      } else
         self.tried_drag := self.drag_gesture_x := self.drag_gesture_y := &null
   end

   #
   # Process a resize
   # @p
   method handle_resize(e)
      local nw, nh
      consume_same(e)
      nw := WAttrib(self.win, "width")
      nh := WAttrib(self.win, "height")
      #
      # Don't allow size to fall below minimum.
      #
      if nw <:= \self.min_width then
         WAttrib(self.win, "width=" || nw)

      if nh <:= \self.min_height then
         WAttrib(self.win, "height=" || nh)

      #
      # Resize buffer canvas
      #
      WAttrib(self.buffer_win, "width=" || nw)
      WAttrib(self.buffer_win, "height=" || nh)
      EraseArea(self.win, 0, 0, nw, nh)
      self.resize()
   end

   #
   # Maybe increment the click count
   # @p
   method check_click_count(e)
      local t

      t := dispatcher.curr_time_of_day()
      if e = \prev_event & prev_x = &x & prev_y = &y & (t - prev_time < double_click_delay) then
         click_count +:= 1
      else
         click_count := 1

      prev_event := e
      prev_time := t
      prev_x := &x
      prev_y := &y
   end

   method get_focus_list()
      local l, c
      l := []
      every c := self.generate_components() do {
         if c.accepts_focus() & c.is_unhidden() & c.is_unshaded() then {
            put(l, c)
         }
      }
      return l
   end

   method find_next_focus()
      local l, i
      l := get_focus_list()
      every i := 1 to *l - 1 do {
         if l[i] === self.focus then
            return l[i + 1]
      }
      return l[1]
   end

   method find_previous_focus()
      local l, i
      l := get_focus_list()
      every i := 2 to *l do {
         if l[i] === self.focus then
            return l[i - 1]
      }
      return l[-1]
   end

   #
   # Set the delay in milliseconds between double clicks.  The
   # default is 500 milliseconds
   #
   method set_double_click_delay(i)
      return self.double_click_delay := i
   end

   #
   # Set the delay in milliseconds between an initial repeating event
   # and the start of repeat events.   The
   # default is 500 milliseconds
   #
   method set_repeat_delay(i)
      return self.repeat_delay := i
   end

   #
   # Set the delay in milliseconds between repeating events.
   # The default is 100 milliseconds
   #
   method set_repeat_rate(i)
      return self.repeat_rate := i
   end

   #
   # Override the Component attrib() method to allow "min_size=x,y" attribute.
   #
   method attrib(x[])
   local i, s, wd, ht
      # handle "min_size=..."; remove it from the list before assigning to
      # self.attribs
      i := 1
      while i <= *x do {
         if match("min_size=", x[i]) then {
            # remove x[i]
            s := x[i]
            delete(x, i)
            # get its width and height
            s ? {
               tab(find("=")+1)
               wd := integer(tab(many(&digits))) | fail
               ="," | fail
               ht := integer(tab(many(&digits))) | fail
               }
            # call set_min_size() with it.
            set_min_size(wd, ht)
            }
         else i +:= 1
         }
      return self.attribs |||:= x
   end


   #
   # This method adds an otherwise meaningless event to the window's event
   # queue.  When that event comes to be processed, the given method in the
   # given object is invoked, with the optional parameter.
   #
   # This is useful if you need to take some action from within an event
   # handler method, but that action has to take place after the current
   # event has completed processing.
   #
   # @param obj     The object whose method will be invoked.
   # @param meth    The name of the method.
   # @param param   An optional parameter which will be passed to the
   #                method.
   #
   method invoke_later(obj, meth, param)
      local p
      p := lang::find_method(obj, meth) | stop("Couldn't find method ", meth)
      put(self.invokes, [p, obj, param])
      Enqueue(self.win, 1000)
   end

   #
   # Call a method added via invoke_later().
   # @p
   method do_invoke()
      local e
      if e := pop(self.invokes) then {
         e[1](e[2], e[3])
      }
   end

   method set_one(attr, val)
      case attr of {
         "double_click_delay" : set_double_click_delay(int_val(attr, val))
         "tooltip_start_delay" : self.set_tooltip_start_delay(int_val(attr, val))
         "tooltip_end_delay" : self.set_tooltip_end_delay(int_val(attr, val))
         "tooltip_continue_delay" : self.set_tooltip_continue_delay(int_val(attr, val))
         "repeat_delay" : set_repeat_delay(int_val(attr, val))
         "repeat_rate" : set_repeat_rate(int_val(attr, val))
         "min_size" : set_min_size!int_vals(attr, val)
         #
         # For a dialog, interpret pos and size as Icon attributes, not set_pos
         # and set_size method invocations.
         #
         "pos" | "size" : set_attribs(as_attrib(attr, val))
         default: self.Component.set_one(attr, val)
      }
   end

   #<p>
   #  Compute a default current position that is centered on the
   #  parent window, if any.  <i>This is intended for internal use only!</i>
   #</p>
   method compute_position()
      local win,pp,px,py,pw,ph,dx,dy
      win := parent.win         # just to save some typing later...
      if \self.abs_pos_flag then {   # compute position relative to screen
         pw := WAttrib(win,"displaywidth")
         ph := WAttrib(win,"displayheight")
         self.x := parse_pos(pw, self.x_spec) |
            fatal("invalid x position specification: "||image(self.x_spec))
         self.y := parse_pos(ph, self.y_spec) |
            fatal("invalid y position specification: "||image(self.y_spec))
         }
      else {                    # compute position relative to parent window
         pp := WAttrib(win,"pos")
         pp ? {
            px := integer(1(tab(upto(",")),move(1)))
            py := integer(tab(0))
            }
         pw := WAttrib(win,"width")
         ph := WAttrib(win,"height")
         dx := WAttrib(win, "dx")
         dy := WAttrib(win, "dy")
         self.x := px + (pw-w)/2 - dx
         self.y := py + (ph-w)/2 - dy
         }
   end

   #<p>
   #  Position the dialog, provided the dialog's parent is set.
   #  Note that if <tt>set_pos()</tt> is not called first then the dialog is
   #  centered in the parent window.  <i>Normally this method is only used
   #  internally.</i>
   #</p>
   method move_dialog()
      if /self.parent then return
      compute_position()
      WAttrib(self.win,"pos="||self.x||","||self.y)
   end

   #<p>
   #  Change the expected location of the dialog relative to the
   #  upper-left corner of the display.  Position specifications
   #  can be given in either absolute pixels or as a percentages
   #  of total range.  See comments for <tt>Component.set_pos()</tt>
   #  for details.  Note that if <tt>set_pos()</tt> is not called then the
   #  dialog is centered in the parent window, <i>provided that the
   #  parent has been set, as in <tt>dlg.set_parent(self)</tt></i>.
   #  <[param x -- new X specification relative to display.>
   #  <[param y -- new Y specification relative to display.]>
   #</p>
   method set_pos(x,y)
      self.Component.set_pos(x,y)
      abs_pos_flag := "yes"
   end

   initially(a[])
      self.Component.initially()
      self.child_dialogs := set([])
      self.pointer_stack := []
      self.invokes := []
      self.double_click_delay := 500
      self.repeat_delay := 500
      self.repeat_rate := 100
      self.tooltip_ticker := Ticker()
      self.tooltip_ticker.connect(self, "process_tooltip", TICK_EVENT)
      self.tooltip_start_delay := 750
      self.tooltip_end_delay := 3000
      self.tooltip_continue_delay := 1500
      set_fields(a)
end
