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

PopUpWidget.cpp

/* ScummVM - Scumm Interpreter
 * Copyright (C) 2002-2005 The ScummVM project
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $Header: /cvsroot/scummvm/scummvm/gui/PopUpWidget.cpp,v 1.40.2.1 2005/10/18 02:11:18 sev Exp $
 */

#include "common/stdafx.h"
#include "common/system.h"
#include "gui/dialog.h"
#include "gui/newgui.h"
#include "gui/PopUpWidget.h"
#include "base/engine.h"

namespace GUI {

//
// PopUpDialog
//

class PopUpDialog : public Dialog {
protected:
      PopUpWidget *_popUpBoss;
      int               _clickX, _clickY;
      byte        *_buffer;
      int               _selection;
      uint32            _openTime;
      bool        _twoColumns;
      int               _entriesPerColumn;
public:
      PopUpDialog(PopUpWidget *boss, int clickX, int clickY, WidgetSize ws = kDefaultWidgetSize);

      void drawDialog();

      void handleMouseUp(int x, int y, int button, int clickCount);
      void handleMouseWheel(int x, int y, int direction);   // Scroll through entries with scroll wheel
      void handleMouseMoved(int x, int y, int button);      // Redraw selections depending on mouse position
      void handleKeyDown(uint16 ascii, int keycode, int modifiers);     // Scroll through entries with arrow keys etc.

protected:
      void drawMenuEntry(int entry, bool hilite);

      int findItem(int x, int y) const;
      void setSelection(int item);
      bool isMouseDown();

      void moveUp();
      void moveDown();
};

PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY, WidgetSize ws)
      : Dialog(0, 0, 16, 16),
      _popUpBoss(boss) {

      // Copy the selection index
      _selection = _popUpBoss->_selectedItem;

      // Calculate real popup dimensions
      _x = _popUpBoss->getAbsX() + _popUpBoss->_labelWidth;
      _y = _popUpBoss->getAbsY() - _popUpBoss->_selectedItem * kLineHeight;
      _h = _popUpBoss->_entries.size() * kLineHeight + 2;
      _w = _popUpBoss->_w - kLineHeight + 2 - _popUpBoss->_labelWidth;

      // Perform clipping / switch to scrolling mode if we don't fit on the screen
      // FIXME - OSystem should send out notification messages when the screen
      // resolution changes... we could generalize CommandReceiver and CommandSender.

      const int screenH = g_system->getOverlayHeight();

      // HACK: For now, we do not do scrolling. Instead, we draw the dialog
      // in two columns if it's too tall.

      if (_h >= screenH) {
            const int screenW = g_system->getOverlayWidth();

            _twoColumns = true;
            _entriesPerColumn = _popUpBoss->_entries.size() / 2;

            if (_popUpBoss->_entries.size() & 1)
                  _entriesPerColumn++;

            _h = _entriesPerColumn * kLineHeight + 2;
            _w = 0;

            for (uint i = 0; i < _popUpBoss->_entries.size(); i++) {
                  int width = g_gui.getStringWidth(_popUpBoss->_entries[i].name);

                  if (width > _w)
                        _w = width;
            }

            _w = 2 * _w + 10;

            if (!(_w & 1))
                  _w++;

            if (_popUpBoss->_selectedItem >= _entriesPerColumn) {
                  _x -= _w / 2;
                  _y = _popUpBoss->getAbsY() - (_popUpBoss->_selectedItem - _entriesPerColumn) * kLineHeight;
            }

            if (_w >= screenW)
                  _w = screenW - 1;
            if (_x < 0)
                  _x = 0;
            if (_x + _w >= screenW)
                  _x = screenW - 1 - _w;
      } else
            _twoColumns = false;

      if (_h >= screenH)
            _h = screenH - 1;
      if (_y < 0)
            _y = 0;
      else if (_y + _h >= screenH)
            _y = screenH - 1 - _h;

      // TODO - implement scrolling if we had to move the menu, or if there are too many entries

      // Remember original mouse position
      _clickX = clickX - _x;
      _clickY = clickY - _y;

      // Time the popup was opened
      _openTime = getMillis();
}

void PopUpDialog::drawDialog() {
      // Draw the menu border
      g_gui.hLine(_x, _y, _x+_w - 1, g_gui._color);
      g_gui.hLine(_x, _y + _h - 1, _x + _w - 1, g_gui._shadowcolor);
      g_gui.vLine(_x, _y, _y+_h - 1, g_gui._color);
      g_gui.vLine(_x + _w - 1, _y, _y + _h - 1, g_gui._shadowcolor);

      if (_twoColumns)
            g_gui.vLine(_x + _w / 2, _y, _y + _h - 2, g_gui._color);

      // Draw the entries
      int count = _popUpBoss->_entries.size();
      for (int i = 0; i < count; i++) {
            drawMenuEntry(i, i == _selection);
      }

      // The last entry may be empty. Fill it with black.
      if (_twoColumns && (count & 1)) {
            g_gui.fillRect(_x + 1 + _w / 2, _y + 1 + kLineHeight * (_entriesPerColumn - 1), _w / 2 - 1, kLineHeight, g_gui._bgcolor);
      }

      g_gui.addDirtyRect(_x, _y, _w, _h);
}

void PopUpDialog::handleMouseUp(int x, int y, int button, int clickCount) {
      // Mouse was released. If it wasn't moved much since the original mouse down,
      // let the popup stay open. If it did move, assume the user made his selection.
      int dist = (_clickX - x) * (_clickX - x) + (_clickY - y) * (_clickY - y);
      if (dist > 3 * 3 || getMillis() - _openTime > 300) {
            setResult(_selection);
            close();
      }
      _clickX = -1;
      _clickY = -1;
      _openTime = (uint32)-1;
}

void PopUpDialog::handleMouseWheel(int x, int y, int direction) {
      if (direction < 0)
            moveUp();
      else if (direction > 0)
            moveDown();
}

void PopUpDialog::handleMouseMoved(int x, int y, int button) {
      // Compute over which item the mouse is...
      int item = findItem(x, y);

      if (item >= 0 && _popUpBoss->_entries[item].name.size() == 0)
            item = -1;

      if (item == -1 && !isMouseDown())
            return;

      // ...and update the selection accordingly
      setSelection(item);
}

void PopUpDialog::handleKeyDown(uint16 ascii, int keycode, int modifiers) {
      if (keycode == 27) {    // escape
            close();
            return;
      }

      if (isMouseDown())
            return;

      switch (keycode) {
      case '\n':        // enter/return
      case '\r':
            setResult(_selection);
            close();
            break;
      case 256+17:      // up arrow
            moveUp();
            break;
      case 256+18:      // down arrow
            moveDown();
            break;
      case 256+22:      // home
            setSelection(0);
            break;
      case 256+23:      // end
            setSelection(_popUpBoss->_entries.size()-1);
            break;
      }
}

int PopUpDialog::findItem(int x, int y) const {
      if (x >= 0 && x < _w && y >= 0 && y < _h) {
            if (_twoColumns) {
                  uint entry = (y - 2) / kLineHeight;
                  if (x > _w / 2) {
                        entry += _entriesPerColumn;

                        if (entry >= _popUpBoss->_entries.size())
                              return -1;
                  }
                  return entry;
            }
            return (y - 2) / kLineHeight;
      }
      return -1;
}

void PopUpDialog::setSelection(int item) {
      if (item != _selection) {
            // Undraw old selection
            if (_selection >= 0)
                  drawMenuEntry(_selection, false);

            // Change selection
            _selection = item;

            // Draw new selection
            if (item >= 0)
                  drawMenuEntry(item, true);
      }
}

bool PopUpDialog::isMouseDown() {
      // TODO/FIXME - need a way to determine whether any mouse buttons are pressed or not.
      // Sure, we could just count mouse button up/down events, but that is cumbersome and
      // error prone. Would be much nicer to add an API to OSystem for this...

      return false;
}

void PopUpDialog::moveUp() {
      if (_selection < 0) {
            setSelection(_popUpBoss->_entries.size() - 1);
      } else if (_selection > 0) {
            int item = _selection;
            do {
                  item--;
            } while (item >= 0 && _popUpBoss->_entries[item].name.size() == 0);
            if (item >= 0)
                  setSelection(item);
      }
}

void PopUpDialog::moveDown() {
      int lastItem = _popUpBoss->_entries.size() - 1;

      if (_selection < 0) {
            setSelection(0);
      } else if (_selection < lastItem) {
            int item = _selection;
            do {
                  item++;
            } while (item <= lastItem && _popUpBoss->_entries[item].name.size() == 0);
            if (item <= lastItem)
                  setSelection(item);
      }
}

void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
      // Draw one entry of the popup menu, including selection
      assert(entry >= 0);
      int x, y, w;

      if (_twoColumns) {
            int n = _popUpBoss->_entries.size() / 2;

            if (_popUpBoss->_entries.size() & 1)
                  n++;

            if (entry >= n) {
                  x = _x + 1 + _w / 2;
                  y = _y + 1 + kLineHeight * (entry - n);
            } else {
                  x = _x + 1;
                  y = _y + 1 + kLineHeight * entry;
            }

            w = _w / 2 - 1;
      } else {
            x = _x + 1;
            y = _y + 1 + kLineHeight * entry;
            w = _w - 2;
      }

      Common::String &name = _popUpBoss->_entries[entry].name;

      g_gui.fillRect(x, y, w, kLineHeight, hilite ? g_gui._textcolorhi : g_gui._bgcolor);
      if (name.size() == 0) {
            // Draw a separator
            g_gui.hLine(x - 1, y + kLineHeight / 2, x + w, g_gui._shadowcolor);
            g_gui.hLine(x, y + 1 + kLineHeight / 2, x + w, g_gui._color);
      } else {
            g_gui.drawString(name, x + 1, y + 2, w - 2, hilite ? g_gui._bgcolor : g_gui._textcolor);
      }
      g_gui.addDirtyRect(x, y, w, kLineHeight);
}


#pragma mark -

//
// PopUpWidget
//

PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const String &label, uint labelWidth, WidgetSize ws)
      : Widget(boss, x, y - 1, w, h + 2), CommandSender(boss), _ws(ws), _label(label), _labelWidth(labelWidth) {
      _flags = WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS;
      _type = kPopUpWidget;

      _selectedItem = -1;

      if (!_label.isEmpty() && _labelWidth == 0)
            _labelWidth = g_gui.getStringWidth(_label);
}

void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) {

      if (isEnabled()) {
            PopUpDialog popupDialog(this, x + getAbsX(), y + getAbsY(), _ws);
            int newSel = popupDialog.runModal();
            if (newSel != -1 && _selectedItem != newSel) {
                  _selectedItem = newSel;
                  sendCommand(kPopUpItemSelectedCmd, _entries[_selectedItem].tag);
            }
      }
}

void PopUpWidget::appendEntry(const String &entry, uint32 tag) {
      Entry e;
      e.name = entry;
      e.tag = tag;
      _entries.push_back(e);
}

void PopUpWidget::clearEntries() {
      _entries.clear();
      _selectedItem = -1;
}

00378 void PopUpWidget::setSelected(int item) {
      // FIXME
      if (item != _selectedItem) {
            if (item >= 0 && item < (int)_entries.size()) {
                  _selectedItem = item;
            } else {
                  _selectedItem = -1;
            }
      }
}

00389 void PopUpWidget::setSelectedTag(uint32 tag) {
      uint item;
      for (item = 0; item < _entries.size(); ++item) {
            if (_entries[item].tag == tag) {
                  setSelected(item);
                  return;
            }
      }
}

void PopUpWidget::drawWidget(bool hilite) {
      NewGui      *gui = &g_gui;
      int x = _x + _labelWidth;
      int w = _w - _labelWidth;

      // Draw the label, if any
      if (_labelWidth > 0)
            gui->drawString(_label, _x, _y + 3, _labelWidth, isEnabled() ? gui->_textcolor : gui->_color, kTextAlignRight);

      // Draw a thin frame around us.
      gui->hLine(x, _y, x + w - 1, gui->_color);
      gui->hLine(x, _y +_h-1, x + w - 1, gui->_shadowcolor);
      gui->vLine(x, _y, _y+_h-1, gui->_color);
      gui->vLine(x + w - 1, _y, _y +_h - 1, gui->_shadowcolor);

      // Draw a set of arrows at the right end to signal this is a dropdown/popup
      Common::Point p0, p1;

      p0 = Common::Point(x + w + 1 - _h / 2, _y + 4);
      p1 = Common::Point(x + w + 1 - _h / 2, _y + _h - 4);

      Graphics::Surface &surf = g_gui.getScreen();
      OverlayColor color = !isEnabled() ? gui->_color : hilite ? gui->_textcolorhi : gui->_textcolor;

      // Evil HACK to draw filled triangles
      // FIXME: The "big" version is pretty ugly.
      for (; p1.y - p0.y > 1; p0.y++, p0.x--, p1.y--, p1.x++) {
            surf.drawLine(p0.x, p0.y, p1.x, p0.y, color);
            surf.drawLine(p0.x, p1.y, p1.x, p1.y, color);
      }

      // Draw the selected entry, if any
      if (_selectedItem >= 0) {
            TextAlignment align = (g_gui.getStringWidth(_entries[_selectedItem].name) > w-6) ? kTextAlignRight : kTextAlignLeft;
            gui->drawString(_entries[_selectedItem].name, x+2, _y+3, w-6, !isEnabled() ? gui->_color : gui->_textcolor, align);
      }
}

} // End of namespace GUI

Generated by  Doxygen 1.6.0   Back to index