Logo Search packages:      
Sourcecode: widelands version File versions  Download package

ui_panel.cc

/*
 * Copyright (C) 2002-2004, 2006-2007 by the Widelands Development Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "constants.h"
#include "font_handler.h"
#include "graphic.h"
#include "rendertarget.h"
#include "sound/sound_handler.h"
#include "types.h"
#include "ui_panel.h"
#include "wlapplication.h"

namespace UI {

Panel *Panel::_modal = 0;
Panel *Panel::_g_mousegrab = 0;
Panel *Panel::_g_mousein = 0;
uint Panel::s_default_cursor = 0;


/**
 * Initialize a panel, link it into the parent's queue.
 */
Panel::Panel
00041       (Panel * const nparent,
       const int nx, const int ny, const uint nw, const uint nh,
       const std::string & tooltip_text)
      :
_parent(nparent), _fchild(0), _lchild(0), _mousein(0), _focus(0),
_flags(pf_handle_mouse|pf_think|pf_visible), _cache(0), _needdraw(false),
_x(nx), _y(ny), _w(nw), _h(nh),
_lborder(0), _rborder(0), _tborder(0), _bborder(0),
_border_snap_distance(0), _panel_snap_distance(0),
      _tooltip(tooltip_text.size() ? strdup(tooltip_text.c_str()) : 0)
{
      assert(nparent != this);
      if (_parent) {
            _next = _parent->_fchild;
            _prev = 0;
            if (_next)
                  _next->_prev = this;
            else
                  _parent->_lchild = this;
            _parent->_fchild = this;
      } else
            _prev = _next = 0;
      update(0, 0, _w, _h);
}

/**
 * Unlink the panel from the parent's queue
 */
00069 Panel::~Panel()
{
      update(0, 0, get_w(), get_h());

      if (_cache)
            g_gr->free_surface(_cache);

      // Release pointers to this object
      if (_g_mousegrab == this)
            _g_mousegrab = 0;
      if (_g_mousein == this)
            _g_mousein = 0;

      // Free children
      free_children();

      // Unlink
      if (_parent) {
            if (_parent->_mousein == this)
                  _parent->_mousein = 0;
            if (_parent->_focus == this)
                  _parent->_focus = 0;

            if (_prev)
                  _prev->_next = _next;
            else
                  _parent->_fchild = _next;
            if (_next)
                  _next->_prev = _prev;
            else
                  _parent->_lchild = _prev;
      }

      free(_tooltip);
}


/**
Free all of the panel's children.
*/
00109 void Panel::free_children() {while (_fchild) delete _fchild;}


/**
 * Enters the event loop; all events will be handled by this panel.
 * \return the return code passed to end_modal. This return code
 * will be negative when the event loop was quit in an abnormal way
 * (e.g. the user clicked the window's close button or similar).
 */
00118 int Panel::run()
{
      WLApplication *app=WLApplication::get();
      Panel *prevmodal = _modal;
      _modal = this;
      _g_mousegrab = 0; // good ol' paranoia
      app->set_mouse_lock(false); // more paranoia :-)

      Panel *forefather = this;
      while (forefather->_parent)
            forefather = forefather->_parent;

      s_default_cursor = g_gr->get_picture(PicMod_UI,  "pics/cursor.png");

      // Loop
      _running = true;

      // Panel-specific startup code. Note that this might call end_modal()!
      start();
      g_gr->update_fullscreen();
      while (_running)
      {
            static InputCallback icb = {
                  Panel::ui_mousepress,
                  Panel::ui_mouserelease,
                  Panel::ui_mousemove,
                  Panel::ui_key
            };

            app->handle_input(&icb);
            if (app->should_die()) end_modal(dying_code);

            if (_flags & pf_think)
                  think();

            if (g_gr->need_update()) {
                  RenderTarget* rt = g_gr->get_render_target();

                  forefather->do_draw(rt);

                  rt->blit(app->get_mouse_position() - Point(3, 7), s_default_cursor);

                  if (Panel *lowest = _mousein)
                  {
                        while (lowest->_mousein)
                              lowest = lowest->_mousein;
                        if (lowest->tooltip()) draw_tooltip(rt, lowest);
                  }

                  g_gr->refresh();
            }

            if (_flags & pf_child_die)
                  check_child_death();

#ifdef DEBUG
#ifndef __WIN32__
            WLApplication::yield_double_game ();
#endif
#endif
      }
      g_gr->update_fullscreen();
      end();

      // Done
      _modal = prevmodal;

      return _retcode;
}

/**
 * Cause run() to return as soon as possible, with the given return code
 */
00191 void Panel::end_modal(int code)
{
      _running = false;
      _retcode = code;
}

/**
 * Called once before the event loop in run is started
 */
00200 void Panel::start()
{
}

/**
 * Called once after the event loop in run() has ended
 */
00207 void Panel::end()
{
}

/**
 * Resizes the panel.
 */
00214 void Panel::set_size(const uint nw, const uint nh)
{
      uint upw = _w;
      uint uph = _h;
      _w = nw;
      _h = nh;

      if (nw > upw) upw = nw;
      if (nh > uph) uph = nh;

      if (_cache) {
            g_gr->free_surface(_cache);
            _cache = g_gr->create_surface(_w, _h);
      }

      update(0, 0, upw, uph);
}

/**
 * Move the panel. Panel's position is relative to the parent.
 */
00235 void Panel::set_pos(const Point n) {
      bool nd = _needdraw;
      update(0, 0, _w, _h);
      _x = n.x;
      _y = n.y;
      update(0, 0, _w, _h);
      _needdraw = nd;
}

/**
Do nothing
*/
00247 void Panel::move_inside_parent() {}

/**
 * Set the size of the inner area (total area minus border)
 */
00252 void Panel::set_inner_size(uint nw, uint nh)
{
      set_size(nw+_lborder+_rborder, nh+_tborder+_bborder);
}


/**
 * Resize so that we match the size of the inner panel.
 */
00261 void Panel::fit_inner(Panel* inner)
{
      set_inner_size(inner->get_w(), inner->get_h());
      inner->set_pos(Point(0, 0));
}


/**
 * Change the border dimensions.
 * Note that since position and total size aren't changed, so that the size
 * and position of the inner area will change.
 */
00273 void Panel::set_border(uint l, uint r, uint t, uint b)
{
      _lborder = l;
      _rborder = r;
      _tborder = t;
      _bborder = b;
      update(0, 0, get_w(), get_h());
}

/**
 * Make this panel the top-most panel in the parent's Z-order.
 */
00285 void Panel::move_to_top()
{
      if (!_parent)
            return;

      // unlink
      if (_prev)
            _prev->_next = _next;
      else
            _parent->_fchild = _next;
      if (_next)
            _next->_prev = _prev;
      else
            _parent->_lchild = _prev;

      // relink
      _prev = 0;
      _next = _parent->_fchild;
      _parent->_fchild = this;
      if (_next)
            _next->_prev = this;
      else
            _parent->_lchild = this;
}

/**
 * Makes the panel visible or invisible
 */
00313 void Panel::set_visible(bool on)
{

   _flags &= ~pf_visible;
      if (on)
            _flags |= pf_visible;

      update(0, 0, _w, _h);
}

/**
 * [virtual]
 * Redraw the panel. Note that all drawing coordinates are relative to the
 * inner area: you cannot overwrite the panel border in this function.
 */
00328 void Panel::draw(RenderTarget *) {}

/**
 * [virtual]
 * Redraw the panel border.
 */
00334 void Panel::draw_border(RenderTarget *) {}


/*
===============
Panel::draw_overlay [virtual]

Draw overlays that appear over all child panels.
This can be used e.g. for debug information.
===============
*/
void Panel::draw_overlay(RenderTarget &) {}


/**
 * Mark a part of a panel for updating.
 */
00351 void Panel::update(int x, int y, int w, int h)
{
      if
            (x >= static_cast<int>(_w) or x + w <= 0
             or
             y >= static_cast<int>(_h) or y + h <= 0)
            return;

      _needdraw = true;

      if (_parent) {
            _parent->update_inner(x+_x, y+_y, w, h);
      } else {
            if (x < 0) {
                  w += x;
                  x = 0;
            }
            if (x+w > g_gr->get_xres())
                  w = g_gr->get_xres() - x;
            if (w <= 0)
                  return;

            if (y < 0) {
                  h += y;
                  y = 0;
            }
            if (y+h > g_gr->get_yres())
                  h = g_gr->get_yres() - y;
            if (h <= 0)
                  return;

            g_gr->update_rectangle(x, y, w, h);
      }
}

/**
 * Mark a part of a panel for updating.
 */
00389 void Panel::update_inner(int x, int y, int w, int h)
{
      update(x-_lborder, y-_tborder, w, h);
}

/**
 * Enable/Disable the drawing cache.
 * When the drawing cache is enabled, draw() is only called after an update()
 * has been called explicitly. Otherwise, the contents of the panel are copied
 * from a cached Pic.
 */
00400 void Panel::set_cache(bool)
{
/* Deactivated for now
      if (enable)
      {
            if (_cache)
                  return;
            _cache = new Pic;
            _cache->set_size(_w, _h);
            _needdraw = true;
      }
      else
      {
                  delete _cache;
                  _cache = 0;
      }
*/
}

/**
 * Called once per event loop pass, unless set_think(false) has
 * been called. It is intended to be used for animations and game logic.
 * The default implementation calls the children's think function.
 */
00424 void Panel::think()
{
      for (Panel *child = _fchild; child; child = child->_next) {
            if (child->get_think())
                  child->think();
      }
}


/**
 * Get mouse position relative to this panel
*/
00436 Point Panel::get_mouse_position() const throw () {
      return
            (_parent ?
             _parent             ->get_mouse_position()
             :
             WLApplication::get()->get_mouse_position())
            -
            Point(get_x() + get_lborder(), get_y() + get_tborder());
}


/**
 * Set mouse position relative to this panel
*/
00450 void Panel::set_mouse_pos(const Point p) {
      const Point relative_p =
            p + Point(get_x() + get_lborder(), get_y() + get_tborder());
      if (_parent) _parent     ->set_mouse_pos(relative_p);
      else WLApplication::get()->set_mouse_pos(relative_p);
}


/*
 * Center the mouse on this panel.
*/
void Panel::center_mouse() {set_mouse_pos(Point(get_w() / 2, get_h() / 2));}


/**
 * Called whenever the mouse enters or leaves the panel. The inside state
 * is relative to the outer area of a panel. This means that the mouse
 * position received in handle_mousemove may be negative while the mouse is
 * still inside the panel as far as handle_mousein is concerned.
 */
00470 void Panel::handle_mousein(bool) {}

/**
 * Called whenever the user presses or releases a mouse button in the panel.
 * If the panel doesn't process the mouse-click, it is handed to the panel's
 * parent.
 *
 * Returns: true if the mouseclick was processed
 */
00479 bool Panel::handle_mousepress  (const Uint8, int, int) {return false;}
bool Panel::handle_mouserelease(const Uint8, int, int) {return false;}

/**
 * Called when the mouse is moved while inside the panel
 *
 */
00486 bool Panel::handle_mousemove(const Uint8, int, int, int, int) {return _tooltip;}

/**
 * Receive a keypress or keyrelease event.
 * code is one of the KEY_xxx constants, c is the corresponding printable
 * character or 0 for special, unprintable keys.
 *
 * Return true if you processed the key.
*/
00495 bool Panel::handle_key(bool, int, char) {return false;}

/**
 *
 * Enable/Disable mouse handling by this panel
 * Default is enabled. Note that when mouse handling is disabled, child panels
 * don't receive mouse events either.
 *
 * Args: yes  true if the panel should receive mouse events
 */
00505 void Panel::set_handle_mouse(bool yes)
{
      if (yes)
            _flags |= pf_handle_mouse;
      else
            _flags &= ~pf_handle_mouse;
}

/**
 *
 * Enable/Disable mouse grabbing. If a panel grabs the mouse, all mouse
 * related events will be sent directly to that panel.
 * You should only grab the mouse as a response to a mouse event (e.g.
 * clicking a mouse button)
 */
00520 void Panel::grab_mouse(bool grab)
{
      if (grab) {
            _g_mousegrab = this;
      } else {
            assert(!_g_mousegrab || _g_mousegrab == this);
            _g_mousegrab = 0;
      }
}

/**
 * Set if this panel can receive the keyboard focus
*/
00533 void Panel::set_can_focus(bool yes)
{

      if (yes) _flags |= pf_can_focus;
   else {
            _flags &= ~pf_can_focus;

            if (_parent && _parent->_focus == this)
                  _parent->_focus = 0;
      }
}

/**
 * Grab the keyboard focus, if it can
 */
00548 void Panel::focus()
{

   // this assert was deleted, because
   // it happens, that a child can focus, but a parent
   // can't. but focus is called recursivly
   // assert(get_can_focus());

      if (!_parent || this == _modal)
            return;
      if (_parent->_focus == this)
            return;

      _parent->_focus = this;
      _parent->focus();
}

/**
 * Enables/Disables calling think() during the event loop.
 * The default is enabled.
 *
 * Args: yes  true if the panel's think function should be called
 */
00571 void Panel::set_think(bool yes)
{
      if (yes)
            _flags |= pf_think;
      else
            _flags &= ~pf_think;
}

/**
 * Cause this panel to be removed on the next frame.
 * Use this for a panel that needs to destroy itself after a button has
 * been pressed (e.g. non-modal dialogs).
 * Do NOT use this to delete a hierarchy of panels that have been modal.
 */
00585 void Panel::die()
{
   _flags |= pf_die;

      for (Panel *p = _parent; p; p = p->_parent) {
            p->_flags |= pf_child_die;
            if (p == _modal)
                  break;
      }
}

/**
 * Wrapper around Sound_Handler::play_fx() to prevent having to include
 * sound_handler.h in every UI subclass just for playing a 'click'
 */
00600 void Panel::play_click()
{g_sound_handler.play_fx("click", 128, PRIO_ALWAYS_PLAY);}

/** [private]
 *
 * Recursively walk the panel tree, killing panels that are marked for death
 * using die().
 */
00608 void Panel::check_child_death()
{
      Panel *next = _fchild;
      while (next) {
            Panel *p = next;
            next = p->_next;

            if (p->_flags & pf_die)
                  delete p;
            else if (p->_flags & pf_child_die)
                  p->check_child_death();
      }

   _flags &= ~pf_child_die;
}

/**
 * [private]
 * dst is the RenderTarget for the parent Panel.
 * Subset for the border first and draw the border, then subset for the inner area
 * and draw the inner area.
 * Draw child panels after drawing self.
 * Draw tooltip if required.
*/
00632 void Panel::do_draw(RenderTarget* dst)
{
      if (!get_visible())
            return;

      if (!_cache)
      {
            Rect outerrc;
            Point outerofs;

            if (dst->enter_window(Rect(Point(_x, _y), _w, _h), &outerrc, &outerofs)) {
                  draw_border(dst);

                  Rect innerwindow
                        (Point(_lborder, _tborder),
                         _w - (_lborder + _rborder), _h - (_tborder + _bborder));

                  if (dst->enter_window(innerwindow, 0, 0)) {
                        draw(dst);

                        // draw back to front
                        for (Panel *child = _lchild; child; child = child->_prev)
                              child->do_draw(dst);

                        draw_overlay(*dst);
                  }

                  dst->set_window(outerrc, outerofs);
            }
      }
      /*
      else
      {
            // redraw only if explicitly requested
            if (_needdraw) {
                  draw_border(_cache);

                  RenderTarget* inner = _cache->enter_window(_lborder, _tborder,
                         _w-(_lborder+_rborder), _h-(_tborder+_bborder));

                  if (inner) {
                        draw(inner);

                        for (Panel *child = _lchild; child; child = child->_prev)
                              child->do_draw(inner);

                        inner->leave_window();
                  }

                  _needdraw = false;
            }

            // now just blit from the cache
            dst->blit(_x, _y, _cache);
      }
      */
}


00691 inline Panel * Panel::child_at_mouse_cursor(int x, int y, Panel * child) {

      for (; child; child = child->_next) {
            if (!child->get_handle_mouse() || !child->get_visible())
                  continue;
            if
                  (x < child->_x + static_cast<int>(child->_w) and x >= child->_x
                   and
                   y < child->_y + static_cast<int>(child->_h) and y >= child->_y)
                  break;
      }

      if (_mousein && _mousein != child)
            _mousein->do_mousein(false);
      _mousein = child;
      if (child)
            child->do_mousein(true);

      return child;
}

/**
 * Propagate mouseleave events (e.g. for buttons that are inside a different
 * window)
 */
00716 void Panel::do_mousein(bool inside)
{
      if (!inside && _mousein) {
            _mousein->do_mousein(false);
            _mousein = false;
      }
      handle_mousein(inside);
}

/**
 * Propagate mousepresses/-releases/-moves to the appropriate panel.
 *
 * Returns whether the event was processed.
 */
00730 bool Panel::do_mousepress(const Uint8 btn, int x, int y) {
      x -= _lborder;
      y -= _tborder;
      if (_flags & pf_top_on_click) move_to_top();
      if (_g_mousegrab != this)
            if (Panel * child = _fchild) for (;; child = child->_next) {
                  child = child_at_mouse_cursor(x, y, child);
                  if (not child) break;
                  if (child->do_mousepress(btn, x - child->_x, y - child->_y))
                        return true;
            }
      return handle_mousepress(btn, x, y);
}
bool Panel::do_mouserelease(const Uint8 btn, int x, int y) {
      x -= _lborder;
      y -= _tborder;
      if (_g_mousegrab != this)
            if (Panel * child = _fchild) for (;; child = child->_next) {
                  child = child_at_mouse_cursor(x, y, child);
                  if (not child) break;
                  if (child->do_mouserelease(btn, x - child->_x, y - child->_y))
                        return true;
            }
      return handle_mouserelease(btn, x, y);
}
bool Panel::do_mousemove(const Uint8 state, int x, int y, int xdiff, int ydiff)
{
      x -= _lborder;
      y -= _tborder;
      if (_g_mousegrab != this)
            if (Panel * child = _fchild) for (;; child = child->_next) {
                  child = child_at_mouse_cursor(x, y, child);
                  if (not child) break;
                  if
                        (child->do_mousemove
                         (state, x - child->_x, y - child->_y, xdiff, ydiff))
                        return true;
            }
      return handle_mousemove(state, x, y, xdiff, ydiff);
}

/**
 * Pass the key event to the focussed child.
 * If it doesn't process the key, we'll see if we can use the event.
*/
00775 bool Panel::do_key(bool down, int code, char c)
{
      if (_focus) {
            if (_focus->do_key(down, code, c))
                  return true;
      }

      return handle_key(down, code, c);
}

/**
 *
 * Determine which panel is to receive a mouse event.
 *
 * Returns: the panel which receives the mouse event
 */
00791 Panel *Panel::ui_trackmouse(int *x, int *y)
{
      Panel *mousein;
      Panel *rcv = 0;

      if (_g_mousegrab)
            mousein = rcv = _g_mousegrab;
      else
            mousein = _modal;

      *x -= mousein->_x;
      *y -= mousein->_y;
      for (Panel *p = mousein->_parent; p; p = p->_parent) {
            *x -= p->_lborder + p->_x;
            *y -= p->_tborder + p->_y;
      }

      if
            (*x >= 0 and *x < static_cast<int>(mousein->_w)
             and
             *y >= 0 and *y < static_cast<int>(mousein->_h))
            rcv = mousein;
      else
            mousein = 0;

      if (mousein != _g_mousein) {
            if (_g_mousein)
                  _g_mousein->do_mousein(false);
            _g_mousein = mousein;
            if (_g_mousein)
                  _g_mousein->do_mousein(true);
      }

      return rcv;
}

/**
 * [static]
 * Input callback function. Pass the mouseclick event to the currently modal
 * panel.
*/
00832 void Panel::ui_mousepress(const Uint8 button, int x, int y) {
      if (Panel * const p = ui_trackmouse(&x, &y))
            p->do_mousepress(button, x, y);
}
void Panel::ui_mouserelease(const Uint8 button, int x, int y) {
      if (Panel * const p = ui_trackmouse(&x, &y))
            p->do_mouserelease(button, x, y);
}

/**
 * [static]
 *
 * Input callback function. Pass the mousemove event to the currently modal
 * panel.
*/
00847 void Panel::ui_mousemove(const Uint8 state, int x, int y, int xdiff, int ydiff) {
      if (!xdiff && !ydiff)
            return;

      Panel *p;
      uint w, h;
      g_gr->get_picture_size(s_default_cursor, w, h);

      g_gr->update_rectangle(x-xdiff, y-ydiff, w, h);
      g_gr->update_rectangle(x, y, w, h);

      p = ui_trackmouse(&x, &y);
      if (!p)
            return;

      p->do_mousemove(state, x, y, xdiff, ydiff);
}

/**
 * [static]
 *
 * Input callback function. Pass the key event to the currently modal panel
 */
00870 void Panel::ui_key(bool down, int code, char c)
{
      _modal->do_key(down, code, c);
}

/**
 * Set the tooltip for the panel.
 */
00878 void Panel::set_tooltip(const char * const text) {
      if (_tooltip != text) {
            free(_tooltip);
            _tooltip = text ? strdup(text) : 0;
      }
}

/** [private]
 * Draw the tooltip.
 */
00888 void Panel::draw_tooltip(RenderTarget* dst, Panel *lowest)
{
      int tip_width, tip_height;
      g_fh->get_size(UI_FONT_TOOLTIP, lowest->tooltip(), &tip_width, &tip_height, 0);
      tip_width += 4;
      tip_height += 4;
      assert(0 <= tip_width);
      assert(0 <= tip_height);
      const WLApplication & wlapplication = *WLApplication::get();
      Rect r
            (wlapplication.get_mouse_position() + Point(2, 32),
             tip_width, tip_height);
      const Point tooltip_bottom_left = r.bottom_left();
      const Point screen_botton_left(g_gr->get_xres(), g_gr->get_yres());
      if (screen_botton_left.x < tooltip_bottom_left.x) r.x -=  4 + r.w;
      if (screen_botton_left.y < tooltip_bottom_left.y) r.y -= 35 + r.h;

      dst->fill_rect(r, RGBColor(230, 200, 50));
      dst->draw_rect(r, RGBColor(0, 0, 0));
      g_fh->draw_string
            (*dst,
             UI_FONT_TOOLTIP,
             UI_FONT_TOOLTIP_CLR,
             r + Point(2, 2),
             lowest->tooltip(),
             Align_Left, -1,
             Widget_Cache_None,
             0, -1, false);
}
};

Generated by  Doxygen 1.6.0   Back to index