diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/popup_box.cpp | 139 | ||||
-rw-r--r-- | src/gui/popup_box.h | 71 | ||||
-rw-r--r-- | src/gui/popupmenu.cpp | 52 | ||||
-rw-r--r-- | src/gui/setup.cpp | 10 | ||||
-rw-r--r-- | src/gui/setup.h | 2 | ||||
-rw-r--r-- | src/gui/setup_players.cpp | 342 | ||||
-rw-r--r-- | src/gui/setup_players.h | 70 | ||||
-rw-r--r-- | src/gui/table.cpp | 419 | ||||
-rw-r--r-- | src/gui/table.h | 150 | ||||
-rw-r--r-- | src/gui/table_model.cpp | 149 | ||||
-rw-r--r-- | src/gui/table_model.h | 137 |
11 files changed, 1537 insertions, 4 deletions
diff --git a/src/gui/popup_box.cpp b/src/gui/popup_box.cpp new file mode 100644 index 00000000..4cac2169 --- /dev/null +++ b/src/gui/popup_box.cpp @@ -0,0 +1,139 @@ +/* + * The Mana World + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "popup_box.h" +#include "window.h" +#include "browserbox.h" + +#include <iostream> +#include <sstream> + +class PoppedSelectionWindow : public Window +{ +public: + PoppedSelectionWindow(PopupBox *owner): + mOwner(owner) + { + setResizable(false); + setTitleBarHeight(0); + mShowTitle = false; + + mBrowserBox = new BrowserBox(); + mBrowserBox->setPosition(4, 4); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mBrowserBox->setOpaque(false); + add(mBrowserBox); + mBrowserBox->setLinkHandler(owner); + + initModel(); + } + + void initModel(void) + { + mBrowserBox->clearRows(); + gcn::ListModel *model = mOwner->getListModel(); + for (int i = 0; i < model->getNumberOfElements(); i++) { + std::stringstream s; + s << "@@" << i << "|" << model->getElementAt(i) << "@@"; + mBrowserBox->addRow(s.str()); + } + + setContentSize(mBrowserBox->getWidth() + 8, mBrowserBox->getHeight() + 8); + } + +protected: + BrowserBox *mBrowserBox; + PopupBox *mOwner; +}; + +PopupBox::PopupBox(gcn::ListModel *list_model) : + DropDown(list_model), + mWindow(NULL) +{ +} + +PopupBox::~PopupBox(void) +{ + if (mWindow) + delete mWindow; +} + +//////////////////////////////////////// +// Widget ops + +void +PopupBox::draw(gcn::Graphics *graphics) +{ + // fill background + graphics->setColor(gcn::Color(0xd0, 0xd0, 0xd0)); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + + // draw frame-ish object + graphics->setColor(gcn::Color(0xc0, 0xc0, 0xc0)); + graphics->drawRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + graphics->setColor(gcn::Color(0xe0, 0xe0, 0xe0)); + graphics->drawLine(0, getHeight(), getWidth(), getHeight()); + graphics->drawLine(getWidth(), 0, getWidth(), getHeight()); + + graphics->drawText(getListModel()->getElementAt(getSelected()), 0, 0); +} + +//////////////////////////////////////// +// MouseListener ops + +void +PopupBox::mousePressed(gcn::MouseEvent& mouseEvent) +{ + if (0 <= mouseEvent.getY() + && mouseEvent.getY() < getHeight() + && mouseEvent.getX() >= 0 + && mouseEvent.getX() < getWidth() + && mouseEvent.getButton() == gcn::MouseEvent::LEFT + && mouseEvent.getSource() == this) { + if (mWindow == NULL) { + mWindow = new PoppedSelectionWindow(this); + mWindow->resizeToContent(); + } + + int x, y; + getAbsolutePosition(x, y); + mWindow->setPosition(mouseEvent.getX() + x, + mouseEvent.getY() + y); + mWindow->setVisible(true); + mWindow->requestMoveToTop(); + } +} + +//////////////////////////////////////// +// LinkHandler ops + +void +PopupBox::handleLink(const std::string &link) +{ + if (mWindow) + mWindow->setVisible(false); + + std::stringstream s; + s << link; + int index; + s >> index; + setSelected(index); +} diff --git a/src/gui/popup_box.h b/src/gui/popup_box.h new file mode 100644 index 00000000..1e3b155e --- /dev/null +++ b/src/gui/popup_box.h @@ -0,0 +1,71 @@ +/* + * The Mana World + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _TMW_POPUP_BOX_H +#define _TMW_POPUP_BOX_H + +#include <guichan/widgets/dropdown.hpp> + +#include "../guichanfwd.h" +#include "linkhandler.h" + +class PoppedSelectionWindow; + +/** + * Popup box widget. Appears as a box that `pops out' into a separate floating window when selected. + * + * API adapted from guichan's listbox API. Typewise + */ +class PopupBox : public gcn::DropDown, + public LinkHandler +{ +public: + /** + * Constructor. + * + * \param listModel the list model to use. + */ + PopupBox(gcn::ListModel *list_model = NULL); + + /** + * Destructor. + */ + virtual ~PopupBox(void); + + // Overriding Widget + + virtual void draw(gcn::Graphics* graphics); + + + // Overriding MouseListener + + virtual void mousePressed(gcn::MouseEvent& mouseEvent); + + + // Implementing Linkhandler + void handleLink(const std::string& link); + +protected: + PoppedSelectionWindow *mWindow; +}; + + +#endif /* !defined(_TMW_POPUP_BOX_H) */ diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp index 8f93cb1e..f9bfe56d 100644 --- a/src/gui/popupmenu.cpp +++ b/src/gui/popupmenu.cpp @@ -38,6 +38,7 @@ #include "../item.h" #include "../localplayer.h" #include "../npc.h" +#include "../player_relations.h" #include "../resources/iteminfo.h" #include "../resources/itemdb.h" @@ -75,8 +76,29 @@ void PopupMenu::showPopup(int x, int y, Being *being) // add as buddy will be options in this menu. const std::string &name = mBeing->getName(); mBrowserBox->addRow("@@trade|Trade With " + name + "@@"); - mBrowserBox->addRow("@@attack|Attack " + name + "@@"); + + mBrowserBox->addRow("##3---"); + + switch (player_relations.getRelation(name)) { + case PlayerRelation::NEUTRAL: + mBrowserBox->addRow("@@friend|Befriend " + name + "@@"); + + case PlayerRelation::FRIEND: + mBrowserBox->addRow("@@disregard|Disregard " + name + "@@"); + mBrowserBox->addRow("@@ignore|Ignore " + name + "@@"); + break; + + case PlayerRelation::DISREGARDED: + mBrowserBox->addRow("@@unignore|Un-Ignore " + name + "@@"); + mBrowserBox->addRow("@@ignore|Completely ignore " + name + "@@"); + break; + + case PlayerRelation::IGNORED: + mBrowserBox->addRow("@@unignore|Un-Ignore " + name + "@@"); + break; + } + //mBrowserBox->addRow("@@follow|Follow " + name + "@@"); //mBrowserBox->addRow("@@buddy|Add " + name + " to Buddy List@@"); } @@ -144,6 +166,34 @@ void PopupMenu::handleLink(const std::string& link) player_node->attack(mBeing, true); } + else if (link == "unignore" && + mBeing != NULL && + mBeing->getType() == Being::PLAYER) + { + player_relations.setRelation(mBeing->getName(), PlayerRelation::NEUTRAL); + } + + else if (link == "ignore" && + mBeing != NULL && + mBeing->getType() == Being::PLAYER) + { + player_relations.setRelation(mBeing->getName(), PlayerRelation::IGNORED); + } + + else if (link == "disregard" && + mBeing != NULL && + mBeing->getType() == Being::PLAYER) + { + player_relations.setRelation(mBeing->getName(), PlayerRelation::DISREGARDED); + } + + else if (link == "friend" && + mBeing != NULL && + mBeing->getType() == Being::PLAYER) + { + player_relations.setRelation(mBeing->getName(), PlayerRelation::FRIEND); + } + /* // Follow Player action else if (link == "follow") diff --git a/src/gui/setup.cpp b/src/gui/setup.cpp index 3089e54a..54bf01f4 100644 --- a/src/gui/setup.cpp +++ b/src/gui/setup.cpp @@ -30,9 +30,11 @@ #include "setup_joystick.h" #include "setup_video.h" #include "setup_keyboard.h" +#include "setup_players.h" #include "tabbedcontainer.h" #include "../utils/dtor.h" +#include <iostream> extern Window *statusWindow; extern Window *minimap; @@ -46,7 +48,7 @@ Setup::Setup(): Window("Setup") { setCloseButton(true); - int width = 250; + int width = 310; int height = 245; setContentSize(width, height); @@ -62,7 +64,7 @@ Setup::Setup(): } TabbedContainer *panel = new TabbedContainer(); - panel->setDimension(gcn::Rectangle(5, 5, 250, 205)); + panel->setDimension(gcn::Rectangle(5, 5, width, 205)); panel->setOpaque(false); SetupTab *tab; @@ -83,6 +85,10 @@ Setup::Setup(): panel->addTab(tab, "Keyboard"); mTabs.push_back(tab); + tab = new Setup_Players(); + panel->addTab(tab, "Players"); + mTabs.push_back(tab); + add(panel); setLocationRelativeTo(getParent()); diff --git a/src/gui/setup.h b/src/gui/setup.h index 77173367..543cab2c 100644 --- a/src/gui/setup.h +++ b/src/gui/setup.h @@ -48,7 +48,7 @@ class Setup : public Window, public gcn::ActionListener /** * Destructor. */ - ~Setup(); + virtual ~Setup(); /** * Event handling method. diff --git a/src/gui/setup_players.cpp b/src/gui/setup_players.cpp new file mode 100644 index 00000000..16c916b4 --- /dev/null +++ b/src/gui/setup_players.cpp @@ -0,0 +1,342 @@ +/* + * The Mana World + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include "setup_players.h" + +#include <vector> +#include <guichan/widgets/label.hpp> +#include "popup_box.h" +#include "button.h" + +#include "checkbox.h" +#include "ok_dialog.h" + +#include "../player_relations.h" +#include "../configuration.h" +#include "../log.h" +#include "../sound.h" + +#define COLUMNS_NR 2 // name plus listbox +#define NAME_COLUMN 0 +#define RELATION_CHOICE_COLUMN 1 + +#define ROW_HEIGHT 12 +// The following column widths really shouldn't be hardcoded but should scale with the size of the widget... except +// that, right now, the widget doesn't exactly scale either. +#define NAME_COLUMN_WIDTH 120 +#define RELATION_CHOICE_COLUMN_WIDTH 80 + +#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column) + +static std::string table_titles[COLUMNS_NR] = {"name", "relation"}; + +static const std::string RELATION_NAMES[PlayerRelation::RELATIONS_NR] = { + "neutral", "friend", "disregarded", "ignored" +}; + +class PlayerRelationListModel : public gcn::ListModel +{ +public: + virtual ~PlayerRelationListModel(void) { } + + virtual int getNumberOfElements(void) + { + return PlayerRelation::RELATIONS_NR; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return ""; + return RELATION_NAMES[i]; + } +}; + +class PlayerTableModel : public TableModel +{ +public: + PlayerTableModel(void) : + mPlayers(NULL) + { + playerRelationsUpdated(); + } + + virtual ~PlayerTableModel(void) + { + freeWidgets(); + if (mPlayers) + delete mPlayers; + } + + virtual int getRows(void) + { + return mPlayers->size(); + } + + virtual int getColumns(void) + { + return COLUMNS_NR; + } + + virtual int getRowHeight(void) + { + return ROW_HEIGHT; + } + + virtual int getColumnWidth(int index) + { + if (index == NAME_COLUMN) + return NAME_COLUMN_WIDTH; + else + return RELATION_CHOICE_COLUMN_WIDTH; + } + + virtual void playerRelationsUpdated(void) + { + signalBeforeUpdate(); + + freeWidgets(); + std::vector<std::string> *player_names = player_relations.getPlayers(); + if (mPlayers) + delete mPlayers; + mPlayers = player_names; + + // set up widgets + for (unsigned int r = 0; r < player_names->size(); ++r) { + std::string name = (*player_names)[r]; + gcn::Widget *widget = new gcn::Label(name); + mWidgets.push_back(widget); + + PopupBox *choicebox = new PopupBox(new PlayerRelationListModel()); + choicebox->setSelected(player_relations.getRelation(name)); + mWidgets.push_back(choicebox); + } + + signalAfterUpdate(); + } + + virtual void updateModelInRow(int row) + { + PopupBox *choicebox = dynamic_cast<PopupBox *>(getElementAt(row, RELATION_CHOICE_COLUMN)); + player_relations.setRelation(getPlayerAt(row), + static_cast<PlayerRelation::relation>(choicebox->getSelected())); + } + + + virtual gcn::Widget *getElementAt(int row, int column) + { + return mWidgets[WIDGET_AT(row, column)]; + } + + virtual void freeWidgets(void) + { + if (mPlayers) + delete mPlayers; + mPlayers = NULL; + + for (std::vector<gcn::Widget *>::const_iterator it = mWidgets.begin(); it != mWidgets.end(); it++) { + delete *it; + } + + mWidgets.clear(); + } + + std::string getPlayerAt(int index) + { + return (*mPlayers)[index]; + } + +protected: + std::vector<std::string> *mPlayers; + std::vector<gcn::Widget *> mWidgets; +}; + +/** + * Class for choosing one of the various `what to do when ignoring a player' options + */ +class IgnoreChoicesListModel : public gcn::ListModel +{ +public: + virtual ~IgnoreChoicesListModel(void) { } + + virtual int getNumberOfElements(void) + { + return player_relations.getPlayerIgnoreStrategies()->size(); + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements()) { + return "???"; + } + return (*player_relations.getPlayerIgnoreStrategies())[i]->mDescription; + } +}; + +#define ACTION_DELETE "delete" +#define ACTION_TABLE "table" +#define ACTION_STRATEGY "strategy" + +Setup_Players::Setup_Players(): + mPlayerTableTitleModel(new StaticTableModel(1, COLUMNS_NR)), + mPlayerTableModel(new PlayerTableModel()), + mPlayerTable(new GuiTable(mPlayerTableModel)), + mPlayerTitleTable(new GuiTable(mPlayerTableTitleModel)), + mPlayerScrollArea(new ScrollArea(mPlayerTable)), + mPersistIgnores(new CheckBox("save player list", player_relations.getPersistIgnores())), + mDefaultTrading(new CheckBox("allow trading", player_relations.getDefault() & PlayerRelation::TRADE)), + mDefaultWhisper(new CheckBox("allow whispers", player_relations.getDefault() & PlayerRelation:: WHISPER)), + mDeleteButton(new Button("Delete", ACTION_DELETE, this)), + mIgnoreActionChoicesBox(new PopupBox(new IgnoreChoicesListModel())) +{ + setOpaque(false); + + int table_width = NAME_COLUMN_WIDTH + RELATION_CHOICE_COLUMN_WIDTH; + mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH); + mPlayerTableTitleModel->fixColumnWidth(RELATION_CHOICE_COLUMN, RELATION_CHOICE_COLUMN_WIDTH); + mPlayerTitleTable->setDimension(gcn::Rectangle(10, 10, table_width, 10)); + mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf)); + for (int i = 0; i < COLUMNS_NR; i++) + mPlayerTableTitleModel->set(0, i, new gcn::Label(table_titles[i])); + mPlayerTitleTable->setLinewiseSelection(true); + + mPlayerScrollArea->setDimension(gcn::Rectangle(10, 25, table_width + COLUMNS_NR, 90)); + mPlayerScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mPlayerTable->setActionEventId(ACTION_TABLE); + mPlayerTable->setLinewiseSelection(true); + mPlayerTable->addActionListener(this); + + mDeleteButton->setPosition(10, 118); + + gcn::Label *ignore_action_label = new gcn::Label("When ignoring:"); + + ignore_action_label->setPosition(80, 118); + mIgnoreActionChoicesBox->setDimension(gcn::Rectangle(80, 132, 120, 12)); + mIgnoreActionChoicesBox->setActionEventId(ACTION_STRATEGY); + mIgnoreActionChoicesBox->addActionListener(this); + int ignore_strategy_index = 0; // safe default + if (player_relations.getPlayerIgnoreStrategy()) { + ignore_strategy_index = player_relations.getPlayerIgnoreStrategyIndex( + player_relations.getPlayerIgnoreStrategy()->mShortName); + if (ignore_strategy_index < 0) + ignore_strategy_index = 0; + } + mIgnoreActionChoicesBox->setSelected(ignore_strategy_index); + mIgnoreActionChoicesBox->adjustHeight(); + + mPersistIgnores->setPosition(80, 148); + mDefaultTrading->setPosition(80, 160); + mDefaultWhisper->setPosition(80, 172); + + reset(); + + add(ignore_action_label); + add(mDefaultTrading); + add(mDefaultWhisper); + add(mIgnoreActionChoicesBox); + add(mDeleteButton); + add(mPlayerScrollArea); + add(mPlayerTitleTable); + add(mPersistIgnores); + + player_relations.addListener(this); +} + +Setup_Players::~Setup_Players(void) +{ + player_relations.removeListener(this); +} + + +void +Setup_Players::reset() +{ + // We now have to search through the list of ignore choices to find the current + // selection. We could use an index into the table of config options in + // player_relations instead of strategies to sidestep this. + int selection = 0; + for (unsigned int i = 0; i < player_relations.getPlayerIgnoreStrategies()->size(); ++i) + if ((*player_relations.getPlayerIgnoreStrategies())[i] == + player_relations.getPlayerIgnoreStrategy()) { + + selection = i; + break; + } + + mIgnoreActionChoicesBox->setSelected(selection); +} + +void +Setup_Players::apply() +{ + player_relations.setPersistIgnores(mPersistIgnores->isSelected()); + player_relations.store(); + + unsigned int old_default_relations = + player_relations.getDefault() & ~(PlayerRelation::TRADE | PlayerRelation::WHISPER); + player_relations.setDefault(old_default_relations + | (mDefaultTrading->isSelected()? PlayerRelation::TRADE : 0) + | (mDefaultWhisper->isSelected()? PlayerRelation::WHISPER : 0)); +} + +void +Setup_Players::cancel() +{ +} + +void +Setup_Players::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_TABLE) { + // temporarily eliminate ourselves: we are fully aware of this change, so there is no + // need for asynchronous updates. (In fact, thouse might destroy the widet that + // triggered them, which would be rather embarrassing.) + player_relations.removeListener(this); + + int row = mPlayerTable->getSelectedRow(); + if (row >= 0) + mPlayerTableModel->updateModelInRow(row); + + player_relations.addListener(this); + + } else if (event.getId() == ACTION_DELETE) { + std::string name = mPlayerTableModel->getPlayerAt(mPlayerTable->getSelectedRow()); + + player_relations.removePlayer(name); + + } else if (event.getId() == ACTION_STRATEGY) { + PlayerIgnoreStrategy *s = + (*player_relations.getPlayerIgnoreStrategies())[ + mIgnoreActionChoicesBox->getSelected()]; + + player_relations.setPlayerIgnoreStrategy(s); + } +} + +void +Setup_Players::updatedPlayer(const std::string &name) +{ + mPlayerTableModel->playerRelationsUpdated(); + mDefaultTrading->setSelected(player_relations.getDefault() & PlayerRelation::TRADE); + mDefaultWhisper->setSelected(player_relations.getDefault() & PlayerRelation::WHISPER); +} diff --git a/src/gui/setup_players.h b/src/gui/setup_players.h new file mode 100644 index 00000000..2c6ccc82 --- /dev/null +++ b/src/gui/setup_players.h @@ -0,0 +1,70 @@ +/* + * The Mana World + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _TMW_GUI_SETUP_PLAYERS_H +#define _TMW_GUI_SETUP_PLAYERS_H + +#include "setuptab.h" + +#include "scrollarea.h" +#include "button.h" +#include "table.h" +#include "popup_box.h" +#include <guichan/actionlistener.hpp> + +#include "../guichanfwd.h" + +#include "../player_relations.h" + +class PlayerTableModel; + +class Setup_Players : public SetupTab, public gcn::ActionListener, public PlayerRelationsListener +{ +public: + Setup_Players(); + virtual ~Setup_Players(); + + void apply(); + void cancel(); + + void reset(); + + void action(const gcn::ActionEvent &event); + + virtual void updatedPlayer(const std::string &name); + +private: + + StaticTableModel *mPlayerTableTitleModel; + PlayerTableModel *mPlayerTableModel; + GuiTable *mPlayerTable; + GuiTable *mPlayerTitleTable; + ScrollArea *mPlayerScrollArea; + + gcn::CheckBox *mPersistIgnores; + gcn::CheckBox *mDefaultTrading; + gcn::CheckBox *mDefaultWhisper; + + Button *mDeleteButton; + PopupBox *mIgnoreActionChoicesBox; +}; + +#endif diff --git a/src/gui/table.cpp b/src/gui/table.cpp new file mode 100644 index 00000000..07f40f76 --- /dev/null +++ b/src/gui/table.cpp @@ -0,0 +1,419 @@ +/* + * The Mana World + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <guichan/graphics.hpp> +#include <guichan/actionlistener.hpp> +#include "table.h" +#include <cassert> + + + +class GuiTableActionListener : public gcn::ActionListener +{ +public: + GuiTableActionListener(GuiTable *_table, gcn::Widget *_widget, int _row, int _column); + + virtual ~GuiTableActionListener(void); + + virtual void action(const gcn::ActionEvent& actionEvent); + +protected: + GuiTable *mTable; + int mRow; + int mColumn; + gcn::Widget *mWidget; +}; + + +GuiTableActionListener::GuiTableActionListener(GuiTable *table, gcn::Widget *widget, int row, int column) : + mTable(table), + mRow(row), + mColumn(column), + mWidget(widget) +{ + if (widget) { + widget->addActionListener(this); + widget->_setParent(table); + } +} + +GuiTableActionListener::~GuiTableActionListener(void) +{ + if (mWidget) { + mWidget->removeActionListener(this); + mWidget->_setParent(NULL); + } +} + +void +GuiTableActionListener::action(const gcn::ActionEvent& actionEvent) +{ + mTable->setSelected(mRow, mColumn); + mTable->distributeActionEvent(); +} + + +GuiTable::GuiTable(TableModel *initial_model) : + mLinewiseMode(false), + mModel(NULL), + mSelectedRow(0), + mSelectedColumn(0), + mTopWidget(NULL) +{ + setModel(initial_model); + addMouseListener(this); + addKeyListener(this); +} + +GuiTable::~GuiTable(void) +{ + delete mModel; +} + +TableModel * +GuiTable::getModel(void) const +{ + return mModel; +} + +void +GuiTable::setModel(TableModel *new_model) +{ + if (mModel) { + uninstallActionListeners(); + mModel->removeListener(this); + } + + mModel = new_model; + installActionListeners(); + + new_model->installListener(this); + recomputeDimensions(); +} + +void +GuiTable::recomputeDimensions(void) +{ + int rows_nr = mModel->getRows(); + int columns_nr = mModel->getColumns(); + int width = 0; + int height = 0; + + if (mSelectedRow >= rows_nr) + mSelectedRow = rows_nr - 1; + + if (mSelectedColumn >= columns_nr) + mSelectedColumn = columns_nr - 1; + + for (int i = 0; i < columns_nr; i++) + width += getColumnWidth(i); + + height = getRowHeight() * rows_nr; + + setWidth(width); + setHeight(height); +} + +void +GuiTable::setSelected(int row, int column) +{ + mSelectedColumn = column; + mSelectedRow = row; +} + +int +GuiTable::getSelectedRow(void) +{ + return mSelectedRow; +} + +int +GuiTable::getSelectedColumn(void) +{ + return mSelectedColumn; +} + +void +GuiTable::setLinewiseSelection(bool linewise) +{ + mLinewiseMode = linewise; +} + +int +GuiTable::getRowHeight(void) +{ + if (mModel) + return mModel->getRowHeight() + 1; // border + else + return 0; +} + +int +GuiTable::getColumnWidth(int i) +{ + if (mModel) + return mModel->getColumnWidth(i) + 1; // border + else + return 0; +} + +void +GuiTable::uninstallActionListeners(void) +{ + for (std::vector<GuiTableActionListener *>::const_iterator it = action_listeners.begin(); it != action_listeners.end(); it++) + delete *it; + action_listeners.clear(); +} + +void +GuiTable::installActionListeners(void) +{ + int rows = mModel->getRows(); + int columns = mModel->getColumns(); + + for (int row = 0; row < rows; ++row) + for (int column = 0; column < columns; ++column) { + gcn::Widget *widget = mModel->getElementAt(row, column); + action_listeners.push_back(new GuiTableActionListener(this, widget, + row, column)); + } + + _setFocusHandler(_getFocusHandler()); // propagate focus handler to widgets +} + +// -- widget ops +void +GuiTable::draw(gcn::Graphics* graphics) +{ + graphics->setColor(getBackgroundColor()); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + + if (!mModel) + return; + + // First, determine how many rows we need to draw, and where we should start. + int first_row = -(getY() / getRowHeight()); + + if (first_row < 0) + first_row = 0; + + int rows_nr = 1 + (getHeight() / getRowHeight()); // May overestimate by one. + + int max_rows_nr = mModel->getRows() - first_row; // clip if neccessary: + if (max_rows_nr < rows_nr) + rows_nr = max_rows_nr; + + // Now determine the first and last column + // Take the easy way out; these are usually bounded and all visible. + int first_column = 0; + int last_column = mModel->getColumns() - 1; + + // Set up everything for drawing + int height = getRowHeight(); + int y_offset = first_row * height; + + for (int r = first_row; r < first_row + rows_nr; ++r) { + int x_offset = 0; + + for (int c = first_column; c <= last_column; ++c) { + gcn::Widget *widget = mModel->getElementAt(r, c); + int width = getColumnWidth(c); + if (widget) { + gcn::Rectangle bounds(x_offset, y_offset, width, height); + + if (widget == mTopWidget) { + bounds.height = widget->getHeight(); + bounds.width = widget->getWidth(); + } + + widget->setDimension(bounds); + + graphics->pushClipArea(bounds); + widget->draw(graphics); + graphics->popClipArea(); + + if (!mLinewiseMode + && c == mSelectedColumn + && r == mSelectedRow) + graphics->drawRectangle(bounds); + } + + x_offset += width; + } + + if (mLinewiseMode + && r == mSelectedRow) + graphics->drawRectangle(gcn::Rectangle(0, y_offset, + x_offset, height)); + + y_offset += height; + } + + if (mTopWidget) { + gcn::Rectangle bounds = mTopWidget->getDimension(); + graphics->pushClipArea(bounds); + mTopWidget->draw(graphics); + graphics->popClipArea(); + } +} + +void +GuiTable::logic(void) +{ +} + +void +GuiTable::moveToTop(gcn::Widget *widget) +{ + gcn::Widget::moveToTop(widget); + this->mTopWidget = widget; +} + +void +GuiTable::moveToBottom(gcn::Widget *widget) +{ + gcn::Widget::moveToBottom(widget); + if (widget == this->mTopWidget) + this->mTopWidget = NULL; +} + +gcn::Rectangle +GuiTable::getChildrenArea(void) +{ + return gcn::Rectangle(0, 0, getWidth(), getHeight()); +} + +// -- KeyListener notifications +void +GuiTable::keyPressed(gcn::KeyEvent& keyEvent) +{ +} + + +// -- MouseListener notifications +void +GuiTable::mousePressed(gcn::MouseEvent& mouseEvent) +{ + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) { + int row = getRowForY(mouseEvent.getY()); + int column = getColumnForX(mouseEvent.getX()); + + if (row > -1 && column > -1) { + mSelectedColumn = column; + mSelectedRow = row; + } + + distributeActionEvent(); + } +} + +void +GuiTable::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent) +{ +} + +void +GuiTable::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent) +{ +} + +void +GuiTable::mouseDragged(gcn::MouseEvent& mouseEvent) +{ +} + +// -- TableModelListener notifications +void +GuiTable::modelUpdated(bool completed) +{ + if (completed) { + recomputeDimensions(); + installActionListeners(); + } else // before the update? + uninstallActionListeners(); +} + +gcn::Widget * +GuiTable::getWidgetAt(int x, int y) +{ + int row = getRowForY(y); + int column = getColumnForX(x); + + if (mTopWidget + && mTopWidget->getDimension().isPointInRect(x, y)) + return mTopWidget; + + if (row > -1 + && column > -1) { + gcn::Widget *w = mModel->getElementAt(row, column); + if (w->isFocusable()) + return w; + else + return NULL; // Grab the event locally + } else + return NULL; +} + +int +GuiTable::getRowForY(int y) +{ + int row = y / getRowHeight(); + + if (row < 0 + || row >= mModel->getRows()) + return -1; + else + return row; +} + +int +GuiTable::getColumnForX(int x) +{ + int column; + int delta = 0; + + for (column = 0; column < mModel->getColumns(); column++) { + delta += getColumnWidth(column); + if (x <= delta) + break; + } + + if (column < 0 + || column >= mModel->getColumns()) + return -1; + else + return column; +} + + +void +GuiTable::_setFocusHandler(gcn::FocusHandler* focusHandler) +{ + gcn::Widget::_setFocusHandler(focusHandler); + + if (mModel) + for (int r = 0; r < mModel->getRows(); ++r) + for (int c = 0; c < mModel->getColumns(); ++c) { + gcn::Widget *w = mModel->getElementAt(r, c); + if (w) + w->_setFocusHandler(focusHandler); + } +} diff --git a/src/gui/table.h b/src/gui/table.h new file mode 100644 index 00000000..cef82d5d --- /dev/null +++ b/src/gui/table.h @@ -0,0 +1,150 @@ +/* + * The Mana World + * Copyright 2008 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TMW_TABLE_H_ +#define TMW_TABLE_H_ + +#include <vector> + +#include <guichan/gui.hpp> +#include <guichan/keylistener.hpp> +#include <guichan/mouselistener.hpp> +#include <guichan/platform.hpp> +#include <guichan/widget.hpp> + +#include "table_model.h" +#include "../guichanfwd.h" + +class GuiTableActionListener; + +/** + * A table, with rows and columns made out of sub-widgets. Largely inspired by (and can be thought of as a generalisation of) + * the guichan listbox implementation. + * + * Normally you want this within a ScrollArea. + * + * \ingroup GUI + */ +class GuiTable : public gcn::Widget, + public gcn::MouseListener, + public gcn::KeyListener, + public TableModelListener +{ + friend class GuiTableActionListener; // so that the action listener can call distributeActionEvent +public: + GuiTable(TableModel * initial_model = NULL); + + virtual ~GuiTable(void); + + /** + * Retrieves the active table model + */ + TableModel *getModel(void) const; + + /** + * Sets the table model + * + * Note that actions issued by widgets returned from the model will update the table + * selection, but only AFTER any event handlers installed within the widget have been + *triggered. To be notified after such an update, add an action listener to the table + * instead. + */ + void setModel(TableModel *m); + + void setSelected(int row, int column); + + int getSelectedRow(void); + + int getSelectedColumn(void); + + gcn::Rectangle getChildrenArea(void); + + /** + * Toggle whether to use linewise selection mode, in which the table selects an entire + * line at a time, rather than a single cell. + * + * Note that column information is tracked even in linewise selection mode; this mode + * therefore only affects visualisation. + * + * Disabled by default. + * + * \param linewise: Whether to enable linewise selection mode + */ + void setLinewiseSelection(bool linewise); + + // Inherited from Widget + virtual void draw(gcn::Graphics* graphics); + + virtual void logic(void); + + virtual gcn::Widget *getWidgetAt(int x, int y); + + virtual void moveToTop(gcn::Widget *child); + + virtual void moveToBottom(gcn::Widget *child); + + virtual void _setFocusHandler(gcn::FocusHandler* focusHandler); + + // Inherited from KeyListener + virtual void keyPressed(gcn::KeyEvent& keyEvent); + + + // Inherited from MouseListener + virtual void mousePressed(gcn::MouseEvent& mouseEvent); + + virtual void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent); + + virtual void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent); + + virtual void mouseDragged(gcn::MouseEvent& mouseEvent); + + // Constraints inherited from TableModelListener + virtual void modelUpdated(bool); + +protected: + + virtual void uninstallActionListeners(void); // frees all action listeners on inner widgets + virtual void installActionListeners(void); // installs all action listeners on inner widgets + + virtual int getRowHeight(void); + virtual int getColumnWidth(int i); + +private: + + int getRowForY(int y); // -1 on error + int getColumnForX(int x); // -1 on error + void recomputeDimensions(void); + bool mLinewiseMode; + + TableModel *mModel; + + int mSelectedRow; + int mSelectedColumn; + + int mPopFramesNr; // Number of frames to skip upwards when drawing the selected widget + + gcn::Widget *mTopWidget; // If someone moves a fresh widget to the top, we must display it + + std::vector<GuiTableActionListener *> action_listeners; // Vector for compactness; used as a list in practice. +}; + + +#endif /* !defined(TMW_TABLE_H_) */ diff --git a/src/gui/table_model.cpp b/src/gui/table_model.cpp new file mode 100644 index 00000000..78df0636 --- /dev/null +++ b/src/gui/table_model.cpp @@ -0,0 +1,149 @@ +/* + * The Mana World + * Copyright 2008 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <guichan/widget.hpp> +#include <cstdlib> +#include "table_model.h" + +void +TableModel::installListener(TableModelListener *listener) +{ + listeners.insert(listener); +} + +void +TableModel::removeListener(TableModelListener *listener) +{ + listeners.erase(listener); +} + +void +TableModel::signalBeforeUpdate(void) +{ + for (std::set<TableModelListener *>::const_iterator it = listeners.begin(); it != listeners.end(); it++) + (*it)->modelUpdated(false); +} + +void +TableModel::signalAfterUpdate(void) +{ + for (std::set<TableModelListener *>::const_iterator it = listeners.begin(); it != listeners.end(); it++) + (*it)->modelUpdated(true); +} + + + +#define WIDGET_AT(row, column) (((row) * mColumns) + (column)) +#define DYN_SIZE(h) ((h) >= 0) // determines whether this size is tagged for auto-detection + +StaticTableModel::StaticTableModel(int row, int column) : + mRows(row), + mColumns(column), + mHeight(1) +{ + mTableModel.resize(row * column, NULL); + mWidths.resize(column, 1); +} + +StaticTableModel::~StaticTableModel(void) +{ + for (std::vector<gcn::Widget *>::const_iterator it = mTableModel.begin(); it != mTableModel.end(); it++) + if (*it) + delete *it; +} + +void +StaticTableModel::set(int row, int column, gcn::Widget *widget) +{ + if (row >= mRows || row < 0 + || column >= mColumns || row > 0) + // raise exn? + return; + + if (DYN_SIZE(mHeight) + && widget->getHeight() > mHeight) + mHeight = widget->getHeight(); + + if (DYN_SIZE(mWidths[column]) + && widget->getWidth() > mWidths[column]) + mWidths[column] = widget->getWidth(); + + signalBeforeUpdate(); + + if (mTableModel[WIDGET_AT(row, column)]) + delete mTableModel[WIDGET_AT(row, column)]; + + mTableModel[WIDGET_AT(row, column)] = widget; + + signalAfterUpdate(); +} + +gcn::Widget * +StaticTableModel::getElementAt(int row, int column) +{ + return mTableModel[WIDGET_AT(row, column)]; +} + +void +StaticTableModel::fixColumnWidth(int column, int width) +{ + if (width < 0 + || column < 0 || column >= mColumns) + return; + + mWidths[column] = -width; // Negate to tag as fixed +} + +void +StaticTableModel::fixRowHeight(int height) +{ + if (height < 0) + return; + + mHeight = -height; +} + +int +StaticTableModel::getRowHeight(void) +{ + return abs(mHeight); +} + +int +StaticTableModel::getColumnWidth(int column) +{ + if (column < 0 || column >= mColumns) + return 0; + + return abs(mWidths[column]); +} + +int +StaticTableModel::getRows(void) +{ + return mRows; +} + +int +StaticTableModel::getColumns(void) +{ + return mColumns; +} diff --git a/src/gui/table_model.h b/src/gui/table_model.h new file mode 100644 index 00000000..48ef1c0d --- /dev/null +++ b/src/gui/table_model.h @@ -0,0 +1,137 @@ +/* + * The Mana World + * Copyright 2008 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TMW_TABLE_MODEL_H_ +#define TMW_TABLE_MODEL_H_ + +#include <guichan/gui.hpp> +#include <set> +#include <vector> +#include "../guichanfwd.h" + +class TableModelListener +{ +public: + /** + * Must be invoked by the TableModel whenever a global change is about to occur or + * has occurred (e.g., when a row or column is being removed or added). + * + * This method is triggered twice, once before and once after the update. + * + * \param completed whether we are signalling the end of the update + */ + virtual void modelUpdated(bool completed) = 0; +}; + +/** + * A model for a regular table of widgets. + */ +class TableModel +{ +public: + virtual ~TableModel(void) { } + + /** + * Determines the number of rows (lines) in the table + */ + virtual int getRows(void) = 0; + + /** + * Determines the number of columns in each row + */ + virtual int getColumns(void) = 0; + + /** + * Determines the height for each row + */ + virtual int getRowHeight(void) = 0; + + /** + * Determines the width of each individual column + */ + virtual int getColumnWidth(int index) = 0; + + /** + * Retrieves the widget stored at the specified location within the table. + */ + virtual gcn::Widget *getElementAt(int row, int column) = 0; + + virtual void installListener(TableModelListener *listener); + + virtual void removeListener(TableModelListener *listener); + +protected: + /** + * Tells all listeners that the table is about to see an update + */ + virtual void signalBeforeUpdate(void); + + /** + * Tells all listeners that the table has seen an update + */ + virtual void signalAfterUpdate(void); + +private: + std::set<TableModelListener *> listeners; +}; + + +class StaticTableModel : public TableModel +{ +public: + StaticTableModel(int width, int height); + virtual ~StaticTableModel(void); + + /** + * Inserts a widget into the table model. + * The model is resized to accomodate the widget's width and height, unless column width / row height have been fixed. + */ + virtual void set(int row, int column, gcn::Widget *widget); + + /** + * Fixes the column width for a given column; this overrides dynamic width inference. + * + * Semantics are undefined for width 0. + */ + virtual void fixColumnWidth(int column, int width); + + /** + * Fixes the row height; this overrides dynamic height inference. + * + * Semantics are undefined for width 0. + */ + virtual void fixRowHeight(int height); + + virtual int getRows(); + virtual int getColumns(); + virtual int getRowHeight(); + virtual int getColumnWidth(int index); + virtual gcn::Widget *getElementAt(int row, int column); + + +protected: + int mRows, mColumns; + int mHeight; + std::vector<gcn::Widget *> mTableModel; + std::vector<int> mWidths; +}; + +#endif /* !defined(TMW_TABLE_MODEL_H_) */ |