summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorBjørn Lindeijer <bjorn@lindeijer.nl>2008-05-14 18:57:32 +0000
committerBjørn Lindeijer <bjorn@lindeijer.nl>2008-05-14 18:57:32 +0000
commit2d648c5dc29a1ceae154194c23c799c7076894b4 (patch)
treef6c31a30260b80713257be211b139263b3291098 /src/gui
parent41906acb990895831e3b2c39102f41c9b580ae10 (diff)
downloadmana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.tar.gz
mana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.tar.bz2
mana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.tar.xz
mana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.zip
Added ability to define friends, players you want to ignore or disregard and
configure whether trading is allowed. Based on new popup code, configuration improvements to store hierarchical data and a table model.
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/popup_box.cpp139
-rw-r--r--src/gui/popup_box.h71
-rw-r--r--src/gui/popupmenu.cpp52
-rw-r--r--src/gui/setup.cpp10
-rw-r--r--src/gui/setup.h2
-rw-r--r--src/gui/setup_players.cpp342
-rw-r--r--src/gui/setup_players.h70
-rw-r--r--src/gui/table.cpp419
-rw-r--r--src/gui/table.h150
-rw-r--r--src/gui/table_model.cpp149
-rw-r--r--src/gui/table_model.h137
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_) */