summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorAndrei Karas <akaras@inbox.ru>2011-01-02 01:48:38 +0200
committerAndrei Karas <akaras@inbox.ru>2011-01-02 02:41:24 +0200
commit3eeae12c498d1a4dbe969462d2ba841f77ee3ccb (patch)
treeff8eab35e732bc0749fc11677c8873a7b3a58704 /src/gui
downloadmanaplus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.gz
manaplus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.bz2
manaplus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.xz
manaplus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.zip
Initial commit.
This code based on mana client http://www.gitorious.org/mana/mana and my private repository.
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/beingpopup.cpp142
-rw-r--r--src/gui/beingpopup.h59
-rw-r--r--src/gui/botcheckerwindow.cpp413
-rw-r--r--src/gui/botcheckerwindow.h95
-rw-r--r--src/gui/buy.cpp323
-rw-r--r--src/gui/buy.h151
-rw-r--r--src/gui/buysell.cpp137
-rw-r--r--src/gui/buysell.h78
-rw-r--r--src/gui/changeemaildialog.cpp167
-rw-r--r--src/gui/changeemaildialog.h78
-rw-r--r--src/gui/changepassworddialog.cpp157
-rw-r--r--src/gui/changepassworddialog.h73
-rw-r--r--src/gui/charcreatedialog.cpp372
-rw-r--r--src/gui/charcreatedialog.h122
-rw-r--r--src/gui/charselectdialog.cpp456
-rw-r--r--src/gui/charselectdialog.h113
-rw-r--r--src/gui/chat.cpp1350
-rw-r--r--src/gui/chat.h318
-rw-r--r--src/gui/confirmdialog.cpp112
-rw-r--r--src/gui/confirmdialog.h57
-rw-r--r--src/gui/connectiondialog.cpp65
-rw-r--r--src/gui/connectiondialog.h62
-rw-r--r--src/gui/debugwindow.cpp248
-rw-r--r--src/gui/debugwindow.h72
-rw-r--r--src/gui/editdialog.cpp73
-rw-r--r--src/gui/editdialog.h66
-rw-r--r--src/gui/emotepopup.cpp214
-rw-r--r--src/gui/emotepopup.h121
-rw-r--r--src/gui/equipmentwindow.cpp260
-rw-r--r--src/gui/equipmentwindow.h98
-rw-r--r--src/gui/focushandler.cpp99
-rw-r--r--src/gui/focushandler.h77
-rw-r--r--src/gui/gui.cpp310
-rw-r--r--src/gui/gui.h148
-rw-r--r--src/gui/help.cpp106
-rw-r--r--src/gui/help.h76
-rw-r--r--src/gui/inventorywindow.cpp503
-rw-r--r--src/gui/inventorywindow.h155
-rw-r--r--src/gui/itemamount.cpp432
-rw-r--r--src/gui/itemamount.h124
-rw-r--r--src/gui/itempopup.cpp238
-rw-r--r--src/gui/itempopup.h71
-rw-r--r--src/gui/killstats.cpp424
-rw-r--r--src/gui/killstats.h132
-rw-r--r--src/gui/login.cpp231
-rw-r--r--src/gui/login.h90
-rw-r--r--src/gui/minimap.cpp292
-rw-r--r--src/gui/minimap.h69
-rw-r--r--src/gui/ministatus.cpp228
-rw-r--r--src/gui/ministatus.h94
-rw-r--r--src/gui/npcdialog.cpp483
-rw-r--r--src/gui/npcdialog.h232
-rw-r--r--src/gui/npcpostdialog.cpp128
-rw-r--r--src/gui/npcpostdialog.h70
-rw-r--r--src/gui/okdialog.cpp81
-rw-r--r--src/gui/okdialog.h57
-rw-r--r--src/gui/outfitwindow.cpp914
-rw-r--r--src/gui/outfitwindow.h135
-rw-r--r--src/gui/palette.cpp274
-rw-r--r--src/gui/palette.h191
-rw-r--r--src/gui/popupmenu.cpp1286
-rw-r--r--src/gui/popupmenu.h149
-rw-r--r--src/gui/quitdialog.cpp204
-rw-r--r--src/gui/quitdialog.h77
-rw-r--r--src/gui/register.cpp258
-rw-r--r--src/gui/register.h110
-rw-r--r--src/gui/sdlinput.cpp432
-rw-r--r--src/gui/sdlinput.h188
-rw-r--r--src/gui/sell.cpp333
-rw-r--r--src/gui/sell.h145
-rw-r--r--src/gui/serverdialog.cpp768
-rw-r--r--src/gui/serverdialog.h204
-rw-r--r--src/gui/setup.cpp178
-rw-r--r--src/gui/setup.h82
-rw-r--r--src/gui/setup_audio.cpp179
-rw-r--r--src/gui/setup_audio.h53
-rw-r--r--src/gui/setup_chat.cpp306
-rw-r--r--src/gui/setup_chat.h92
-rw-r--r--src/gui/setup_colors.cpp443
-rw-r--r--src/gui/setup_colors.h96
-rw-r--r--src/gui/setup_joystick.cpp101
-rw-r--r--src/gui/setup_joystick.h48
-rw-r--r--src/gui/setup_keyboard.cpp210
-rw-r--r--src/gui/setup_keyboard.h81
-rw-r--r--src/gui/setup_other.cpp426
-rw-r--r--src/gui/setup_other.h125
-rw-r--r--src/gui/setup_players.cpp505
-rw-r--r--src/gui/setup_players.h95
-rw-r--r--src/gui/setup_theme.cpp239
-rw-r--r--src/gui/setup_theme.h74
-rw-r--r--src/gui/setup_video.cpp819
-rw-r--r--src/gui/setup_video.h136
-rw-r--r--src/gui/shopwindow.cpp788
-rw-r--r--src/gui/shopwindow.h173
-rw-r--r--src/gui/shortcutwindow.cpp152
-rw-r--r--src/gui/shortcutwindow.h71
-rw-r--r--src/gui/skilldialog.cpp523
-rw-r--r--src/gui/skilldialog.h91
-rw-r--r--src/gui/socialwindow.cpp1306
-rw-r--r--src/gui/socialwindow.h159
-rw-r--r--src/gui/specialswindow.cpp257
-rw-r--r--src/gui/specialswindow.h73
-rw-r--r--src/gui/speechbubble.cpp91
-rw-r--r--src/gui/speechbubble.h58
-rw-r--r--src/gui/spellpopup.cpp105
-rw-r--r--src/gui/spellpopup.h67
-rw-r--r--src/gui/statuspopup.cpp543
-rw-r--r--src/gui/statuspopup.h78
-rw-r--r--src/gui/statuswindow.cpp880
-rw-r--r--src/gui/statuswindow.h99
-rw-r--r--src/gui/textcommandeditor.cpp390
-rw-r--r--src/gui/textcommandeditor.h105
-rw-r--r--src/gui/textdialog.cpp94
-rw-r--r--src/gui/textdialog.h74
-rw-r--r--src/gui/textpopup.cpp99
-rw-r--r--src/gui/textpopup.h68
-rw-r--r--src/gui/theme.cpp791
-rw-r--r--src/gui/theme.h273
-rw-r--r--src/gui/trade.cpp420
-rw-r--r--src/gui/trade.h170
-rw-r--r--src/gui/truetypefont.cpp336
-rw-r--r--src/gui/truetypefont.h104
-rw-r--r--src/gui/unregisterdialog.cpp145
-rw-r--r--src/gui/unregisterdialog.h68
-rw-r--r--src/gui/updatewindow.cpp672
-rw-r--r--src/gui/updatewindow.h210
-rw-r--r--src/gui/userpalette.cpp292
-rw-r--r--src/gui/userpalette.h222
-rw-r--r--src/gui/viewport.cpp763
-rw-r--r--src/gui/viewport.h298
-rw-r--r--src/gui/whoisonline.cpp550
-rw-r--r--src/gui/whoisonline.h139
-rw-r--r--src/gui/widgets/avatarlistbox.cpp346
-rw-r--r--src/gui/widgets/avatarlistbox.h70
-rw-r--r--src/gui/widgets/battletab.cpp54
-rw-r--r--src/gui/widgets/battletab.h47
-rw-r--r--src/gui/widgets/browserbox.cpp534
-rw-r--r--src/gui/widgets/browserbox.h205
-rw-r--r--src/gui/widgets/button.cpp227
-rw-r--r--src/gui/widgets/button.h94
-rw-r--r--src/gui/widgets/channeltab.cpp132
-rw-r--r--src/gui/widgets/channeltab.h62
-rw-r--r--src/gui/widgets/chattab.cpp431
-rw-r--r--src/gui/widgets/chattab.h173
-rw-r--r--src/gui/widgets/checkbox.cpp187
-rw-r--r--src/gui/widgets/checkbox.h92
-rw-r--r--src/gui/widgets/container.cpp33
-rw-r--r--src/gui/widgets/container.h43
-rw-r--r--src/gui/widgets/desktop.cpp157
-rw-r--r--src/gui/widgets/desktop.h73
-rw-r--r--src/gui/widgets/dropdown.cpp303
-rw-r--r--src/gui/widgets/dropdown.h97
-rw-r--r--src/gui/widgets/dropshortcutcontainer.cpp303
-rw-r--r--src/gui/widgets/dropshortcutcontainer.h88
-rw-r--r--src/gui/widgets/emoteshortcutcontainer.cpp259
-rw-r--r--src/gui/widgets/emoteshortcutcontainer.h84
-rw-r--r--src/gui/widgets/flowcontainer.cpp88
-rw-r--r--src/gui/widgets/flowcontainer.h73
-rw-r--r--src/gui/widgets/icon.cpp60
-rw-r--r--src/gui/widgets/icon.h66
-rw-r--r--src/gui/widgets/inttextfield.cpp112
-rw-r--r--src/gui/widgets/inttextfield.h76
-rw-r--r--src/gui/widgets/itemcontainer.cpp475
-rw-r--r--src/gui/widgets/itemcontainer.h195
-rw-r--r--src/gui/widgets/itemlinkhandler.cpp66
-rw-r--r--src/gui/widgets/itemlinkhandler.h47
-rw-r--r--src/gui/widgets/itemshortcutcontainer.cpp375
-rw-r--r--src/gui/widgets/itemshortcutcontainer.h93
-rw-r--r--src/gui/widgets/label.cpp38
-rw-r--r--src/gui/widgets/label.h52
-rw-r--r--src/gui/widgets/layout.cpp362
-rw-r--r--src/gui/widgets/layout.h319
-rw-r--r--src/gui/widgets/layouthelper.cpp63
-rw-r--r--src/gui/widgets/layouthelper.h90
-rw-r--r--src/gui/widgets/linkhandler.h42
-rw-r--r--src/gui/widgets/listbox.cpp146
-rw-r--r--src/gui/widgets/listbox.h78
-rw-r--r--src/gui/widgets/passwordfield.cpp36
-rw-r--r--src/gui/widgets/passwordfield.h46
-rw-r--r--src/gui/widgets/playerbox.cpp120
-rw-r--r--src/gui/widgets/playerbox.h74
-rw-r--r--src/gui/widgets/popup.cpp174
-rw-r--r--src/gui/widgets/popup.h174
-rw-r--r--src/gui/widgets/progressbar.cpp225
-rw-r--r--src/gui/widgets/progressbar.h139
-rw-r--r--src/gui/widgets/progressindicator.cpp78
-rw-r--r--src/gui/widgets/progressindicator.h45
-rw-r--r--src/gui/widgets/radiobutton.cpp163
-rw-r--r--src/gui/widgets/radiobutton.h85
-rw-r--r--src/gui/widgets/resizegrip.cpp82
-rw-r--r--src/gui/widgets/resizegrip.h60
-rw-r--r--src/gui/widgets/scrollarea.cpp445
-rw-r--r--src/gui/widgets/scrollarea.h151
-rw-r--r--src/gui/widgets/setuptab.cpp31
-rw-r--r--src/gui/widgets/setuptab.h64
-rw-r--r--src/gui/widgets/shopitems.cpp118
-rw-r--r--src/gui/widgets/shopitems.h120
-rw-r--r--src/gui/widgets/shoplistbox.cpp185
-rw-r--r--src/gui/widgets/shoplistbox.h104
-rw-r--r--src/gui/widgets/shortcutcontainer.cpp67
-rw-r--r--src/gui/widgets/shortcutcontainer.h115
-rw-r--r--src/gui/widgets/slider.cpp298
-rw-r--r--src/gui/widgets/slider.h98
-rw-r--r--src/gui/widgets/spellshortcutcontainer.cpp285
-rw-r--r--src/gui/widgets/spellshortcutcontainer.h88
-rw-r--r--src/gui/widgets/tab.cpp196
-rw-r--r--src/gui/widgets/tab.h80
-rw-r--r--src/gui/widgets/tabbedarea.cpp221
-rw-r--r--src/gui/widgets/tabbedarea.h129
-rw-r--r--src/gui/widgets/table.cpp585
-rw-r--r--src/gui/widgets/table.h195
-rw-r--r--src/gui/widgets/tablemodel.cpp173
-rw-r--r--src/gui/widgets/tablemodel.h149
-rw-r--r--src/gui/widgets/textbox.cpp149
-rw-r--r--src/gui/widgets/textbox.h70
-rw-r--r--src/gui/widgets/textfield.cpp306
-rw-r--r--src/gui/widgets/textfield.h110
-rw-r--r--src/gui/widgets/textpreview.cpp82
-rw-r--r--src/gui/widgets/textpreview.h130
-rw-r--r--src/gui/widgets/tradetab.cpp59
-rw-r--r--src/gui/widgets/tradetab.h50
-rw-r--r--src/gui/widgets/vertcontainer.cpp53
-rw-r--r--src/gui/widgets/vertcontainer.h52
-rw-r--r--src/gui/widgets/whispertab.cpp164
-rw-r--r--src/gui/widgets/whispertab.h67
-rw-r--r--src/gui/widgets/window.cpp924
-rw-r--r--src/gui/widgets/window.h441
-rw-r--r--src/gui/widgets/windowcontainer.cpp40
-rw-r--r--src/gui/widgets/windowcontainer.h59
-rw-r--r--src/gui/windowmenu.cpp285
-rw-r--r--src/gui/windowmenu.h82
-rw-r--r--src/gui/worldselectdialog.cpp139
-rw-r--r--src/gui/worldselectdialog.h73
233 files changed, 48498 insertions, 0 deletions
diff --git a/src/gui/beingpopup.cpp b/src/gui/beingpopup.cpp
new file mode 100644
index 000000000..6b3a25ee4
--- /dev/null
+++ b/src/gui/beingpopup.cpp
@@ -0,0 +1,142 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/beingpopup.h"
+
+#include "being.h"
+#include "graphics.h"
+#include "units.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+
+#include "gui/widgets/label.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+
+
+BeingPopup::BeingPopup():
+ Popup("BeingPopup")
+{
+ // Being Name
+ mBeingName = new Label("A");
+ mBeingName->setFont(boldFont);
+ mBeingName->setPosition(getPadding(), getPadding());
+
+ const int fontHeight = mBeingName->getHeight() + getPadding();
+
+ // Being's party
+ mBeingParty = new Label("A");
+ mBeingParty->setPosition(getPadding(), fontHeight);
+
+ // Being's party
+ mBeingGuild = new Label("A");
+ mBeingGuild->setPosition(getPadding(), 2 * fontHeight);
+
+ mBeingRank = new Label("A");
+ mBeingRank->setPosition(getPadding(), 3 * fontHeight);
+
+ add(mBeingName);
+ add(mBeingParty);
+ add(mBeingGuild);
+ add(mBeingRank);
+}
+
+BeingPopup::~BeingPopup()
+{
+}
+
+void BeingPopup::show(int x, int y, Being *b)
+{
+ if (!b)
+ {
+ setVisible(false);
+ return;
+ }
+
+ Label *label1 = mBeingParty;
+ Label *label2 = mBeingGuild;
+ Label *label3 = mBeingRank;
+
+ mBeingName->setCaption(b->getName());
+ mBeingName->adjustSize();
+ label1->setCaption("");
+ label2->setCaption("");
+ label3->setCaption("");
+
+ if (!(b->getPartyName().empty()))
+ {
+ label1->setCaption(strprintf(_("Party: %s"),
+ b->getPartyName().c_str()));
+ label1->adjustSize();
+ }
+ else
+ {
+ label3 = label2;
+ label2 = label1;
+ label1 = 0;
+ }
+
+ if (!(b->getGuildName().empty()))
+ {
+ label2->setCaption(strprintf(_("Guild: %s"),
+ b->getGuildName().c_str()));
+ label2->adjustSize();
+ }
+ else
+ {
+ label3 = label2;
+ label2 = 0;
+ }
+
+ if (b->getPvpRank() > 0)
+ {
+ label3->setCaption(strprintf(_("Pvp rank: %d"), b->getPvpRank()));
+ label3->adjustSize();
+ }
+ else
+ {
+ label3 = 0;
+ }
+
+ int minWidth = mBeingName->getWidth();
+ if (label1 && label1->getWidth() > minWidth)
+ minWidth = label1->getWidth();
+ if (label2 && label2->getWidth() > minWidth)
+ minWidth = label2->getWidth();
+ if (label3 && label3->getWidth() > minWidth)
+ minWidth = label3->getWidth();
+
+ int height = getFont()->getHeight();
+ if (label1)
+ height += getFont()->getHeight();
+ if (label2)
+ height += getFont()->getHeight();
+ if (label3)
+ height += getFont()->getHeight();
+
+ setContentSize(minWidth + 10, height + 10);
+
+ position(x, y);
+ return;
+}
diff --git a/src/gui/beingpopup.h b/src/gui/beingpopup.h
new file mode 100644
index 000000000..d43f8e105
--- /dev/null
+++ b/src/gui/beingpopup.h
@@ -0,0 +1,59 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEINGPOPUP_H
+#define BEINGPOPUP_H
+
+#include "gui/widgets/popup.h"
+
+class Being;
+class Label;
+
+/**
+ * A popup that displays information about a being.
+ */
+class BeingPopup : public Popup
+{
+ public:
+ /**
+ * Constructor. Initializes the being popup.
+ */
+ BeingPopup();
+
+ /**
+ * Destructor. Cleans up the being popup on deletion.
+ */
+ ~BeingPopup();
+
+ /**
+ * Sets the info to be displayed given a particular player.
+ */
+ void show(int x, int y, Being *b);
+
+ // TODO: Add a version for monsters, NPCs, etc?
+
+ private:
+ Label *mBeingName;
+ Label *mBeingParty;
+ Label *mBeingGuild;
+ Label *mBeingRank;
+};
+
+#endif // BEINGPOPUP_H
diff --git a/src/gui/botcheckerwindow.cpp b/src/gui/botcheckerwindow.cpp
new file mode 100644
index 000000000..f7bd7d060
--- /dev/null
+++ b/src/gui/botcheckerwindow.cpp
@@ -0,0 +1,413 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "botcheckerwindow.h"
+
+#include <SDL.h>
+#include <SDL_thread.h>
+#include <vector>
+#include <algorithm>
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/table.h"
+
+#include "actorspritemanager.h"
+#include "chat.h"
+#include "configuration.h"
+#include "localplayer.h"
+#include "main.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+
+#define COLUMNS_NR 5 // name plus listbox
+#define NAME_COLUMN 0
+#define TIME_COLUMN 1
+
+#define ROW_HEIGHT 12
+// The following column widths really shouldn't be hardcoded but should scale with the size of the widget... excep
+// that, right now, the widget doesn't exactly scale either.
+#define NAME_COLUMN_WIDTH 185
+#define TIME_COLUMN_WIDTH 70
+
+#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column)
+
+class UsersTableModel : public TableModel
+{
+public:
+ UsersTableModel() :
+ mPlayers(0)
+ {
+ playersUpdated();
+ }
+
+ virtual ~UsersTableModel()
+ {
+ freeWidgets();
+ }
+
+ virtual int getRows() const
+ {
+ return static_cast<int>(mPlayers.size());
+ }
+
+ virtual int getColumns() const
+ {
+ return COLUMNS_NR;
+ }
+
+ virtual int getRowHeight() const
+ {
+ return ROW_HEIGHT;
+ }
+
+ virtual int getColumnWidth(int index) const
+ {
+ if (index == NAME_COLUMN)
+ return NAME_COLUMN_WIDTH;
+ else
+ return TIME_COLUMN_WIDTH;
+ }
+
+ virtual void playersUpdated()
+ {
+ signalBeforeUpdate();
+
+ freeWidgets();
+ mPlayers.clear();
+ if (actorSpriteManager && botCheckerWindow
+ && botCheckerWindow->mEnabled)
+ {
+ std::set<ActorSprite*> beings = actorSpriteManager->getAll();
+ ActorSprites::iterator i = beings.begin();
+ for (ActorSprites::const_iterator i = beings.begin();
+ i != beings.end(); i++)
+ {
+ Being *being = dynamic_cast<Being*>(*i);
+
+ if (being && being->getType() == Being::PLAYER
+ && being != player_node && being->getName() != "")
+ {
+ mPlayers.push_back(being);
+ }
+ }
+ }
+
+ unsigned int curTime = cur_time;
+ // set up widgets
+ for (unsigned int r = 0; r < mPlayers.size(); ++r)
+ {
+ if (!mPlayers.at(r))
+ continue;
+
+ std::string name = mPlayers.at(r)->getName();
+ gcn::Widget *widget = new Label(name);
+
+ mWidgets.push_back(widget);
+
+ if (mPlayers.at(r)->getAttackTime() != 0)
+ {
+ widget = new Label(toString(curTime
+ - mPlayers.at(r)->getAttackTime()));
+ }
+ else
+ {
+ widget = new Label(toString(curTime
+ - mPlayers.at(r)->getTestTime()) + "?");
+ }
+ mWidgets.push_back(widget);
+
+ if (mPlayers.at(r)->getTalkTime() != 0)
+ {
+ widget = new Label(toString(curTime
+ - mPlayers.at(r)->getTalkTime()));
+ }
+ else
+ {
+ widget = new Label(toString(curTime
+ - mPlayers.at(r)->getTestTime()) + "?");
+ }
+ mWidgets.push_back(widget);
+
+ if (mPlayers.at(r)->getMoveTime() != 0)
+ {
+ widget = new Label(toString(curTime
+ - mPlayers.at(r)->getMoveTime()));
+ }
+ else
+ {
+ widget = new Label(toString(curTime
+ - mPlayers.at(r)->getTestTime()) + "?");
+ }
+ mWidgets.push_back(widget);
+
+ std::string str;
+ bool talkBot = false;
+ bool moveBot = false;
+ bool attackBot = false;
+ bool otherBot = false;
+
+ if (curTime - mPlayers.at(r)->getTestTime() > 2 * 60)
+ {
+ int attack = curTime - (mPlayers.at(r)->getAttackTime()
+ ? mPlayers.at(r)->getAttackTime()
+ : mPlayers.at(r)->getTestTime());
+ int talk = curTime - (mPlayers.at(r)->getTalkTime()
+ ? mPlayers.at(r)->getTalkTime()
+ : mPlayers.at(r)->getTestTime()) - attack;
+ int move = curTime - (mPlayers.at(r)->getMoveTime()
+ ? mPlayers.at(r)->getMoveTime()
+ : mPlayers.at(r)->getTestTime()) - attack;
+ int other = curTime - (mPlayers.at(r)->getOtherTime()
+ ? mPlayers.at(r)->getMoveTime()
+ : mPlayers.at(r)->getOtherTime()) - attack;
+
+ if (attack < 2 * 60)
+ attackBot = true;
+
+ // attacking but not talking more than 2 minutes
+ if (talk > 2 * 60 && talk > 2 * 60)
+ {
+ talkBot = true;
+ str += toString((talk) / 60) + " ";
+ }
+
+ // attacking but not moving more than 2 minutes
+ if (move > 2 * 60 && move > 2 * 60)
+ {
+ moveBot = true;
+ str += toString((move) / 60);
+ }
+
+ // attacking but not other activity more than 2 minutes
+ if (move > 2 * 60 && other > 2 * 60)
+ otherBot = true;
+ }
+
+ if (str.length() > 0)
+ {
+ if (attackBot && talkBot && moveBot && otherBot)
+ str = "bot!! " + str;
+ else if (attackBot && talkBot && moveBot)
+ str = "bot! " + str;
+ else if (talkBot && moveBot)
+ str = "bot " + str;
+ else if (talkBot || moveBot)
+ str = "bot? " + str;
+ }
+ else
+ {
+ str = "ok";
+ }
+
+ widget = new Label(str);
+ mWidgets.push_back(widget);
+
+ }
+
+ signalAfterUpdate();
+ }
+
+ virtual void updateModelInRow(int row _UNUSED_)
+ {
+ }
+
+
+ virtual gcn::Widget *getElementAt(int row, int column) const
+ {
+ return mWidgets[WIDGET_AT(row, column)];
+ }
+
+ virtual void freeWidgets()
+ {
+ for (std::vector<gcn::Widget *>::const_iterator it = mWidgets.begin();
+ it != mWidgets.end(); it++)
+ {
+ delete *it;
+ }
+
+ mWidgets.clear();
+ }
+
+protected:
+ std::vector<Being*> mPlayers;
+ std::vector<gcn::Widget*> mWidgets;
+};
+
+
+BotCheckerWindow::BotCheckerWindow():
+ Window(_("Bot Checker")),
+ mEnabled(false)
+{
+ int w = 500;
+ int h = 250;
+
+ mLastUpdateTime = 0;
+ mNeedUpdate = false;
+
+ mTableModel = new UsersTableModel();
+ mTable = new GuiTable(mTableModel);
+ mTable->setOpaque(false);
+ mTable->setLinewiseSelection(true);
+ mTable->setWrappingEnabled(true);
+ mTable->setActionEventId("skill");
+ mTable->addActionListener(this);
+
+ mPlayerTableTitleModel = new StaticTableModel(1, COLUMNS_NR);
+ mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH);
+
+ for (int f = 0; f < 4; f++)
+ {
+ mPlayerTableTitleModel->fixColumnWidth(TIME_COLUMN + f,
+ TIME_COLUMN_WIDTH);
+ }
+
+ mPlayerTitleTable = new GuiTable(mPlayerTableTitleModel);
+ //mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf));
+ mPlayerTitleTable->setHeight(1);
+
+ mPlayerTableTitleModel->set(0, 0, new Label(_("Name")));
+ mPlayerTableTitleModel->set(0, 1, new Label(_("Attack")));
+ mPlayerTableTitleModel->set(0, 2, new Label(_("Talk")));
+ mPlayerTableTitleModel->set(0, 3, new Label(_("Move")));
+ mPlayerTableTitleModel->set(0, 4, new Label(_("Result")));
+
+ mPlayerTitleTable->setLinewiseSelection(true);
+
+ setWindowName("BotCheckerWindow");
+ setCloseButton(true);
+ setDefaultSize(w, h, ImageRect::CENTER);
+
+ playersScrollArea = new ScrollArea(mTable);
+
+ mIncButton = new Button(_("Reset"), "reset", this);
+ playersScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mPlayerTitleTable->setPosition(getPadding(), getPadding());
+ mPlayerTitleTable->setWidth(w - 10);
+ mPlayerTitleTable->setHeight(20);
+
+ playersScrollArea->setPosition(getPadding(), 20 + 2*getPadding());
+ playersScrollArea->setWidth(w - 15);
+ playersScrollArea->setHeight(h - 80);
+
+ mIncButton->setPosition(getPadding(), 190 + 3*getPadding());
+ mIncButton->setWidth(80);
+ mIncButton->setHeight(20);
+
+ add(mPlayerTitleTable);
+ add(playersScrollArea);
+ add(mIncButton);
+
+ center();
+
+ setWidth(w);
+ setHeight(h);
+ loadWindowState();
+
+ config.addListener("enableBotCheker", this);
+ mEnabled = config.getBoolValue("enableBotCheker");
+}
+
+BotCheckerWindow::~BotCheckerWindow()
+{
+ config.removeListener("enableBotCheker", this);
+}
+
+void BotCheckerWindow::logic()
+{
+ if (mEnabled && mTableModel)
+ {
+ unsigned int nowTime = cur_time;
+ if (nowTime - mLastUpdateTime > 5 && mNeedUpdate)
+ {
+ mTableModel->playersUpdated();
+ mNeedUpdate = false;
+ mLastUpdateTime = nowTime;
+ }
+ else if (nowTime - mLastUpdateTime > 15)
+ {
+ mTableModel->playersUpdated();
+ mNeedUpdate = false;
+ mLastUpdateTime = nowTime;
+ }
+ }
+
+ Window::logic();
+}
+
+void BotCheckerWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "reset")
+ {
+ reset();
+ mNeedUpdate = true;
+ }
+
+}
+
+void BotCheckerWindow::update()
+{
+}
+
+void BotCheckerWindow::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+}
+
+void BotCheckerWindow::updateList()
+{
+ if (mTableModel)
+ mNeedUpdate = true;
+}
+
+void BotCheckerWindow::reset()
+{
+ if (actorSpriteManager)
+ {
+ std::set<ActorSprite*> beings = actorSpriteManager->getAll();
+ ActorSprites::iterator i = beings.begin();
+ for (ActorSprites::const_iterator i = beings.begin();
+ i != beings.end(); i++)
+ {
+ Being *being = dynamic_cast<Being*>(*i);
+
+ if (being && being->getType() == Being::PLAYER
+ && being != player_node && being->getName() != "")
+ {
+ being->resetCounters();
+ }
+ }
+ }
+}
+
+void BotCheckerWindow::optionChanged(const std::string &name)
+{
+ if (name == "enableBotCheker")
+ mEnabled = config.getBoolValue("enableBotCheker");
+}
diff --git a/src/gui/botcheckerwindow.h b/src/gui/botcheckerwindow.h
new file mode 100644
index 000000000..cb225d3e7
--- /dev/null
+++ b/src/gui/botcheckerwindow.h
@@ -0,0 +1,95 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef BOTCHECKER_H
+#define BOTCHECKER_H
+
+#include "configlistener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <vector>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+struct BOTCHK
+{
+ short id; /**< Index into "botchecker_db" array */
+ short lv, sp;
+};
+
+class GuiTable;
+class ScrollArea;
+class UsersTableModel;
+class StaticTableModel;
+
+class BotCheckerWindow : public Window, public gcn::ActionListener,
+ public ConfigListener
+{
+ public:
+ friend class UsersTableModel;
+
+ /**
+ * Constructor.
+ */
+ BotCheckerWindow();
+
+ /**
+ * Destructor.
+ */
+ ~BotCheckerWindow();
+
+ void action(const gcn::ActionEvent &event);
+
+ void update();
+
+ void logic();
+
+ void widgetResized(const gcn::Event &event);
+
+ void updateList();
+
+ void reset();
+
+ void optionChanged(const std::string &name);
+
+ private:
+ GuiTable *mTable;
+ ScrollArea *playersScrollArea;
+ UsersTableModel *mTableModel;
+ StaticTableModel *mPlayerTableTitleModel;
+ GuiTable *mPlayerTitleTable;
+ gcn::Button *mIncButton;
+ int mLastUpdateTime;
+ bool mNeedUpdate;
+ bool mEnabled;
+};
+
+extern BotCheckerWindow *botCheckerWindow;
+
+#endif
diff --git a/src/gui/buy.cpp b/src/gui/buy.cpp
new file mode 100644
index 000000000..2be6c65a4
--- /dev/null
+++ b/src/gui/buy.cpp
@@ -0,0 +1,323 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/buy.h"
+
+#include "shopitem.h"
+#include "units.h"
+
+#include "gui/setup.h"
+#include "gui/trade.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/shopitems.h"
+#include "gui/widgets/shoplistbox.h"
+#include "gui/widgets/slider.h"
+
+#include "shopitem.h"
+#include "units.h"
+
+#include "net/buysellhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+BuyDialog::DialogList BuyDialog::instances;
+
+BuyDialog::BuyDialog(int npcId):
+ Window(_("Buy")),
+ mNpcId(npcId), mMoney(0), mAmountItems(0), mMaxItems(0), mNick("")
+{
+ init();
+}
+
+BuyDialog::BuyDialog(std::string nick):
+ Window(_("Buy")),
+ mNpcId(-1), mMoney(0), mAmountItems(0), mMaxItems(0), mNick(nick)
+{
+ init();
+ logger->log("BuyDialog::BuyDialog nick:" + mNick);
+}
+
+void BuyDialog::init()
+{
+ setWindowName("Buy");
+ setResizable(true);
+ setCloseButton(true);
+ setMinWidth(260);
+ setMinHeight(230);
+ setDefaultSize(260, 230, ImageRect::CENTER);
+
+ mShopItems = new ShopItems;
+
+ mShopItemList = new ShopListBox(mShopItems, mShopItems);
+ mScrollArea = new ScrollArea(mShopItemList);
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mSlider = new Slider(1.0);
+ mQuantityLabel = new Label(strprintf("%d / %d", mAmountItems, mMaxItems));
+ mQuantityLabel->setAlignment(gcn::Graphics::CENTER);
+ mMoneyLabel = new Label(strprintf(_("Price: %s / Total: %s"),
+ "", ""));
+
+ // TRANSLATORS: This is a narrow symbol used to denote 'increasing'.
+ // You may change this symbol if your language uses another.
+ mIncreaseButton = new Button(_("+"), "inc", this);
+ // TRANSLATORS: This is a narrow symbol used to denote 'decreasing'.
+ // You may change this symbol if your language uses another.
+ mDecreaseButton = new Button(_("-"), "dec", this);
+ mBuyButton = new Button(_("Buy"), "buy", this);
+ mQuitButton = new Button(_("Quit"), "quit", this);
+ mAddMaxButton = new Button(_("Max"), "max", this);
+
+ mDecreaseButton->adjustSize();
+ mDecreaseButton->setWidth(mIncreaseButton->getWidth());
+
+ mIncreaseButton->setEnabled(false);
+ mDecreaseButton->setEnabled(false);
+ mBuyButton->setEnabled(false);
+ mSlider->setEnabled(false);
+
+ mSlider->setActionEventId("slider");
+ mSlider->addActionListener(this);
+ mShopItemList->addSelectionListener(this);
+
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+
+ place(0, 0, mScrollArea, 8, 5).setPadding(3);
+ place(0, 5, mDecreaseButton);
+ place(1, 5, mSlider, 3);
+ place(4, 5, mIncreaseButton);
+ place(5, 5, mQuantityLabel, 2);
+ place(7, 5, mAddMaxButton);
+ place(0, 6, mMoneyLabel, 8);
+ place(6, 7, mBuyButton);
+ place(7, 7, mQuitButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ center();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(true);
+}
+
+BuyDialog::~BuyDialog()
+{
+ delete mShopItems;
+ mShopItems = 0;
+
+ instances.remove(this);
+}
+
+void BuyDialog::setMoney(int amount)
+{
+ mMoney = amount;
+ mShopItemList->setPlayersMoney(amount);
+
+ updateButtonsAndLabels();
+}
+
+void BuyDialog::reset()
+{
+ mShopItems->clear();
+ mShopItemList->adjustSize();
+
+ // Reset previous selected items to prevent failing asserts
+ mShopItemList->setSelected(-1);
+ mSlider->setValue(0);
+
+ setMoney(0);
+}
+
+void BuyDialog::addItem(int id, int amount, int price)
+{
+ mShopItems->addItem(id, amount, price);
+ mShopItemList->adjustSize();
+}
+
+void BuyDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "quit")
+ {
+ close();
+ return;
+ }
+
+ int selectedItem = mShopItemList->getSelected();
+
+ // The following actions require a valid selection
+ if (selectedItem < 0 ||
+ selectedItem >= static_cast<int>(mShopItems->getNumberOfElements()))
+ {
+ return;
+ }
+
+ if (event.getId() == "slider")
+ {
+ mAmountItems = static_cast<int>(mSlider->getValue());
+ updateButtonsAndLabels();
+ }
+ else if (event.getId() == "inc" && mAmountItems < mMaxItems)
+ {
+ mAmountItems++;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (event.getId() == "dec" && mAmountItems > 1)
+ {
+ mAmountItems--;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (event.getId() == "max")
+ {
+ mAmountItems = mMaxItems;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ // TODO: Actually we'd have a bug elsewhere if this check for the number
+ // of items to be bought ever fails, Bertram removed the assertions, is
+ // there a better way to ensure this fails in an _obvious_ way in C++?
+ else if (event.getId() == "buy" && mAmountItems > 0 &&
+ mAmountItems <= mMaxItems)
+ {
+ if (mNpcId != -1)
+ {
+ Net::getNpcHandler()->buyItem(mNpcId,
+ mShopItems->at(selectedItem)->getId(), mAmountItems);
+
+ // Update money and adjust the max number of items that can be bought
+ mMaxItems -= mAmountItems;
+ setMoney(mMoney -
+ mAmountItems * mShopItems->at(selectedItem)->getPrice());
+
+ // Reset selection
+ mAmountItems = 1;
+ mSlider->setValue(1);
+ mSlider->gcn::Slider::setScale(1, mMaxItems);
+ }
+ else if (tradeWindow)
+ {
+ ShopItem *item = mShopItems->at(selectedItem);
+ if (item)
+ {
+ Net::getBuySellHandler()->sendBuyRequest(mNick,
+ item, mAmountItems);
+// logger->log("buy button mNick:" + mNick);
+ if (tradeWindow)
+ {
+ tradeWindow->addAutoMoney(mNick,
+ item->getPrice() * mAmountItems);
+ }
+ }
+ }
+ }
+}
+
+void BuyDialog::valueChanged(const gcn::SelectionEvent &event _UNUSED_)
+{
+ // Reset amount of items and update labels
+ mAmountItems = 1;
+ mSlider->setValue(1);
+
+ updateButtonsAndLabels();
+ mSlider->gcn::Slider::setScale(1, mMaxItems);
+}
+
+void BuyDialog::updateButtonsAndLabels()
+{
+ const int selectedItem = mShopItemList->getSelected();
+ int price = 0;
+
+ if (selectedItem > -1)
+ {
+ ShopItem * item = mShopItems->at(selectedItem);
+ if (item)
+ {
+ int itemPrice = item->getPrice();
+
+ // Calculate how many the player can afford
+ if (itemPrice)
+ mMaxItems = mMoney / itemPrice;
+ else
+ mMaxItems = 1;
+
+ if (item->getQuantity() > 0 && mMaxItems > item->getQuantity())
+ mMaxItems = item->getQuantity();
+
+ if (mAmountItems > mMaxItems)
+ mAmountItems = mMaxItems;
+
+ // Calculate price of pending purchase
+ price = mAmountItems * itemPrice;
+ }
+ }
+ else
+ {
+ mMaxItems = 0;
+ mAmountItems = 0;
+ }
+
+ // Enable or disable buttons and slider
+ mIncreaseButton->setEnabled(mAmountItems < mMaxItems);
+ mDecreaseButton->setEnabled(mAmountItems > 1);
+ mBuyButton->setEnabled(mAmountItems > 0);
+ mSlider->setEnabled(mMaxItems > 1);
+
+ // Update quantity and money labels
+ mQuantityLabel->setCaption(strprintf("%d / %d", mAmountItems, mMaxItems));
+ mMoneyLabel->setCaption(strprintf(_("Price: %s / Total: %s"),
+ Units::formatCurrency(price).c_str(),
+ Units::formatCurrency(mMoney - price).c_str()));
+}
+
+void BuyDialog::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (visible && mShopItemList)
+ mShopItemList->requestFocus();
+ else
+ scheduleDelete();
+}
+
+void BuyDialog::closeAll()
+{
+ DialogList::iterator it = instances.begin();
+ DialogList::iterator it_end = instances.end();
+
+ for (; it != it_end; it++)
+ {
+ if (*it)
+ (*it)->close();
+ }
+}
diff --git a/src/gui/buy.h b/src/gui/buy.h
new file mode 100644
index 000000000..5133414c1
--- /dev/null
+++ b/src/gui/buy.h
@@ -0,0 +1,151 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BUY_H
+#define BUY_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class ShopItems;
+class ShopListBox;
+class ListBox;
+
+/**
+ * The buy dialog.
+ *
+ * \ingroup Interface
+ */
+class BuyDialog : public Window, public gcn::ActionListener,
+ public gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ BuyDialog(int npcId);
+
+ /**
+ * Constructor.
+ */
+ BuyDialog(std::string nick);
+
+ /**
+ * Destructor
+ */
+ ~BuyDialog();
+
+ void init();
+
+ /**
+ * Resets the dialog, clearing shop inventory.
+ */
+ void reset();
+
+ /**
+ * Sets the amount of available money.
+ */
+ void setMoney(int amount);
+
+ /**
+ * Adds an item to the shop inventory.
+ */
+ void addItem(int id, int amount, int price);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Returns the number of items in the shop inventory.
+ */
+ int getNumberOfElements();
+
+ /**
+ * Updates the labels according to the selected item.
+ */
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ /**
+ * Returns the name of item number i in the shop inventory.
+ */
+ std::string getElementAt(int i);
+
+ /**
+ * Updates the state of buttons and labels.
+ */
+ void updateButtonsAndLabels();
+
+ /**
+ * Sets the visibility of this window.
+ */
+ void setVisible(bool visible);
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive()
+ { return !instances.empty(); }
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+ private:
+ typedef std::list<BuyDialog*> DialogList;
+ static DialogList instances;
+
+ int mNpcId;
+
+ gcn::Button *mBuyButton;
+ gcn::Button *mQuitButton;
+ gcn::Button *mAddMaxButton;
+ gcn::Button *mIncreaseButton;
+ gcn::Button *mDecreaseButton;
+ ShopListBox *mShopItemList;
+ gcn::ScrollArea *mScrollArea;
+ gcn::Label *mMoneyLabel;
+ gcn::Label *mQuantityLabel;
+ gcn::Slider *mSlider;
+
+ ShopItems *mShopItems;
+
+ int mMoney;
+ int mAmountItems;
+ int mMaxItems;
+ std::string mNick;
+};
+
+#endif
diff --git a/src/gui/buysell.cpp b/src/gui/buysell.cpp
new file mode 100644
index 000000000..f6a1fc193
--- /dev/null
+++ b/src/gui/buysell.cpp
@@ -0,0 +1,137 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/buysell.h"
+
+#include "gui/setup.h"
+
+#include "gui/widgets/button.h"
+
+#include "net/buysellhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "utils/gettext.h"
+
+BuySellDialog::DialogList BuySellDialog::instances;
+
+BuySellDialog::BuySellDialog(int npcId):
+ Window(_("Shop")),
+ mNpcId(npcId),
+ mNick(""),
+ mBuyButton(0)
+{
+ init();
+}
+
+BuySellDialog::BuySellDialog(std::string nick):
+ Window(_("Shop")),
+ mNpcId(-1),
+ mNick(nick),
+ mBuyButton(0)
+{
+ init();
+}
+
+void BuySellDialog::init()
+{
+ setWindowName("BuySell");
+ //setupWindow->registerWindowForReset(this);
+ setCloseButton(true);
+
+ static const char *buttonNames[] =
+ {
+ N_("Buy"), N_("Sell"), N_("Cancel"), 0
+ };
+ int x = 10, y = 10;
+
+ for (const char **curBtn = buttonNames; *curBtn; curBtn++)
+ {
+ Button *btn = new Button(gettext(*curBtn), *curBtn, this);
+ if (!mBuyButton)
+ mBuyButton = btn; // For focus request
+ btn->setPosition(x, y);
+ add(btn);
+ x += btn->getWidth() + 10;
+ }
+ mBuyButton->requestFocus();
+
+ setContentSize(x, 2 * y + mBuyButton->getHeight());
+
+ center();
+ setDefaultSize();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(true);
+}
+
+BuySellDialog::~BuySellDialog()
+{
+ instances.remove(this);
+}
+
+void BuySellDialog::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (visible)
+ {
+ if (mBuyButton)
+ mBuyButton->requestFocus();
+ }
+ else
+ {
+ scheduleDelete();
+ }
+}
+
+void BuySellDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "Buy")
+ {
+ if (mNpcId != -1)
+ Net::getNpcHandler()->buy(mNpcId);
+ else
+ Net::getBuySellHandler()->requestSellList(mNick);
+ }
+ else if (event.getId() == "Sell")
+ {
+ if (mNpcId != -1)
+ Net::getNpcHandler()->sell(mNpcId);
+ else
+ Net::getBuySellHandler()->requestBuyList(mNick);
+ }
+
+ close();
+}
+
+void BuySellDialog::closeAll()
+{
+ DialogList::iterator it = instances.begin();
+ DialogList::iterator it_end = instances.end();
+
+ for (; it != it_end; it++)
+ {
+ if (*it)
+ (*it)->close();
+ }
+}
diff --git a/src/gui/buysell.h b/src/gui/buysell.h
new file mode 100644
index 000000000..f533252ee
--- /dev/null
+++ b/src/gui/buysell.h
@@ -0,0 +1,78 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BUYSELL_H
+#define BUYSELL_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+/**
+ * A dialog to choose between buying or selling at a shop.
+ *
+ * \ingroup Interface
+ */
+class BuySellDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor. The action listener passed will receive "sell", "buy"
+ * or "cancel" events when the respective buttons are pressed.
+ *
+ * @see Window::Window
+ */
+ BuySellDialog(int npcId);
+
+ BuySellDialog(std::string nick);
+
+ virtual ~BuySellDialog();
+
+ void init();
+
+ void setVisible(bool visible);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive()
+ { return !instances.empty(); }
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+ private:
+ typedef std::list<BuySellDialog*> DialogList;
+ static DialogList instances;
+
+ int mNpcId;
+ std::string mNick;
+ gcn::Button *mBuyButton;
+};
+
+#endif
diff --git a/src/gui/changeemaildialog.cpp b/src/gui/changeemaildialog.cpp
new file mode 100644
index 000000000..4485e0199
--- /dev/null
+++ b/src/gui/changeemaildialog.cpp
@@ -0,0 +1,167 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/changeemaildialog.h"
+
+#include "client.h"
+#include "log.h"
+
+#include "gui/register.h"
+#include "gui/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <string>
+#include <sstream>
+
+ChangeEmailDialog::ChangeEmailDialog(LoginData *loginData):
+ Window(_("Change Email Address"), true),
+ mWrongDataNoticeListener(new WrongDataNoticeListener),
+ mLoginData(loginData)
+{
+ gcn::Label *accountLabel = new Label(strprintf(_("Account: %s"),
+ mLoginData->username.c_str()));
+ gcn::Label *newEmailLabel = new Label(_("Type new email address twice:"));
+ mFirstEmailField = new TextField;
+ mSecondEmailField = new TextField;
+ mChangeEmailButton = new Button(_("Change Email Address"),
+ "change_email", this);
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+
+ const int width = 200;
+ const int height = 130;
+ setContentSize(width, height);
+
+ accountLabel->setPosition(5, 5);
+ accountLabel->setWidth(130);
+
+ newEmailLabel->setPosition(
+ 5, accountLabel->getY() + accountLabel->getHeight() + 7);
+ newEmailLabel->setWidth(width - 5);
+
+ mFirstEmailField->setPosition(
+ 5, newEmailLabel->getY() + newEmailLabel->getHeight() + 7);
+ mFirstEmailField->setWidth(130);
+
+ mSecondEmailField->setPosition(
+ 5, mFirstEmailField->getY() + mFirstEmailField->getHeight() + 7);
+ mSecondEmailField->setWidth(130);
+
+ mCancelButton->setPosition(
+ width - 5 - mCancelButton->getWidth(),
+ height - 5 - mCancelButton->getHeight());
+ mChangeEmailButton->setPosition(
+ mCancelButton->getX() - 5 - mChangeEmailButton->getWidth(),
+ mCancelButton->getY());
+
+ add(accountLabel);
+ add(newEmailLabel);
+ add(mFirstEmailField);
+ add(mSecondEmailField);
+ add(mChangeEmailButton);
+ add(mCancelButton);
+
+ center();
+ setVisible(true);
+ mFirstEmailField->requestFocus();
+
+ mFirstEmailField->setActionEventId("change_email");
+ mSecondEmailField->setActionEventId("change_email");
+}
+
+ChangeEmailDialog::~ChangeEmailDialog()
+{
+ delete mWrongDataNoticeListener;
+ mWrongDataNoticeListener = 0;
+}
+
+void ChangeEmailDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "cancel")
+ {
+ Client::setState(STATE_CHAR_SELECT);
+ }
+ else if (event.getId() == "change_email")
+ {
+
+ const std::string username = mLoginData->username.c_str();
+ const std::string newFirstEmail = mFirstEmailField->getText();
+ const std::string newSecondEmail = mSecondEmailField->getText();
+ logger->log("ChangeEmailDialog::Email change, Username is %s",
+ username.c_str());
+
+ std::stringstream errorMessage;
+ int error = 0;
+
+ unsigned int min = Net::getLoginHandler()->getMinPasswordLength();
+ unsigned int max = Net::getLoginHandler()->getMaxPasswordLength();
+
+ if (newFirstEmail.length() < min)
+ {
+ // First email address too short
+ errorMessage << strprintf(_("The new email address needs to be at "
+ "least %d characters long."), min);
+ error = 1;
+ }
+ else if (newFirstEmail.length() > max - 1 )
+ {
+ // First email address too long
+ errorMessage << strprintf(_("The new email address needs to be "
+ "less than %d characters long."), max);
+ error = 1;
+ }
+ else if (newFirstEmail != newSecondEmail)
+ {
+ // Second Pass mismatch
+ errorMessage << _("The email address entries mismatch.");
+ error = 2;
+ }
+
+ if (error > 0)
+ {
+ if (error == 1)
+ mWrongDataNoticeListener->setTarget(this->mFirstEmailField);
+ else if (error == 2)
+ mWrongDataNoticeListener->setTarget(this->mSecondEmailField);
+
+ OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str());
+ if (dlg)
+ dlg->addActionListener(mWrongDataNoticeListener);
+ }
+ else
+ {
+ // No errors detected, change account password.
+ mChangeEmailButton->setEnabled(false);
+ // Set the new email address
+ mLoginData->email = newFirstEmail;
+ Client::setState(STATE_CHANGEEMAIL_ATTEMPT);
+ }
+ }
+}
diff --git a/src/gui/changeemaildialog.h b/src/gui/changeemaildialog.h
new file mode 100644
index 000000000..7e5f04fa4
--- /dev/null
+++ b/src/gui/changeemaildialog.h
@@ -0,0 +1,78 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_CHANGEEMAIL_H
+#define GUI_CHANGEEMAIL_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class LoginData;
+class OkDialog;
+class WrongDataNoticeListener;
+
+/**
+ * The Change email dialog.
+ *
+ * \ingroup Interface
+ */
+class ChangeEmailDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ ChangeEmailDialog(LoginData *loginData);
+
+ /**
+ * Destructor.
+ */
+ ~ChangeEmailDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * This is used to pass the pointer to where the new email should be
+ * put when the dialog finishes.
+ */
+ static void setEmail(std::string *email);
+
+ private:
+ gcn::TextField *mFirstEmailField;
+ gcn::TextField *mSecondEmailField;
+
+ gcn::Button *mChangeEmailButton;
+ gcn::Button *mCancelButton;
+
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+
+ LoginData *mLoginData;
+};
+
+#endif // GUI_CHANGEEMAIL_H
diff --git a/src/gui/changepassworddialog.cpp b/src/gui/changepassworddialog.cpp
new file mode 100644
index 000000000..bc29c9264
--- /dev/null
+++ b/src/gui/changepassworddialog.cpp
@@ -0,0 +1,157 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "changepassworddialog.h"
+
+#include "client.h"
+#include "log.h"
+
+#include "gui/register.h"
+#include "gui/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/passwordfield.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <string>
+#include <sstream>
+
+ChangePasswordDialog::ChangePasswordDialog(LoginData *loginData):
+ Window(_("Change Password"), true),
+ mWrongDataNoticeListener(new WrongDataNoticeListener),
+ mLoginData(loginData)
+{
+ gcn::Label *accountLabel = new Label(
+ strprintf(_("Account: %s"), mLoginData->username.c_str()));
+ mOldPassField = new PasswordField;
+ mFirstPassField = new PasswordField;
+ mSecondPassField = new PasswordField;
+ mChangePassButton = new Button(_("Change Password"), "change_password",
+ this);
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+
+ place(0, 0, accountLabel, 3);
+ place(0, 1, new Label(_("Password:")), 3);
+ place(0, 2, mOldPassField, 3).setPadding(1);
+ place(0, 3, new Label(_("Type new password twice:")), 3);
+ place(0, 4, mFirstPassField, 3).setPadding(1);
+ place(0, 5, mSecondPassField, 3).setPadding(1);
+ place(1, 6, mCancelButton);
+ place(2, 6, mChangePassButton);
+ reflowLayout(200);
+
+ center();
+ setVisible(true);
+ mOldPassField->requestFocus();
+
+ mOldPassField->setActionEventId("change_password");
+ mFirstPassField->setActionEventId("change_password");
+ mSecondPassField->setActionEventId("change_password");
+}
+
+ChangePasswordDialog::~ChangePasswordDialog()
+{
+ delete mWrongDataNoticeListener;
+ mWrongDataNoticeListener = 0;
+}
+
+void ChangePasswordDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "cancel")
+ {
+ Client::setState(STATE_CHAR_SELECT);
+ }
+ else if (event.getId() == "change_password")
+ {
+
+ const std::string username = mLoginData->username.c_str();
+ const std::string oldPassword = mOldPassField->getText();
+ const std::string newFirstPass = mFirstPassField->getText();
+ const std::string newSecondPass = mSecondPassField->getText();
+ logger->log("ChangePasswordDialog::Password change, Username is %s",
+ username.c_str());
+
+ std::stringstream errorMessage;
+ int error = 0;
+
+ unsigned int min = Net::getLoginHandler()->getMinPasswordLength();
+ unsigned int max = Net::getLoginHandler()->getMaxPasswordLength();
+
+ // Check old Password
+ if (oldPassword.empty())
+ {
+ // No old password
+ errorMessage << _("Enter the old password first.");
+ error = 1;
+ }
+ else if (newFirstPass.length() < min)
+ {
+ // First password too short
+ errorMessage << strprintf(_("The new password needs to be at least"
+ " %d characters long."), min);
+ error = 2;
+ }
+ else if (newFirstPass.length() > max - 1 )
+ {
+ // First password too long
+ errorMessage << strprintf(_("The new password needs to be less "
+ "than %d characters long."), max);
+ error = 2;
+ }
+ else if (newFirstPass != newSecondPass)
+ {
+ // Second Pass mismatch
+ errorMessage << _("The new password entries mismatch.");
+ error = 3;
+ }
+
+ if (error > 0)
+ {
+ if (error == 1)
+ mWrongDataNoticeListener->setTarget(this->mOldPassField);
+ else if (error == 2)
+ mWrongDataNoticeListener->setTarget(this->mFirstPassField);
+ else if (error == 3)
+ mWrongDataNoticeListener->setTarget(this->mSecondPassField);
+
+ OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str());
+ dlg->addActionListener(mWrongDataNoticeListener);
+ }
+ else
+ {
+ // No errors detected, change account password.
+ mChangePassButton->setEnabled(false);
+ // Set the new password
+ mLoginData->password = oldPassword;
+ mLoginData->newPassword = newFirstPass;
+ Client::setState(STATE_CHANGEPASSWORD_ATTEMPT);
+ }
+ }
+}
diff --git a/src/gui/changepassworddialog.h b/src/gui/changepassworddialog.h
new file mode 100644
index 000000000..361debe45
--- /dev/null
+++ b/src/gui/changepassworddialog.h
@@ -0,0 +1,73 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHANGEPASSWORDDIALOG_H
+#define CHANGEPASSWORDDIALOG_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class LoginData;
+class OkDialog;
+class WrongDataNoticeListener;
+
+/**
+ * The Change password dialog.
+ *
+ * \ingroup Interface
+ */
+class ChangePasswordDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ ChangePasswordDialog(LoginData *loginData);
+
+ /**
+ * Destructor
+ */
+ ~ChangePasswordDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ gcn::TextField *mOldPassField;
+ gcn::TextField *mFirstPassField;
+ gcn::TextField *mSecondPassField;
+
+ gcn::Button *mChangePassButton;
+ gcn::Button *mCancelButton;
+
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+
+ LoginData *mLoginData;
+};
+
+#endif
diff --git a/src/gui/charcreatedialog.cpp b/src/gui/charcreatedialog.cpp
new file mode 100644
index 000000000..4dc6251b9
--- /dev/null
+++ b/src/gui/charcreatedialog.cpp
@@ -0,0 +1,372 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/charcreatedialog.h"
+
+#include "game.h"
+#include "localplayer.h"
+#include "main.h"
+#include "units.h"
+
+#include "gui/charselectdialog.h"
+#include "gui/confirmdialog.h"
+#include "gui/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/playerbox.h"
+#include "gui/widgets/radiobutton.h"
+#include "gui/widgets/slider.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/charhandler.h"
+#include "net/messageout.h"
+#include "net/net.h"
+
+#include "resources/colordb.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+
+CharCreateDialog::CharCreateDialog(CharSelectDialog *parent, int slot):
+ Window(_("Create Character"), true, parent),
+ mCharSelectDialog(parent),
+ mSlot(slot)
+{
+ mPlayer = new Being(0, ActorSprite::PLAYER, 0, NULL);
+ mPlayer->setGender(GENDER_MALE);
+
+ int numberOfHairColors = ColorDB::size();
+
+ mHairStyle = rand() % mPlayer->getNumOfHairstyles();
+ mHairColor = rand() % numberOfHairColors;
+ updateHair();
+
+ mNameField = new TextField("");
+ mNameLabel = new Label(_("Name:"));
+ // TRANSLATORS: This is a narrow symbol used to denote 'next'.
+ // You may change this symbol if your language uses another.
+ mNextHairColorButton = new Button(_(">"), "nextcolor", this);
+ // TRANSLATORS: This is a narrow symbol used to denote 'previous'.
+ // You may change this symbol if your language uses another.
+ mPrevHairColorButton = new Button(_("<"), "prevcolor", this);
+ mHairColorLabel = new Label(_("Hair color:"));
+ mNextHairStyleButton = new Button(_(">"), "nextstyle", this);
+ mPrevHairStyleButton = new Button(_("<"), "prevstyle", this);
+ mHairStyleLabel = new Label(_("Hair style:"));
+ mCreateButton = new Button(_("Create"), "create", this);
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+ mMale = new RadioButton(_("Male"), "gender");
+ mFemale = new RadioButton(_("Female"), "gender");
+
+ // Default to a Male character
+ mMale->setSelected(true);
+
+ mMale->setActionEventId("gender");
+ mFemale->setActionEventId("gender");
+
+ mMale->addActionListener(this);
+ mFemale->addActionListener(this);
+
+ mPlayerBox = new PlayerBox(mPlayer);
+ mPlayerBox->setWidth(74);
+
+ mNameField->setActionEventId("create");
+ mNameField->addActionListener(this);
+
+ mAttributesLeft = new Label(
+ strprintf(_("Please distribute %d points"), 99));
+
+ int w = 200;
+ int h = 330;
+ setContentSize(w, h);
+ mPlayerBox->setDimension(gcn::Rectangle(80, 30, 110, 85));
+ mNameLabel->setPosition(5, 5);
+ mNameField->setDimension(
+ gcn::Rectangle(45, 5, w - 45 - 7, mNameField->getHeight()));
+ mPrevHairColorButton->setPosition(90, 35);
+ mNextHairColorButton->setPosition(165, 35);
+ mHairColorLabel->setPosition(5, 40);
+ mPrevHairStyleButton->setPosition(90, 64);
+ mNextHairStyleButton->setPosition(165, 64);
+ mHairStyleLabel->setPosition(5, 70);
+ mAttributesLeft->setPosition(15, 280);
+ updateSliders();
+ mCancelButton->setPosition(
+ w - 5 - mCancelButton->getWidth(),
+ h - 5 - mCancelButton->getHeight());
+ mCreateButton->setPosition(
+ mCancelButton->getX() - 5 - mCreateButton->getWidth(),
+ h - 5 - mCancelButton->getHeight());
+
+ mMale->setPosition(30, 120);
+ mFemale->setPosition(100, 120);
+
+ add(mPlayerBox);
+ add(mNameField);
+ add(mNameLabel);
+ add(mNextHairColorButton);
+ add(mPrevHairColorButton);
+ add(mHairColorLabel);
+ add(mNextHairStyleButton);
+ add(mPrevHairStyleButton);
+ add(mHairStyleLabel);
+ add(mAttributesLeft);
+ add(mCreateButton);
+ add(mCancelButton);
+
+ add(mMale);
+ add(mFemale);
+
+ center();
+ setVisible(true);
+ mNameField->requestFocus();
+}
+
+CharCreateDialog::~CharCreateDialog()
+{
+ delete mPlayer;
+ mPlayer = 0;
+
+ // Make sure the char server handler knows that we're gone
+ Net::getCharHandler()->setCharCreateDialog(0);
+}
+
+void CharCreateDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "create")
+ {
+ if (Net::getNetworkType() == ServerInfo::MANASERV
+ || getName().length() >= 4)
+ {
+ // Attempt to create the character
+ mCreateButton->setEnabled(false);
+
+ std::vector<int> atts;
+ for (unsigned i = 0; i < mAttributeSlider.size(); i++)
+ {
+ atts.push_back(static_cast<int>(
+ mAttributeSlider[i]->getValue()));
+ }
+
+ int characterSlot = mSlot;
+ // On Manaserv, the slots start at 1, so we offset them.
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ ++characterSlot;
+
+ Net::getCharHandler()->newCharacter(getName(), characterSlot,
+ mFemale->isSelected(),
+ mHairStyle,
+ mHairColor, atts);
+ }
+ else
+ {
+ new OkDialog(_("Error"),
+ _("Your name needs to be at least 4 characters."),
+ true, this);
+ }
+ }
+ else if (event.getId() == "cancel")
+ {
+ scheduleDelete();
+ }
+ else if (event.getId() == "nextcolor")
+ {
+ mHairColor++;
+ updateHair();
+ }
+ else if (event.getId() == "prevcolor")
+ {
+ mHairColor--;
+ updateHair();
+ }
+ else if (event.getId() == "nextstyle")
+ {
+ mHairStyle++;
+ updateHair();
+ }
+ else if (event.getId() == "prevstyle")
+ {
+ mHairStyle--;
+ updateHair();
+ }
+ else if (event.getId() == "statslider")
+ {
+ updateSliders();
+ }
+ else if (event.getId() == "gender")
+ {
+ if (mMale->isSelected())
+ mPlayer->setGender(GENDER_MALE);
+ else
+ mPlayer->setGender(GENDER_FEMALE);
+ }
+}
+
+std::string CharCreateDialog::getName() const
+{
+ std::string name = mNameField->getText();
+ trim(name);
+ return name;
+}
+
+void CharCreateDialog::updateSliders()
+{
+ for (unsigned i = 0; i < mAttributeSlider.size(); i++)
+ {
+ // Update captions
+ mAttributeValue[i]->setCaption(
+ toString(static_cast<int>(mAttributeSlider[i]->getValue())));
+ mAttributeValue[i]->adjustSize();
+ }
+
+ // Update distributed points
+ int pointsLeft = mMaxPoints - getDistributedPoints();
+ if (pointsLeft == 0)
+ {
+ mAttributesLeft->setCaption(_("Character stats OK"));
+ mCreateButton->setEnabled(true);
+ }
+ else
+ {
+ mCreateButton->setEnabled(false);
+ if (pointsLeft > 0)
+ {
+ mAttributesLeft->setCaption(
+ strprintf(_("Please distribute %d points"), pointsLeft));
+ }
+ else
+ {
+ mAttributesLeft->setCaption(
+ strprintf(_("Please remove %d points"), -pointsLeft));
+ }
+ }
+
+ mAttributesLeft->adjustSize();
+}
+
+void CharCreateDialog::unlock()
+{
+ mCreateButton->setEnabled(true);
+}
+
+int CharCreateDialog::getDistributedPoints() const
+{
+ int points = 0;
+
+ for (unsigned i = 0; i < mAttributeSlider.size(); i++)
+ points += static_cast<int>(mAttributeSlider[i]->getValue());
+ return points;
+}
+
+void CharCreateDialog::setAttributes(const std::vector<std::string> &labels,
+ int available, int min, int max)
+{
+ mMaxPoints = available;
+
+ for (unsigned i = 0; i < mAttributeLabel.size(); i++)
+ {
+ remove(mAttributeLabel[i]);
+ delete mAttributeLabel[i];
+ mAttributeLabel[i] = 0;
+ remove(mAttributeSlider[i]);
+ delete mAttributeSlider[i];
+ mAttributeSlider[i] = 0;
+ remove(mAttributeValue[i]);
+ delete mAttributeValue[i];
+ mAttributeValue[i] = 0;
+ }
+
+ mAttributeLabel.resize(labels.size());
+ mAttributeSlider.resize(labels.size());
+ mAttributeValue.resize(labels.size());
+
+ int w = 200;
+ int h = 330;
+
+ for (unsigned i = 0; i < labels.size(); i++)
+ {
+ mAttributeLabel[i] = new Label(labels[i]);
+ mAttributeLabel[i]->setWidth(70);
+ mAttributeLabel[i]->setPosition(5, 140 + i*20);
+ add(mAttributeLabel[i]);
+
+ mAttributeSlider[i] = new Slider(min, max);
+ mAttributeSlider[i]->setDimension(gcn::Rectangle(75, 140 + i * 20,
+ 100, 10));
+ mAttributeSlider[i]->setActionEventId("statslider");
+ mAttributeSlider[i]->addActionListener(this);
+ add(mAttributeSlider[i]);
+
+ mAttributeValue[i] = new Label(toString(min));
+ mAttributeValue[i]->setPosition(180, 140 + i*20);
+ add(mAttributeValue[i]);
+ }
+
+ mAttributesLeft->setPosition(15, 280);
+ updateSliders();
+
+ mCancelButton->setPosition(
+ w - 5 - mCancelButton->getWidth(),
+ h - 5 - mCancelButton->getHeight());
+ mCreateButton->setPosition(
+ mCancelButton->getX() - 5 - mCreateButton->getWidth(),
+ h - 5 - mCancelButton->getHeight());
+}
+
+void CharCreateDialog::setFixedGender(bool fixed, Gender gender)
+{
+ if (gender == GENDER_FEMALE)
+ {
+ mFemale->setSelected(true);
+ mMale->setSelected(false);
+ }
+ else
+ {
+ mMale->setSelected(true);
+ mFemale->setSelected(false);
+ }
+
+ mPlayer->setGender(gender);
+
+ if (fixed)
+ {
+ mMale->setEnabled(false);
+ mFemale->setEnabled(false);
+ }
+}
+
+void CharCreateDialog::updateHair()
+{
+ mHairStyle %= Being::getNumOfHairstyles();
+ if (mHairStyle < 0)
+ mHairStyle += Being::getNumOfHairstyles();
+
+ mHairColor %= ColorDB::size();
+ if (mHairColor < 0)
+ mHairColor += ColorDB::size();
+
+ mPlayer->setSprite(Net::getCharHandler()->hairSprite(),
+ mHairStyle * -1, ColorDB::get(mHairColor));
+}
diff --git a/src/gui/charcreatedialog.h b/src/gui/charcreatedialog.h
new file mode 100644
index 000000000..018de3f59
--- /dev/null
+++ b/src/gui/charcreatedialog.h
@@ -0,0 +1,122 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHAR_CREATE_DIALOG_H
+#define CHAR_CREATE_DIALOG_H
+
+#include "being.h"
+#include "guichanfwd.h"
+
+#include "gui/charselectdialog.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <string>
+#include <vector>
+
+class LocalPlayer;
+class PlayerBox;
+
+/**
+ * Character creation dialog.
+ *
+ * \ingroup Interface
+ */
+class CharCreateDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ CharCreateDialog(CharSelectDialog *parent, int slot);
+
+ /**
+ * Destructor.
+ */
+ ~CharCreateDialog();
+
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Unlocks the dialog, enabling the create character button again.
+ */
+ void unlock();
+
+ void setAttributes(const std::vector<std::string> &labels,
+ int available,
+ int min, int max);
+
+ void setFixedGender(bool fixed, Gender gender = GENDER_FEMALE);
+
+ private:
+ int getDistributedPoints() const;
+
+ void updateSliders();
+
+ /**
+ * Returns the name of the character to create.
+ */
+ std::string getName() const;
+
+ /**
+ * Communicate character creation to the server.
+ */
+ void attemptCharCreate();
+
+ void updateHair();
+
+ CharSelectDialog *mCharSelectDialog;
+
+ gcn::TextField *mNameField;
+ gcn::Label *mNameLabel;
+ gcn::Button *mNextHairColorButton;
+ gcn::Button *mPrevHairColorButton;
+ gcn::Label *mHairColorLabel;
+ gcn::Button *mNextHairStyleButton;
+ gcn::Button *mPrevHairStyleButton;
+ gcn::Label *mHairStyleLabel;
+
+ gcn::RadioButton *mMale;
+ gcn::RadioButton *mFemale;
+
+ std::vector<gcn::Slider*> mAttributeSlider;
+ std::vector<gcn::Label*> mAttributeLabel;
+ std::vector<gcn::Label*> mAttributeValue;
+ gcn::Label *mAttributesLeft;
+
+ int mMaxPoints;
+ int mUsedPoints;
+
+ gcn::Button *mCreateButton;
+ gcn::Button *mCancelButton;
+
+ Being *mPlayer;
+ PlayerBox *mPlayerBox;
+
+ int mHairStyle;
+ int mHairColor;
+
+ int mSlot;
+};
+
+#endif // CHAR_CREATE_DIALOG_H
diff --git a/src/gui/charselectdialog.cpp b/src/gui/charselectdialog.cpp
new file mode 100644
index 000000000..d95930108
--- /dev/null
+++ b/src/gui/charselectdialog.cpp
@@ -0,0 +1,456 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/charselectdialog.h"
+
+#include "client.h"
+#include "game.h"
+#include "localplayer.h"
+#include "units.h"
+#include "log.h"
+
+#include "gui/changeemaildialog.h"
+#include "gui/changepassworddialog.h"
+#include "gui/charcreatedialog.h"
+#include "gui/confirmdialog.h"
+#include "gui/okdialog.h"
+#include "gui/sdlinput.h"
+#include "gui/unregisterdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/container.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/playerbox.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/charhandler.h"
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/messageout.h"
+#include "net/net.h"
+
+#include "resources/colordb.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+
+#include <string>
+#include <cassert>
+
+// Character slots per row in the dialog
+static const int SLOTS_PER_ROW = 5;
+
+/**
+ * Listener for confirming character deletion.
+ */
+class CharDeleteConfirm : public ConfirmDialog
+{
+ public:
+ CharDeleteConfirm(CharSelectDialog *m, int index):
+ ConfirmDialog(_("Confirm Character Delete"),
+ _("Are you sure you want to delete this character?"),
+ false, false, m),
+ mMaster(m),
+ mIndex(index)
+ {
+ }
+
+ void action(const gcn::ActionEvent &event)
+ {
+ if (event.getId() == "yes" && mMaster)
+ mMaster->attemptCharacterDelete(mIndex);
+
+ ConfirmDialog::action(event);
+ }
+
+ private:
+ CharSelectDialog *mMaster;
+ int mIndex;
+};
+
+class CharacterDisplay : public Container
+{
+ public:
+ CharacterDisplay(CharSelectDialog *charSelectDialog);
+
+ void setCharacter(Net::Character *character);
+
+ Net::Character *getCharacter() const
+ { return mCharacter; }
+
+ void requestFocus();
+
+ void setActive(bool active);
+
+ private:
+ void update();
+
+ Net::Character *mCharacter;
+
+ PlayerBox *mPlayerBox;
+ Label *mName;
+ Label *mLevel;
+ Label *mMoney;
+ Button *mButton;
+ Button *mDelete;
+};
+
+CharSelectDialog::CharSelectDialog(LoginData *loginData):
+ Window(_("Account and Character Management")),
+ mLocked(false),
+ mUnregisterButton(0),
+ mChangeEmailButton(0),
+ mCharacterEntries(0),
+ mLoginData(loginData),
+ mCharHandler(Net::getCharHandler())
+{
+ setCloseButton(false);
+
+ mAccountNameLabel = new Label(loginData->username);
+ mSwitchLoginButton = new Button(_("Switch Login"), "switch", this);
+ mChangePasswordButton = new Button(_("Change Password"), "change_password",
+ this);
+
+ int optionalActions = Net::getLoginHandler()->supportedOptionalActions();
+
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+
+ place(0, 0, mAccountNameLabel, 2);
+ place(0, 1, mSwitchLoginButton);
+
+ if (optionalActions & Net::LoginHandler::Unregister)
+ {
+ mUnregisterButton = new Button(_("Unregister"),
+ "unregister", this);
+ place(3, 1, mUnregisterButton);
+ }
+
+ place(0, 2, mChangePasswordButton);
+
+ if (optionalActions & Net::LoginHandler::ChangeEmail)
+ {
+ mChangeEmailButton = new Button(_("Change Email"),
+ "change_email", this);
+ place(3, 2, mChangeEmailButton);
+ }
+
+ place = getPlacer(0, 1);
+
+ for (int i = 0; i < (int)mLoginData->characterSlots; i++)
+ {
+ mCharacterEntries.push_back(new CharacterDisplay(this));
+ place(i % SLOTS_PER_ROW, (int)i / SLOTS_PER_ROW, mCharacterEntries[i]);
+ }
+
+ reflowLayout();
+
+ addKeyListener(this);
+
+ center();
+ setVisible(true);
+
+ Net::getCharHandler()->setCharSelectDialog(this);
+ if (mCharacterEntries[0])
+ mCharacterEntries[0]->requestFocus();
+}
+
+CharSelectDialog::~CharSelectDialog()
+{
+}
+
+void CharSelectDialog::action(const gcn::ActionEvent &event)
+{
+ // Check if a button of a character was pressed
+ const gcn::Widget *sourceParent = event.getSource()->getParent();
+ int selected = -1;
+ for (int i = 0; i < (int)mCharacterEntries.size(); ++i)
+ {
+ if (mCharacterEntries[i] == sourceParent)
+ {
+ selected = i;
+ break;
+ }
+ }
+
+ const std::string &eventId = event.getId();
+
+ if (selected != -1)
+ {
+ if (eventId == "use")
+ {
+ attemptCharacterSelect(selected);
+ }
+ else if (eventId == "new" &&
+ !mCharacterEntries[selected]->getCharacter())
+ {
+ // Start new character dialog
+ CharCreateDialog *charCreateDialog =
+ new CharCreateDialog(this, selected);
+ mCharHandler->setCharCreateDialog(charCreateDialog);
+ }
+ else if (eventId == "delete"
+ && mCharacterEntries[selected]->getCharacter())
+ {
+ new CharDeleteConfirm(this, selected);
+ }
+ }
+ else if (eventId == "switch")
+ {
+ Client::setState(STATE_SWITCH_LOGIN);
+ }
+ else if (eventId == "change_password")
+ {
+ Client::setState(STATE_CHANGEPASSWORD);
+ }
+ else if (eventId == "change_email")
+ {
+ Client::setState(STATE_CHANGEEMAIL);
+ }
+ else if (eventId == "unregister")
+ {
+ Client::setState(STATE_UNREGISTER);
+ }
+}
+
+void CharSelectDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ESCAPE)
+ {
+ action(gcn::ActionEvent(mSwitchLoginButton,
+ mSwitchLoginButton->getActionEventId()));
+ }
+}
+
+/**
+ * Communicate character deletion to the server.
+ */
+void CharSelectDialog::attemptCharacterDelete(int index)
+{
+ if (mLocked)
+ return;
+
+ mCharHandler->deleteCharacter(mCharacterEntries[index]->getCharacter());
+ lock();
+}
+
+/**
+ * Communicate character selection to the server.
+ */
+void CharSelectDialog::attemptCharacterSelect(int index)
+{
+ if (mLocked || !mCharacterEntries[index])
+ return;
+
+ setVisible(false);
+ if (mCharHandler && mCharacterEntries[index])
+ {
+ mCharHandler->chooseCharacter(
+ mCharacterEntries[index]->getCharacter());
+ }
+ lock();
+}
+
+void CharSelectDialog::setCharacters(const Net::Characters &characters)
+{
+ // Reset previous characters
+ std::vector<CharacterDisplay*>::iterator iter, iter_end;
+ for (iter = mCharacterEntries.begin(), iter_end = mCharacterEntries.end();
+ iter != iter_end; ++iter)
+ (*iter)->setCharacter(0);
+
+ Net::Characters::const_iterator i, i_end = characters.end();
+ for (i = characters.begin(); i != i_end; ++i)
+ {
+ if (!*i)
+ continue;
+
+ Net::Character *character = *i;
+
+ // Slots Number start at 1 for Manaserv, so we offset them by one.
+ int characterSlot = character->slot;
+ if (Net::getNetworkType() == ServerInfo::MANASERV && characterSlot > 0)
+ --characterSlot;
+
+ if (characterSlot >= (int)mCharacterEntries.size())
+ {
+ logger->log("Warning: slot out of range: %d", character->slot);
+ continue;
+ }
+
+ mCharacterEntries[characterSlot]->setCharacter(character);
+ }
+}
+
+void CharSelectDialog::lock()
+{
+ assert(!mLocked);
+ setLocked(true);
+}
+
+void CharSelectDialog::unlock()
+{
+ setLocked(false);
+}
+
+void CharSelectDialog::setLocked(bool locked)
+{
+ mLocked = locked;
+
+ if (mSwitchLoginButton)
+ mSwitchLoginButton->setEnabled(!locked);
+ if (mChangePasswordButton)
+ mChangePasswordButton->setEnabled(!locked);
+ if (mUnregisterButton)
+ mUnregisterButton->setEnabled(!locked);
+ if (mChangeEmailButton)
+ mChangeEmailButton->setEnabled(!locked);
+
+ for (int i = 0; i < (int)mCharacterEntries.size(); ++i)
+ {
+ if (mCharacterEntries[i])
+ mCharacterEntries[i]->setActive(!mLocked);
+ }
+}
+
+bool CharSelectDialog::selectByName(const std::string &name,
+ SelectAction action)
+{
+ if (mLocked)
+ return false;
+
+ for (int i = 0; i < (int)mCharacterEntries.size(); ++i)
+ {
+ Net::Character *character = mCharacterEntries[i]->getCharacter();
+ if (mCharacterEntries[i] && character)
+ {
+ if (character->dummy->getName() == name)
+ {
+ if (mCharacterEntries[i])
+ mCharacterEntries[i]->requestFocus();
+ if (action == Choose)
+ attemptCharacterSelect(i);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+CharacterDisplay::CharacterDisplay(CharSelectDialog *charSelectDialog):
+ mCharacter(0),
+ mPlayerBox(new PlayerBox)
+{
+ mButton = new Button("wwwwwwwww", "go", charSelectDialog);
+ mName = new Label("wwwwwwwwwwwwwwwwwwwwwwww");
+ mLevel = new Label("(888)");
+ mMoney = new Label("wwwwwwwww");
+
+ mDelete = new Button(_("Delete"), "delete", charSelectDialog);
+
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mPlayerBox, 3, 5);
+ place(0, 5, mName, 3);
+ place(0, 6, mLevel, 3);
+ place(0, 7, mMoney, 3);
+ place(0, 8, mButton, 3);
+ place(0, 9, mDelete, 3);
+
+ update();
+
+
+ // Setting the width so that the largest label fits.
+ mName->adjustSize();
+ mMoney->adjustSize();
+/*
+ int width = 74;
+ if (width - 20 < mName->getWidth())
+ width = 20 + mName->getWidth();
+ if (width - 20 < mMoney->getWidth())
+ width = 20 + mMoney->getWidth();
+ h.reflowLayout(width, 112 + mName->getHeight() + mLevel->getHeight() +
+ mMoney->getHeight() + mButton->getHeight() + mDelete->getHeight());
+*/
+
+ setWidth(100);
+ setHeight(200);
+}
+
+void CharacterDisplay::setCharacter(Net::Character *character)
+{
+ if (mCharacter == character)
+ return;
+
+ mCharacter = character;
+ mPlayerBox->setPlayer(character ? character->dummy : 0);
+ update();
+}
+
+void CharacterDisplay::requestFocus()
+{
+ mButton->requestFocus();
+}
+
+void CharacterDisplay::setActive(bool active)
+{
+ mButton->setEnabled(active);
+ mDelete->setEnabled(active);
+}
+
+void CharacterDisplay::update()
+{
+ if (mCharacter)
+ {
+ const LocalPlayer *character = mCharacter->dummy;
+ mButton->setCaption(_("Choose"));
+ mButton->setActionEventId("use");
+ mName->setCaption(strprintf("%s", character->getName().c_str()));
+ mLevel->setCaption(strprintf("Level %d",
+ mCharacter->data.mAttributes[LEVEL]));
+ mMoney->setCaption(Units::formatCurrency(
+ mCharacter->data.mAttributes[MONEY]));
+
+ mDelete->setVisible(true);
+ }
+ else
+ {
+ mButton->setCaption(_("Create"));
+ mButton->setActionEventId("new");
+ mName->setCaption(_("(empty)"));
+ mLevel->setCaption(_("(empty)"));
+ mMoney->setCaption(Units::formatCurrency(0));
+
+ mDelete->setVisible(false);
+ }
+
+ // Recompute layout
+ distributeResizedEvent();
+}
diff --git a/src/gui/charselectdialog.h b/src/gui/charselectdialog.h
new file mode 100644
index 000000000..7596e755b
--- /dev/null
+++ b/src/gui/charselectdialog.h
@@ -0,0 +1,113 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHAR_SELECT_H
+#define CHAR_SELECT_H
+
+#include "being.h"
+#include "guichanfwd.h"
+#include "main.h"
+
+#include "gui/widgets/window.h"
+
+#include "net/charhandler.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+class CharacterDisplay;
+class LocalPlayer;
+class LoginData;
+class PlayerBox;
+
+namespace Net
+{
+ class CharHandler;
+}
+
+/**
+ * Character selection dialog.
+ *
+ * \ingroup Interface
+ */
+class CharSelectDialog : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ friend class CharDeleteConfirm;
+ friend class Net::CharHandler;
+
+ /**
+ * Constructor.
+ */
+ CharSelectDialog(LoginData *loginData);
+
+ ~CharSelectDialog();
+
+ void action(const gcn::ActionEvent &event);
+
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ enum SelectAction
+ {
+ Focus = 0,
+ Choose
+ };
+
+ /**
+ * Attempt to select the character with the given name. Returns whether
+ * a character with the given name was found.
+ *
+ * \param action determines what to do when a character with the given
+ * name was found (just focus or also try to choose this
+ * character).
+ */
+ bool selectByName(const std::string &name,
+ SelectAction action = Focus);
+
+ private:
+ void attemptCharacterDelete(int index);
+ void attemptCharacterSelect(int index);
+
+ void setCharacters(const Net::Characters &characters);
+
+ void lock();
+ void unlock();
+ void setLocked(bool locked);
+
+ bool mLocked;
+
+ gcn::Label *mAccountNameLabel;
+
+ gcn::Button *mSwitchLoginButton;
+ gcn::Button *mChangePasswordButton;
+ gcn::Button *mUnregisterButton;
+ gcn::Button *mChangeEmailButton;
+
+ /** The player boxes */
+ std::vector<CharacterDisplay*> mCharacterEntries;
+
+ LoginData *mLoginData;
+
+ Net::CharHandler *mCharHandler;
+};
+
+#endif
diff --git a/src/gui/chat.cpp b/src/gui/chat.cpp
new file mode 100644
index 000000000..3ffa018c3
--- /dev/null
+++ b/src/gui/chat.cpp
@@ -0,0 +1,1350 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chat.h"
+
+#include "actorspritemanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "guild.h"
+#include "keyboardconfig.h"
+#include "localplayer.h"
+#include "party.h"
+#include "playerinfo.h"
+#include "playerrelations.h"
+#include "spellshortcut.h"
+#include "sound.h"
+
+#include "gui/setup.h"
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/battletab.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/itemlinkhandler.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/tabbedarea.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/tradetab.h"
+#include "gui/widgets/whispertab.h"
+
+#include "net/chathandler.h"
+#include "net/net.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/focushandler.hpp>
+#include <guichan/focuslistener.hpp>
+
+#include <sstream>
+
+#include <sys/stat.h>
+
+/**
+ * The chat input hides when it loses focus. It is also invisible by default.
+ */
+class ChatInput : public TextField, public gcn::FocusListener
+{
+ public:
+ ChatInput():
+ TextField("", false)
+ {
+ setVisible(false);
+ addFocusListener(this);
+ }
+
+ /**
+ * Called if the chat input loses focus. It will set itself to
+ * invisible as result.
+ */
+ void focusLost(const gcn::Event &event _UNUSED_)
+ {
+ setVisible(false);
+ }
+};
+
+const char *COLOR_NAME[14] =
+{
+ N_("default"),
+ N_("black"),
+ N_("red"),
+ N_("green"),
+ N_("blue"),
+ N_("gold"),
+ N_("yellow"),
+ N_("pink"),
+ N_("purple"),
+ N_("grey"),
+ N_("brown"),
+ N_("rainbow 1"),
+ N_("rainbow 2"),
+ N_("rainbow 3"),
+};
+
+
+class ColorListModel : public gcn::ListModel
+{
+public:
+ virtual ~ColorListModel() { }
+
+ virtual int getNumberOfElements()
+ {
+ return 14;
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return COLOR_NAME[i];
+ }
+};
+
+#define ACTION_COLOR_PICKER "color picker"
+
+
+ChatWindow::ChatWindow():
+ Window(_("Chat")),
+ mTmpVisible(false),
+ mChatHistoryIndex(0)
+{
+ listen(CHANNEL_NOTICES);
+ listen(CHANNEL_ATTRIBUTES);
+
+ setWindowName("Chat");
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ // no title presented, title bar is padding so window can be moved.
+ gcn::Window::setTitleBarHeight(gcn::Window::getPadding() + 4);
+ setShowTitle(false);
+ setResizable(true);
+ setDefaultVisible(true);
+ setSaveVisible(true);
+ setDefaultSize(600, 123, ImageRect::LOWER_LEFT);
+ setMinWidth(150);
+ setMinHeight(90);
+
+ mItemLinkHandler = new ItemLinkHandler;
+
+ mChatInput = new ChatInput;
+ mChatInput->setActionEventId("chatinput");
+ mChatInput->addActionListener(this);
+
+ mChatTabs = new TabbedArea;
+
+ mChatColor = config.getIntValue("chatColor");
+ mColorListModel = new ColorListModel;
+ mColorPicker = new DropDown(mColorListModel);
+
+ mColorPicker->setActionEventId(ACTION_COLOR_PICKER);
+ mColorPicker->addActionListener(this);
+ mColorPicker->setSelected(mChatColor);
+
+ add(mChatTabs);
+ add(mChatInput);
+ add(mColorPicker);
+
+ loadWindowState();
+
+ mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
+ - 2*getPadding() - 8, getPadding());
+
+ // Add key listener to chat input to be able to respond to up/down
+ mChatInput->addKeyListener(this);
+ mCurHist = mHistory.end();
+
+ mReturnToggles = config.getBoolValue("ReturnToggles");
+
+ mRainbowColor = 0;
+
+ mColorPicker->setVisible(config.getBoolValue("showChatColorsList"));
+
+ fillCommands();
+ initTradeFilter();
+}
+
+ChatWindow::~ChatWindow()
+{
+ config.setValue("ReturnToggles", mReturnToggles);
+ removeAllWhispers();
+ delete mItemLinkHandler;
+ mItemLinkHandler = 0;
+ delete mColorPicker;
+ mColorPicker = 0;
+ delete mColorListModel;
+ mColorListModel = 0;
+}
+
+void ChatWindow::fillCommands()
+{
+ mCommands.push_back("/all");
+ mCommands.push_back("/away ");
+ mCommands.push_back("/closeall");
+ mCommands.push_back("/clear");
+ mCommands.push_back("/create ");
+ mCommands.push_back("/close");
+ mCommands.push_back("/cacheinfo");
+ mCommands.push_back("/erase ");
+ mCommands.push_back("/follow ");
+ mCommands.push_back("/heal ");
+ mCommands.push_back("/ignoreall");
+ mCommands.push_back("/help");
+ mCommands.push_back("/announce ");
+ mCommands.push_back("/where");
+ mCommands.push_back("/who");
+ mCommands.push_back("/msg ");
+ mCommands.push_back("/mail ");
+ mCommands.push_back("/whisper ");
+ mCommands.push_back("/w ");
+ mCommands.push_back("/query ");
+ mCommands.push_back("/ignore ");
+ mCommands.push_back("/unignore ");
+ mCommands.push_back("/join ");
+ mCommands.push_back("/list");
+ mCommands.push_back("/party");
+ mCommands.push_back("/createparty ");
+ mCommands.push_back("/createguild ");
+ mCommands.push_back("/me ");
+ mCommands.push_back("/toggle");
+ mCommands.push_back("/present");
+ mCommands.push_back("/quit");
+ mCommands.push_back("/move ");
+ mCommands.push_back("/target ");
+ mCommands.push_back("/invite ");
+ mCommands.push_back("/leave");
+ mCommands.push_back("/kick ");
+ mCommands.push_back("/item");
+ mCommands.push_back("/imitation");
+ mCommands.push_back("/exp");
+ mCommands.push_back("/ping");
+ mCommands.push_back("/outfit ");
+ mCommands.push_back("/emote ");
+ mCommands.push_back("/navigate ");
+ mCommands.push_back("/priceload");
+ mCommands.push_back("/pricesave");
+ mCommands.push_back("/trade ");
+ mCommands.push_back("/friend ");
+ mCommands.push_back("/befriend ");
+ mCommands.push_back("/disregard ");
+ mCommands.push_back("/neutral ");
+ mCommands.push_back("/raw ");
+ mCommands.push_back("/disconnect");
+ mCommands.push_back("/undress ");
+ mCommands.push_back("/attack");
+ mCommands.push_back("/dirs");
+ mCommands.push_back("/info");
+ mCommands.push_back("/wait");
+}
+
+void ChatWindow::resetToDefaultSize()
+{
+ Window::resetToDefaultSize();
+}
+
+void ChatWindow::adjustTabSize()
+{
+ const gcn::Rectangle area = getChildrenArea();
+
+ mChatInput->setPosition(mChatInput->getFrameSize(),
+ area.height - mChatInput->getHeight() -
+ mChatInput->getFrameSize());
+ mChatInput->setWidth(area.width - 2 * mChatInput->getFrameSize());
+
+ mChatTabs->setWidth(area.width - 2 * mChatTabs->getFrameSize());
+ mChatTabs->setHeight(area.height - 2 * mChatTabs->getFrameSize() -
+ (mChatInput->getHeight() + mChatInput->getFrameSize() * 2));
+
+ ChatTab *tab = getFocused();
+ if (tab)
+ {
+ gcn::Widget *content = tab->mScrollArea;
+ if (content)
+ {
+ content->setSize(mChatTabs->getWidth()
+ - 2 * content->getFrameSize(),
+ mChatTabs->getContainerHeight()
+ - 2 * content->getFrameSize());
+ content->logic();
+ }
+ }
+
+ mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
+ - 2*getPadding() - 8, getPadding());
+
+}
+
+void ChatWindow::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+
+ adjustTabSize();
+}
+
+/*
+void ChatWindow::logic()
+{
+ Window::logic();
+
+ Tab *tab = getFocused();
+ if (tab != mCurrentTab)
+ mCurrentTab = tab;
+}
+*/
+
+ChatTab *ChatWindow::getFocused() const
+{
+ return static_cast<ChatTab*>(mChatTabs->getSelectedTab());
+}
+
+void ChatWindow::clearTab(ChatTab *tab)
+{
+ if (tab)
+ tab->clearText();
+}
+
+void ChatWindow::clearTab()
+{
+ clearTab(getFocused());
+}
+
+void ChatWindow::prevTab()
+{
+ if (!mChatTabs)
+ return;
+
+ int tab = mChatTabs->getSelectedTabIndex();
+
+ if (tab == 0)
+ tab = mChatTabs->getNumberOfTabs();
+ tab--;
+
+ mChatTabs->setSelectedTab(tab);
+}
+
+void ChatWindow::nextTab()
+{
+ if (!mChatTabs)
+ return;
+
+ int tab = mChatTabs->getSelectedTabIndex();
+
+ tab++;
+ if (tab == mChatTabs->getNumberOfTabs())
+ tab = 0;
+
+ mChatTabs->setSelectedTab(tab);
+}
+
+void ChatWindow::defaultTab()
+{
+ if (mChatTabs)
+ mChatTabs->setSelectedTab((unsigned)0);
+}
+
+void ChatWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "chatinput")
+ {
+ std::string message = mChatInput->getText();
+
+ if (!message.empty())
+ {
+ // If message different from previous, put it in the history
+ if (mHistory.empty() || message != mHistory.back())
+ mHistory.push_back(message);
+
+ // Reset history iterator
+ mCurHist = mHistory.end();
+
+ // Send the message to the server
+ chatInput(addColors(message));
+
+ // Clear the text from the chat input
+ mChatInput->setText("");
+ }
+
+ if (message.empty() || !mReturnToggles)
+ {
+ // Remove focus and hide input
+ if (mFocusHandler)
+ mFocusHandler->focusNone();
+
+ // If the chatWindow is shown up because you want to send a message
+ // It should hide now
+ if (mTmpVisible)
+ setVisible(false);
+ }
+ }
+ else if (event.getId() == ACTION_COLOR_PICKER)
+ {
+ mChatColor = mColorPicker->getSelected();
+ config.setValue("chatColor", mChatColor);
+ }
+
+ if (mColorPicker && mColorPicker->isVisible()
+ != config.getBoolValue("showChatColorsList"))
+ {
+ mColorPicker->setVisible(config.getBoolValue(
+ "showChatColorsList"));
+ }
+}
+
+bool ChatWindow::requestChatFocus()
+{
+ // Make sure chatWindow is visible
+ if (!isVisible())
+ {
+ setVisible(true);
+
+ /*
+ * This is used to hide chatWindow after sending the message. There is
+ * a trick here, because setVisible will set mTmpVisible to false, you
+ * have to put this sentence *after* setVisible, not before it
+ */
+ mTmpVisible = true;
+ }
+
+ // Don't do anything else if the input is already visible and has focus
+ if (mChatInput->isVisible() && mChatInput->isFocused())
+ return false;
+
+ // Give focus to the chat input
+ mChatInput->setVisible(true);
+ mChatInput->requestFocus();
+ return true;
+}
+
+bool ChatWindow::isInputFocused() const
+{
+ return mChatInput->isFocused();
+}
+
+void ChatWindow::removeTab(ChatTab *tab)
+{
+ mChatTabs->removeTab(tab);
+}
+
+void ChatWindow::addTab(ChatTab *tab)
+{
+ mChatTabs->addTab(tab, tab->mScrollArea);
+
+ // Update UI
+ logic();
+}
+
+void ChatWindow::removeWhisper(const std::string &nick)
+{
+ std::string tempNick = nick;
+ toLower(tempNick);
+ mWhispers.erase(tempNick);
+}
+
+void ChatWindow::removeAllWhispers()
+{
+ TabMap::iterator iter;
+ std::list<ChatTab*> tabs;
+
+ for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter)
+ tabs.push_back(iter->second);
+
+ for (std::list<ChatTab*>::iterator it = tabs.begin();
+ it != tabs.end(); ++it)
+ {
+ delete *it;
+ }
+
+ mWhispers.clear();
+}
+
+void ChatWindow::ignoreAllWhispers()
+{
+ TabMap::iterator iter;
+ for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter)
+ {
+ WhisperTab *tab = dynamic_cast<WhisperTab*>(iter->second);
+ if (tab && player_relations.getRelation(tab->getNick())
+ != PlayerRelation::IGNORED)
+ {
+ player_relations.setRelation(tab->getNick(),
+ PlayerRelation::IGNORED);
+ }
+
+ delete(iter->second);
+ iter->second = 0;
+ }
+}
+
+void ChatWindow::chatInput(const std::string &message)
+{
+ ChatTab *tab = NULL;
+ std::string msg = message;
+ trim(msg);
+
+ if (config.getBoolValue("allowCommandsInChatTabs")
+ && msg.length() > 1
+ && ((msg.at(0) == '#' && msg.at(1) != '#') || msg.at(0) == '@')
+ && localChatTab)
+ {
+ tab = localChatTab;
+ }
+ else
+ {
+ tab = getFocused();
+ }
+ if (tab)
+ tab->chatInput(msg);
+ Game::instance()->setValidSpeed();
+}
+
+void ChatWindow::localChatInput(const std::string &msg)
+{
+ if (localChatTab)
+ localChatTab->chatInput(msg);
+ else
+ chatInput(msg);
+}
+
+void ChatWindow::doPresent()
+{
+ if (!actorSpriteManager)
+ return;
+
+ const ActorSprites &actors = actorSpriteManager->getAll();
+ std::string response = "";
+ int playercount = 0;
+
+ for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end();
+ it != it_end; it++)
+ {
+ if ((*it)->getType() == ActorSprite::PLAYER)
+ {
+ if (!response.empty())
+ response += ", ";
+ response += static_cast<Being*>(*it)->getName();
+ ++playercount;
+ }
+ }
+
+ std::string log = strprintf(_("Present: %s; %d players are present."),
+ response.c_str(), playercount);
+
+ if (getFocused())
+ getFocused()->chatLog(log, BY_SERVER);
+}
+
+void ChatWindow::scroll(int amount)
+{
+ if (!isVisible())
+ return;
+
+ ChatTab *tab = getFocused();
+ if (tab)
+ tab->scroll(amount);
+}
+
+void ChatWindow::mousePressed(gcn::MouseEvent &event)
+{
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (viewport)
+ {
+ gcn::Tab *tab = mChatTabs->getSelectedTab();
+ if (tab)
+ {
+ ChatTab *cTab = dynamic_cast<ChatTab*>(tab);
+ if (cTab)
+ viewport->showChatPopup(cTab);
+ }
+ }
+ }
+
+ Window::mousePressed(event);
+
+ if (event.isConsumed())
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ ChatTab *tab = getFocused();
+ if (tab)
+ mMoved = !isResizeAllowed(event);
+ }
+
+ mDragOffsetX = event.getX();
+ mDragOffsetY = event.getY();
+}
+
+void ChatWindow::mouseDragged(gcn::MouseEvent &event)
+{
+ Window::mouseDragged(event);
+
+ if (event.isConsumed())
+ return;
+
+ if (isMovable() && mMoved)
+ {
+ int newX = std::max(0, getX() + event.getX() - mDragOffsetX);
+ int newY = std::max(0, getY() + event.getY() - mDragOffsetY);
+ newX = std::min(graphics->getWidth() - getWidth(), newX);
+ newY = std::min(graphics->getHeight() - getHeight(), newY);
+ setPosition(newX, newY);
+ }
+}
+
+/*
+void ChatWindow::mouseReleased(gcn::MouseEvent &event _UNUSED_)
+{
+
+}
+*/
+
+void ChatWindow::keyPressed(gcn::KeyEvent &event)
+{
+ if (event.getKey().getValue() == Key::DOWN)
+ {
+ if (mCurHist != mHistory.end())
+ {
+ // Move forward through the history
+ HistoryIterator prevHist = mCurHist++;
+
+ if (mCurHist != mHistory.end())
+ {
+ mChatInput->setText(*mCurHist);
+ mChatInput->setCaretPosition(static_cast<unsigned>(
+ mChatInput->getText().length()));
+ }
+ else
+ {
+ mChatInput->setText("");
+ mCurHist = prevHist;
+ }
+ }
+ else if (mChatInput->getText() != "")
+ {
+ mChatInput->setText("");
+ }
+ }
+ else if (event.getKey().getValue() == Key::UP &&
+ mCurHist != mHistory.begin() && !mHistory.empty())
+ {
+ // Move backward through the history
+ mCurHist--;
+ mChatInput->setText(*mCurHist);
+ mChatInput->setCaretPosition(static_cast<unsigned>(
+ mChatInput->getText().length()));
+ }
+ else if (event.getKey().getValue() == Key::INSERT &&
+ mChatInput->getText() != "")
+ {
+ // Add the current message to the history and clear the text
+ if (mHistory.empty() || mChatInput->getText() != mHistory.back())
+ mHistory.push_back(mChatInput->getText());
+ mCurHist = mHistory.end();
+ mChatInput->setText("");
+ }
+ else if (keyboard.isKeyActive(keyboard.KEY_AUTOCOMPLETE_CHAT) &&
+ mChatInput->getText() != "")
+ {
+ autoComplete();
+ return;
+ }
+ else if (keyboard.isKeyActive(keyboard.KEY_DEACTIVATE_CHAT) &&
+ mChatInput->isVisible())
+ {
+ mChatInput->setVisible(false);
+ }
+ else if (keyboard.isKeyActive(keyboard.KEY_CHAT_PREV_HISTORY) &&
+ mChatInput->isVisible())
+ {
+ ChatTab *tab = getFocused();
+ if (tab && tab->getRows().size() > 0)
+ {
+ if (!mChatHistoryIndex)
+ {
+ mChatHistoryIndex = static_cast<unsigned>(
+ tab->getRows().size());
+
+ mChatInput->setText("");
+ mChatInput->setCaretPosition(0);
+ return;
+ }
+ else
+ {
+ mChatHistoryIndex --;
+ }
+
+ std::list<std::string>::iterator it;
+ unsigned int f = 0;
+ for (it = tab->getRows().begin();
+ it != tab->getRows().end(); ++it, f++)
+ {
+ if (f == mChatHistoryIndex)
+ mChatInput->setText(*it);
+ }
+ mChatInput->setCaretPosition(static_cast<unsigned>(
+ mChatInput->getText().length()));
+ }
+ }
+ else if (keyboard.isKeyActive(keyboard.KEY_CHAT_NEXT_HISTORY) &&
+ mChatInput->isVisible())
+ {
+ ChatTab *tab = getFocused();
+ if (tab && !tab->getRows().empty())
+ {
+ if (mChatHistoryIndex + 1 < tab->getRows().size())
+ {
+ mChatHistoryIndex ++;
+ }
+ else if (mChatHistoryIndex < tab->getRows().size())
+ {
+ mChatHistoryIndex ++;
+ mChatInput->setText("");
+ mChatInput->setCaretPosition(0);
+ return;
+ }
+ else
+ {
+ mChatHistoryIndex = 0;
+ }
+
+ std::list<std::string>::iterator it;
+ unsigned int f = 0;
+ for (it = tab->getRows().begin();
+ it != tab->getRows().end(); ++it, f++)
+ {
+ if (f == mChatHistoryIndex)
+ mChatInput->setText(*it);
+ }
+ mChatInput->setCaretPosition(static_cast<unsigned>(
+ mChatInput->getText().length()));
+ }
+ }
+
+ std::string Temp;
+ switch (event.getKey().getValue())
+ {
+ case Key::F2: Temp = "\u2318"; break;
+ case Key::F3: Temp = "\u263A"; break;
+ case Key::F4: Temp = "\u2665"; break;
+ case Key::F5: Temp = "\u266A"; break;
+ case Key::F6: Temp = "\u266B"; break;
+ case Key::F7: Temp = "\u26A0"; break;
+ case Key::F8: Temp = "\u2622"; break;
+ case Key::F9: Temp = "\u262E"; break;
+ case Key::F10: Temp = "\u2605"; break;
+ case Key::F11: Temp = "\u2618"; break;
+ case Key::F12: Temp = "\u2592"; break;
+ default: break;
+ }
+
+ if (Temp != "")
+ addInputText(Temp, false);
+}
+
+void ChatWindow::event(Channels channel, const Mana::Event &event)
+{
+ if (channel == CHANNEL_NOTICES)
+ {
+ if (event.getName() == EVENT_SERVERNOTICE && localChatTab)
+ localChatTab->chatLog(event.getString("message"), BY_SERVER);
+ }
+ else if (channel == CHANNEL_ATTRIBUTES)
+ {
+ if (!config.getBoolValue("showBattleEvents"))
+ return;
+
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ switch (event.getInt("id"))
+ {
+ case EXP:
+ {
+ if (event.getInt("oldValue") > event.getInt("newValue"))
+ break;
+
+ int change = event.getInt("newValue")
+ - event.getInt("oldValue");
+
+ if (change != 0)
+ battleChatLog("+" + toString(change) + " xp");
+ break;
+ }
+ case LEVEL:
+ battleChatLog("Level: " + toString(
+ event.getInt("newValue")));
+ break;
+ default:
+ break;
+ };
+ }
+ }
+}
+
+void ChatWindow::addInputText(const std::string &text, bool space)
+{
+ int caretPos = mChatInput->getCaretPosition();
+ const std::string inputText = mChatInput->getText();
+
+ std::ostringstream ss;
+ ss << inputText.substr(0, caretPos) << text;
+ if (space)
+ ss << " ";
+
+ ss << inputText.substr(caretPos);
+
+ mChatInput->setText(ss.str());
+ mChatInput->setCaretPosition(caretPos + static_cast<int>(
+ text.length()) + static_cast<int>(space));
+ requestChatFocus();
+}
+
+void ChatWindow::addItemText(const std::string &item)
+{
+ std::ostringstream text;
+ text << "[" << item << "]";
+ addInputText(text.str());
+}
+
+void ChatWindow::setVisible(bool isVisible)
+{
+ Window::setVisible(isVisible);
+
+ /*
+ * For whatever reason, if setVisible is called, the mTmpVisible effect
+ * should be disabled.
+ */
+ mTmpVisible = false;
+}
+
+void ChatWindow::whisper(const std::string &nick,
+ const std::string &mes, Own own)
+{
+ if (mes.empty() || !player_node)
+ return;
+
+ std::string playerName = player_node->getName();
+ std::string tempNick = nick;
+
+ toLower(playerName);
+ toLower(tempNick);
+
+ if (tempNick.compare(playerName) == 0)
+ return;
+
+ ChatTab *tab = 0;
+ TabMap::const_iterator i = mWhispers.find(tempNick);
+
+ if (i != mWhispers.end())
+ tab = i->second;
+ else if (config.getBoolValue("whispertab"))
+ tab = addWhisperTab(nick);
+
+ if (tab)
+ {
+ if (own == BY_PLAYER)
+ {
+ tab->chatInput(mes);
+ }
+ else if (own == BY_SERVER)
+ {
+ tab->chatLog(mes);
+ }
+ else
+ {
+ tab->chatLog(nick, mes);
+ player_node->afkRespond(tab, nick);
+ }
+ }
+ else if (localChatTab)
+ {
+ if (own == BY_PLAYER)
+ {
+ Net::getChatHandler()->privateMessage(nick, mes);
+
+ localChatTab->chatLog(strprintf(_("Whispering to %s: %s"),
+ nick.c_str(), mes.c_str()), BY_PLAYER);
+ }
+ else
+ {
+ localChatTab->chatLog(nick + " : " + mes, ACT_WHISPER, false);
+ if (player_node)
+ player_node->afkRespond(0, nick);
+ }
+ }
+}
+
+ChatTab *ChatWindow::addWhisperTab(const std::string &nick, bool switchTo)
+{
+ if (!player_node)
+ return NULL;
+
+ std::string playerName = player_node->getName();
+ std::string tempNick = nick;
+
+ toLower(playerName);
+ toLower(tempNick);
+
+ TabMap::const_iterator i = mWhispers.find(tempNick);
+ ChatTab *ret;
+
+ if (tempNick.compare(playerName) == 0)
+ return NULL;
+
+ if (i != mWhispers.end())
+ {
+ ret = i->second;
+ }
+ else
+ {
+ ret = new WhisperTab(nick);
+ mWhispers[tempNick] = ret;
+ if (config.getBoolValue("showChatHistory"))
+ ret->loadFromLogFile(nick);
+ }
+
+ if (switchTo)
+ mChatTabs->setSelectedTab(ret);
+
+ return ret;
+}
+
+ChatTab *ChatWindow::getWhisperTab(const std::string &nick) const
+{
+ if (!player_node)
+ return NULL;
+
+ std::string playerName = player_node->getName();
+ std::string tempNick = nick;
+
+ toLower(playerName);
+ toLower(tempNick);
+
+ TabMap::const_iterator i = mWhispers.find(tempNick);
+ ChatTab *ret = 0;
+
+ if (tempNick.compare(playerName) == 0)
+ return NULL;
+
+ if (i != mWhispers.end())
+ ret = i->second;
+
+ return ret;
+}
+
+std::string ChatWindow::addColors(std::string &msg)
+{
+ // default color or chat command
+ if (mChatColor == 0 || msg.length() == 0 || msg.at(0) == '#'
+ || msg.at(0) == '/' || msg.at(0) == '@' || msg.at(0) == '!')
+ {
+ return msg;
+ }
+
+ std::string newMsg = "";
+ int cMap[] = {1, 4, 5, 2, 3, 6, 7, 9, 0, 8};
+
+ // rainbow
+ switch(mChatColor)
+ {
+ case 11:
+ msg = removeColors(msg);
+ for (unsigned int f = 0; f < msg.length(); f ++)
+ {
+ newMsg += "##" + toString(mRainbowColor++) + msg.at(f);
+ if (mRainbowColor > 9)
+ mRainbowColor = 0;
+ }
+ return newMsg;
+ case 12:
+ msg = removeColors(msg);
+ for (unsigned int f = 0; f < msg.length(); f ++)
+ {
+ newMsg += "##" + toString(cMap[mRainbowColor++]) + msg.at(f);
+ if (mRainbowColor > 9)
+ mRainbowColor = 0;
+ }
+ return newMsg;
+ case 13:
+ msg = removeColors(msg);
+ for (unsigned int f = 0; f < msg.length(); f ++)
+ {
+ newMsg += "##" + toString(cMap[9-mRainbowColor++]) + msg.at(f);
+ if (mRainbowColor > 9)
+ mRainbowColor = 0;
+ }
+ return newMsg;
+ default:
+ break;
+ }
+
+ // simple colors
+ return "##" + toString(mChatColor - 1) + msg;
+}
+
+void ChatWindow::autoComplete()
+{
+ int caretPos = mChatInput->getCaretPosition();
+ int startName = 0;
+ const std::string inputText = mChatInput->getText();
+ std::string name = inputText.substr(0, caretPos);
+ std::string newName("");
+
+ for (int f = caretPos - 1; f > -1; f --)
+ {
+ if (isWordSeparator(inputText[f]))
+ {
+ startName = f + 1;
+ name = inputText.substr(f + 1, caretPos - f);
+ break;
+ }
+ }
+
+ if (caretPos - 1 + 1 == startName)
+ return;
+
+ ChatTab *cTab = static_cast<ChatTab*>(mChatTabs->getSelectedTab());
+ std::vector<std::string> nameList;
+
+ cTab->getAutoCompleteList(nameList);
+ newName = autoComplete(nameList, name);
+
+ if (newName == "" && actorSpriteManager)
+ {
+ actorSpriteManager->getPlayerNames(nameList, true);
+ newName = autoComplete(nameList, name);
+ }
+ if (newName == "")
+ newName = autoCompleteHistory(name);
+ if (newName == "" && spellManager)
+ newName = spellManager->autoComplete(name);
+ if (newName == "")
+ newName = autoCompleteCommands(name);
+ if (newName == "" && actorSpriteManager)
+ {
+ actorSpriteManager->getMobNames(nameList);
+ newName = autoComplete(nameList, name);
+ }
+
+ if (newName != "")
+ {
+ mChatInput->setText(inputText.substr(0, startName) + newName
+ + inputText.substr(caretPos, inputText.length() - caretPos));
+
+ int len = caretPos - static_cast<int>(name.length())
+ + static_cast<int>(newName.length());
+
+ if (startName > 0)
+ mChatInput->setCaretPosition(len + 1);
+ else
+ mChatInput->setCaretPosition(len);
+ }
+}
+
+std::string ChatWindow::autoComplete(std::vector<std::string> &names,
+ std::string partName) const
+{
+ std::vector<std::string>::iterator i = names.begin();
+ toLower(partName);
+ std::string newName("");
+
+ while (i != names.end())
+ {
+ if (!i->empty())
+ {
+ std::string name = *i;
+ toLower(name);
+
+ std::string::size_type pos = name.find(partName, 0);
+ if (pos == 0)
+ {
+ if (newName != "")
+ {
+ toLower(newName);
+ newName = findSameSubstring(name, newName);
+ }
+ else
+ {
+ newName = *i;
+ }
+ }
+ }
+ ++i;
+ }
+
+ return newName;
+}
+
+std::string ChatWindow::autoCompleteCommands(std::string partName)
+{
+ Commands::iterator i = mCommands.begin();
+ std::vector<std::string> nameList;
+
+ while (i != mCommands.end())
+ {
+ std::string line = *i;
+ std::string::size_type pos = line.find(partName, 0);
+ if (pos == 0)
+ nameList.push_back(line);
+ ++i;
+ }
+ return autoComplete(nameList, partName);
+}
+
+/*
+void ChatWindow::moveTabLeft(ChatTab *tab)
+{
+ mChatTabs->moveLeft(tab);
+}
+
+void ChatWindow::moveTabRight(ChatTab *tab)
+{
+ mChatTabs->moveRight(tab);
+}
+*/
+
+std::string ChatWindow::autoCompleteHistory(std::string partName)
+{
+ History::iterator i = mHistory.begin();
+ std::vector<std::string> nameList;
+
+ while (i != mHistory.end())
+ {
+ std::string line = *i;
+ unsigned int f = 0;
+ while (f < line.length() && !isWordSeparator(line.at(f)))
+ f++;
+
+ line = line.substr(0, f);
+ if (line != "")
+ nameList.push_back(line);
+
+ ++i;
+ }
+ return autoComplete(nameList, partName);
+}
+
+void ChatWindow::resortChatLog(std::string line, Own own,
+ bool ignoreRecord, bool tryRemoveColors)
+{
+ if (own == -1)
+ own = BY_SERVER;
+
+ if (tradeChatTab)
+ {
+ if (findI(line, mTradeFilter) != std::string::npos)
+ {
+// logger->log("trade: " + line);
+ tradeChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+ return;
+ }
+
+ unsigned long idx = line.find(": \302\202");
+ if (idx != std::string::npos)
+ {
+ line = line.erase(idx + 2, 2);
+ tradeChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+ return;
+ }
+
+ unsigned long idx1 = line.find("@@");
+ if (idx1 != std::string::npos)
+ {
+ unsigned long idx2 = line.find("|", idx1);
+ if (idx2 != std::string::npos)
+ {
+ unsigned long idx3 = line.find("@@", idx2);
+ if (idx3 != std::string::npos)
+ {
+ tradeChatTab->chatLog(line, own, ignoreRecord,
+ tryRemoveColors);
+ return;
+ }
+ }
+ }
+ }
+
+ if (localChatTab)
+ localChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+}
+
+void ChatWindow::battleChatLog(std::string line, Own own,
+ bool ignoreRecord, bool tryRemoveColors)
+{
+ if (own == -1)
+ own = BY_SERVER;
+ if (battleChatTab)
+ battleChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+ else if (debugChatTab)
+ debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+}
+
+void ChatWindow::initTradeFilter()
+{
+ std::string tradeListName = Client::getServerConfigDirectory()
+ + "/tradefilter.txt";
+
+ std::ifstream tradeFile;
+ struct stat statbuf;
+
+ if (!stat(tradeListName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode))
+ {
+ tradeFile.open(tradeListName.c_str(), std::ios::in);
+ char line[100];
+ while (tradeFile.getline(line, 100))
+ {
+ std::string str = line;
+ if (!str.empty())
+ mTradeFilter.push_back(str);
+ }
+ tradeFile.close();
+ }
+}
+
+void ChatWindow::updateOnline(std::set<std::string> &onlinePlayers)
+{
+ TabMap::iterator iter;
+ const Party *party = 0;
+ const Guild *guild = 0;
+ if (player_node)
+ {
+ party = player_node->getParty();
+ guild = player_node->getGuild();
+ }
+ for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter)
+ {
+ if (!iter->second)
+ return;
+
+ WhisperTab *tab = static_cast<WhisperTab*>(iter->second);
+
+ if (!tab)
+ continue;
+
+ if (onlinePlayers.find(tab->getNick()) != onlinePlayers.end())
+ {
+ tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER));
+ }
+ else
+ {
+ const std::string nick = tab->getNick();
+ if (actorSpriteManager)
+ {
+ const Being *being = actorSpriteManager->findBeingByName(
+ nick, ActorSprite::PLAYER);
+ if (being)
+ {
+ tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER));
+ continue;
+ }
+ }
+ if (party)
+ {
+ const PartyMember *pm = party->getMember(nick);
+ if (pm && pm->getOnline())
+ {
+ tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER));
+ continue;
+ }
+ }
+ if (guild)
+ {
+ const GuildMember *gm = guild->getMember(nick);
+ if (gm && gm->getOnline())
+ {
+ tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER));
+ continue;
+ }
+ }
+ tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER_OFFLINE));
+ }
+ }
+}
+
+void ChatWindow::loadState()
+{
+ int num = 0;
+ while (num < 50)
+ {
+ std::string nick = serverConfig.getValue(
+ "chatWhisper" + toString(num), "");
+
+ if (nick.empty())
+ break;
+ addWhisperTab(nick);
+ serverConfig.deleteKey("chatWhisper" + toString(num));
+ num ++;
+ }
+
+ while (num < 50)
+ {
+ serverConfig.deleteKey("chatWhisper" + toString(num));
+ num ++;
+ }
+}
+
+void ChatWindow::saveState()
+{
+ int num = 0;
+ TabMap::iterator iter;
+ for (iter = mWhispers.begin(); iter != mWhispers.end() && num < 50; ++iter)
+ {
+ if (!iter->second)
+ return;
+
+ WhisperTab *tab = static_cast<WhisperTab*>(iter->second);
+
+ if (!tab)
+ continue;
+
+ serverConfig.setValue("chatWhisper" + toString(num),
+ tab->getNick());
+
+ num ++;
+ }
+
+ while (num < 50)
+ {
+ serverConfig.deleteKey("chatWhisper" + toString(num));
+ num ++;
+ }
+}
+
+std::string ChatWindow::doReplace(const std::string &msg)
+{
+ if (Client::getServerName() == "server.themanaworld.org"
+ || Client::getServerName() == "themanaworld.org"
+ || Client::getServerName() == "81.161.192.4")
+ {
+ return msg;
+ }
+
+ std::string str = msg;
+ replaceSpecialChars(str);
+ return str;
+}
diff --git a/src/gui/chat.h b/src/gui/chat.h
new file mode 100644
index 000000000..444057b57
--- /dev/null
+++ b/src/gui/chat.h
@@ -0,0 +1,318 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHAT_H
+#define CHAT_H
+
+#include "listener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/widget.hpp>
+#include <guichan/widgetlistener.hpp>
+
+#include <list>
+#include <string>
+#include <map>
+#include <vector>
+#include <set>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class BrowserBox;
+class ChatTab;
+class Channel;
+class ChatInput;
+class ColorListModel;
+class ScrollArea;
+class TabbedArea;
+class ItemLinkHandler;
+class Tab;
+class WhisperTab;
+
+#define DEFAULT_CHAT_WINDOW_SCROLL 7 // 1 means `1/8th of the window size'.
+
+enum Own
+{
+ BY_GM = 0,
+ BY_PLAYER,
+ BY_OTHER,
+ BY_SERVER,
+ BY_CHANNEL,
+ ACT_WHISPER, // getting whispered at
+ ACT_IS, // equivalent to "/me" on IRC
+ BY_LOGGER,
+ BY_UNKNOWN = -1
+};
+
+/** One item in the chat log */
+struct CHATLOG
+{
+ std::string nick;
+ std::string text;
+ Own own;
+};
+
+/**
+ * The chat window.
+ *
+ * \ingroup Interface
+ */
+class ChatWindow : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener,
+ public Mana::Listener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ChatWindow();
+
+ /**
+ * Destructor: used to write back values to the config file
+ */
+ ~ChatWindow();
+
+ /**
+ * Reset the chat window to default positions.
+ */
+ void resetToDefaultSize();
+
+ /**
+ * Gets the focused tab.
+ */
+ ChatTab *getFocused() const;
+
+ /**
+ * Clear the given tab.
+ */
+ void clearTab(ChatTab *tab);
+
+ /**
+ * Clear the current tab.
+ */
+ void clearTab();
+
+ /**
+ * Switch to the previous tab in order
+ */
+ void prevTab();
+
+ /**
+ * Switch to the next tab in order
+ */
+ void nextTab();
+
+ /**
+ * Switch to the default tab
+ */
+ void defaultTab();
+
+ /**
+ * Performs action.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Request focus for typing chat message.
+ *
+ * \returns true if the input was shown
+ * false otherwise
+ */
+ bool requestChatFocus();
+
+ /**
+ * Checks whether ChatWindow is Focused or not.
+ */
+ bool isInputFocused() const;
+
+ /**
+ * Passes the text to the current tab as input
+ *
+ * @param msg The message text which is to be sent.
+ */
+ void chatInput(const std::string &msg);
+
+ /**
+ * Passes the text to the local chat tab as input
+ *
+ * @param msg The message text which is to be sent.
+ */
+ void localChatInput(const std::string &msg);
+
+ /** Called when key is pressed */
+ void keyPressed(gcn::KeyEvent &event);
+
+ /** Set the chat input as the given text. */
+ void setInputText(const std::string &text);
+
+ /** Add the given text to the chat input. */
+ void addInputText(const std::string &text, bool space = true);
+
+ /** Called to add item to chat */
+ void addItemText(const std::string &item);
+
+ /** Override to reset mTmpVisible */
+ void setVisible(bool visible);
+
+ /**
+ * Handles mouse when dragged.
+ */
+ void mouseDragged(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse when pressed.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+
+ void event(Channels channel, const Mana::Event &event);
+
+ /**
+ * Scrolls the chat window
+ *
+ * @param amount direction and amount to scroll. Negative numbers scroll
+ * up, positive numbers scroll down. The absolute amount indicates the
+ * amount of 1/8ths of chat window real estate that should be scrolled.
+ */
+ void scroll(int amount);
+
+ /**
+ * Sets the file being recorded to
+ *
+ * @param msg The file to write out to. If null, then stop recording.
+ */
+ void setRecordingFile(const std::string &msg);
+
+ bool getReturnTogglesChat() const
+ { return mReturnToggles; }
+
+ void setReturnTogglesChat(bool toggles)
+ { mReturnToggles = toggles; }
+
+ void doPresent();
+
+ void whisper(const std::string &nick, const std::string &mes,
+ Own own = BY_OTHER);
+
+ ChatTab *addWhisperTab(const std::string &nick, bool switchTo = false);
+
+ ChatTab *getWhisperTab(const std::string &nick) const;
+
+ void removeAllWhispers();
+
+ void ignoreAllWhispers();
+
+ void resortChatLog(std::string line, Own own = BY_UNKNOWN,
+ bool ignoreRecord = false,
+ bool tryRemoveColors = true);
+
+ void battleChatLog(std::string line, Own own = BY_UNKNOWN,
+ bool ignoreRecord = false,
+ bool tryRemoveColors = true);
+
+ void updateOnline(std::set<std::string> &onlinePlayers);
+
+ void loadState();
+
+ void saveState();
+
+ std::string doReplace(const std::string &msg);
+
+ protected:
+ friend class ChatTab;
+ friend class WhisperTab;
+ friend class PopupMenu;
+
+ /** Remove the given tab from the window */
+ void removeTab(ChatTab *tab);
+
+ /** Add the tab to the window */
+ void addTab(ChatTab *tab);
+
+ void removeWhisper(const std::string &nick);
+
+ void adjustTabSize();
+
+ void autoComplete();
+
+ std::string addColors(std::string &msg);
+
+ std::string autoCompleteHistory(std::string partName);
+
+ std::string autoCompleteCommands(std::string partName);
+
+ std::string autoComplete(std::vector<std::string> &names,
+ std::string partName) const;
+
+ /** Used for showing item popup on clicking links **/
+ ItemLinkHandler *mItemLinkHandler;
+
+ /** Input box for typing chat messages. */
+ ChatInput *mChatInput;
+
+ void widgetResized(const gcn::Event &event);
+
+ void initTradeFilter();
+
+ int mRainbowColor;
+
+ private:
+ void fillCommands();
+
+ bool mTmpVisible;
+
+ /** Tabbed area for holding each channel. */
+ TabbedArea *mChatTabs;
+
+ typedef std::map<const std::string, ChatTab*> TabMap;
+ /** Manage whisper tabs */
+ TabMap mWhispers;
+
+ typedef std::list<std::string> History;
+ typedef History::iterator HistoryIterator;
+ History mHistory; /**< Command history. */
+ HistoryIterator mCurHist; /**< History iterator. */
+
+ typedef std::list<std::string> Commands;
+ typedef Commands::iterator CommandsIterator;
+ History mCommands; /**< Command list. */
+
+ bool mReturnToggles; /**< Marks whether <Return> toggles the chat log
+ or not */
+
+ std::list<std::string> mTradeFilter;
+
+ gcn::DropDown *mColorPicker;
+ ColorListModel *mColorListModel;
+ int mChatColor;
+ unsigned int mChatHistoryIndex;
+};
+
+extern ChatWindow *chatWindow;
+
+#endif
diff --git a/src/gui/confirmdialog.cpp b/src/gui/confirmdialog.cpp
new file mode 100644
index 000000000..1d24a25bd
--- /dev/null
+++ b/src/gui/confirmdialog.cpp
@@ -0,0 +1,112 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/confirmdialog.h"
+
+#include "sound.h"
+
+#include "gui/gui.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/textbox.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+ConfirmDialog::ConfirmDialog(const std::string &title, const std::string &msg,
+ bool ignore, bool modal, Window *parent):
+ Window(title, modal, parent)
+{
+ mTextBox = new TextBox;
+ mTextBox->setEditable(false);
+ mTextBox->setOpaque(false);
+ mTextBox->setTextWrapped(msg, 260);
+
+ gcn::Button *yesButton = new Button(_("Yes"), "yes", this);
+ gcn::Button *noButton = new Button(_("No"), "no", this);
+ gcn::Button *ignoreButton = NULL;
+
+ if (ignore)
+ ignoreButton = new Button(_("Ignore"), "ignore", this);
+
+ const int numRows = mTextBox->getNumberOfRows();
+ int inWidth = yesButton->getWidth() + noButton->getWidth() +
+ (2 * getPadding());
+
+ if (ignoreButton)
+ inWidth += ignoreButton->getWidth();
+
+ const int fontHeight = getFont()->getHeight();
+ const int height = numRows * fontHeight;
+ int width = getFont()->getWidth(title);
+
+ if (width < mTextBox->getMinWidth())
+ width = mTextBox->getMinWidth();
+ if (width < inWidth)
+ width = inWidth;
+
+ setContentSize(mTextBox->getMinWidth() + fontHeight, height + fontHeight +
+ noButton->getHeight());
+ mTextBox->setPosition(getPadding(), getPadding());
+
+ // 8 is the padding that GUIChan adds to button widgets
+ // (top and bottom combined)
+ yesButton->setPosition((width - inWidth) / 2, height + 8);
+ noButton->setPosition(yesButton->getX() + yesButton->getWidth()
+ + (2 * getPadding()),
+ height + 8);
+ if (ignoreButton)
+ {
+ ignoreButton->setPosition(noButton->getX() + noButton->getWidth()
+ + (2 * getPadding()), height + 8);
+ }
+
+ add(mTextBox);
+ add(yesButton);
+ add(noButton);
+
+ if (ignore && ignoreButton)
+ add(ignoreButton);
+
+ if (getParent())
+ {
+ center();
+ getParent()->moveToTop(this);
+ }
+ setVisible(true);
+ yesButton->requestFocus();
+ sound.playGuiSfx("system/newmessage.ogg");
+}
+
+void ConfirmDialog::action(const gcn::ActionEvent &event)
+{
+ setActionEventId(event.getId());
+ distributeActionEvent();
+
+ // Can we receive anything else anyway?
+ if (event.getId() == "yes" || event.getId() == "no"
+ || event.getId() == "ignore")
+ {
+ scheduleDelete();
+ }
+}
+
diff --git a/src/gui/confirmdialog.h b/src/gui/confirmdialog.h
new file mode 100644
index 000000000..0ba9c869b
--- /dev/null
+++ b/src/gui/confirmdialog.h
@@ -0,0 +1,57 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OPTION_DIALOG_H
+#define OPTION_DIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class TextBox;
+
+/**
+ * An option dialog.
+ *
+ * \ingroup GUI
+ */
+class ConfirmDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ ConfirmDialog(const std::string &title, const std::string &msg,
+ bool ignore = false, bool modal = false,
+ Window *parent = NULL);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ TextBox *mTextBox;
+};
+
+#endif
diff --git a/src/gui/connectiondialog.cpp b/src/gui/connectiondialog.cpp
new file mode 100644
index 000000000..8f960d335
--- /dev/null
+++ b/src/gui/connectiondialog.cpp
@@ -0,0 +1,65 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "connectiondialog.h"
+
+#include "log.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/progressindicator.h"
+
+#include "utils/gettext.h"
+
+ConnectionDialog::ConnectionDialog(const std::string &text,
+ State cancelState):
+ Window(""),
+ mCancelState(cancelState)
+{
+ setTitleBarHeight(0);
+ setMovable(false);
+ setMinWidth(0);
+
+ ProgressIndicator *progressIndicator = new ProgressIndicator;
+ gcn::Label *label = new Label(text);
+ Button *cancelButton = new Button(_("Cancel"), "cancelButton", this);
+
+ place(0, 0, progressIndicator);
+ place(0, 1, label);
+ place(0, 2, cancelButton).setHAlign(LayoutCell::CENTER);
+ reflowLayout();
+
+ center();
+ setVisible(true);
+}
+
+void ConnectionDialog::action(const gcn::ActionEvent &)
+{
+ logger->log1("Cancel pressed");
+ Client::setState(mCancelState);
+}
+
+void ConnectionDialog::draw(gcn::Graphics *graphics)
+{
+ // Don't draw the window background, only draw the children
+ drawChildren(graphics);
+}
diff --git a/src/gui/connectiondialog.h b/src/gui/connectiondialog.h
new file mode 100644
index 000000000..623a66455
--- /dev/null
+++ b/src/gui/connectiondialog.h
@@ -0,0 +1,62 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONNECTION_H
+#define CONNECTION_H
+
+#include "client.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+/**
+ * The connection dialog.
+ *
+ * \ingroup Interface
+ */
+class ConnectionDialog : public Window, gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @param text The text to display
+ * @param cancelState The state to enter when Cancel is pressed
+ *
+ * @see Window::Window
+ */
+ ConnectionDialog(const std::string &text, State cancelState);
+
+ /**
+ * Called when the user presses Cancel. Restores the global state to
+ * the previous one.
+ */
+ void action(const gcn::ActionEvent &);
+
+ void draw(gcn::Graphics *graphics);
+
+ private:
+ gcn::Label *mLabel;
+ State mCancelState;
+};
+
+#endif
diff --git a/src/gui/debugwindow.cpp b/src/gui/debugwindow.cpp
new file mode 100644
index 000000000..2feb30c80
--- /dev/null
+++ b/src/gui/debugwindow.cpp
@@ -0,0 +1,248 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/debugwindow.h"
+
+#include "client.h"
+#include "game.h"
+#include "localplayer.h"
+#include "main.h"
+#include "map.h"
+#include "particle.h"
+
+#include "gui/setup.h"
+#include "gui/setup_video.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/chattab.h"
+
+#include "resources/image.h"
+
+#include "net/packetcounters.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+DebugWindow::DebugWindow():
+ Window(_("Debug"))
+{
+ setWindowName("Debug");
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ mUpdateTime = 0;
+ setResizable(true);
+ setCloseButton(true);
+ setSaveVisible(true);
+ setDefaultSize(500, 150, ImageRect::CENTER);
+
+#ifdef USE_OPENGL
+ switch (Image::getLoadAsOpenGL())
+ {
+ case 0:
+ mFPSText = _("%d FPS (Software)");
+ break;
+ case 1:
+ default:
+ mFPSText = _("%d FPS (fast OpenGL)");
+ break;
+ case 2:
+ mFPSText = _("%d FPS (old OpenGL)");
+ break;
+ };
+#else
+ mFPSText = _("%d FPS (Software)");
+#endif
+
+ mFPSLabel = new Label(strprintf(_("%d FPS"), 0));
+ mMusicFileLabel = new Label(strprintf(_("Music:")));
+ mMapLabel = new Label(strprintf(_("Map:")));
+ mMinimapLabel = new Label(strprintf(_("Minimap:")));
+ mTileMouseLabel = new Label(strprintf(_("Cursor: (%d, %d)"), 0, 0));
+ mParticleCountLabel = new Label(strprintf(_("Particle count: %d"), 88888));
+ mMapActorCountLabel = new Label(strprintf(
+ _("Map actors count: %d"), 88888));
+
+ mPingLabel = new Label(" ");
+ mInPackets1Label = new Label(" ");
+ mOutPackets1Label = new Label(" ");
+
+ mXYLabel = new Label(strprintf("%s (?,?)", _("Player Position:")));
+ mTargetLabel = new Label(strprintf("%s ?", _("Target:")));
+ mTargetIdLabel = new Label(strprintf("%s ? ", _("Target Id:")));
+ mTargetLevelLabel = new Label(strprintf("%s ?", _("Target Level:")));
+ mTargetPartyLabel = new Label(strprintf("%s ?", _("Target Party:")));
+ mTargetGuildLabel = new Label(strprintf("%s ?", _("Target Guild:")));
+
+ place(0, 0, mFPSLabel, 3);
+ place(4, 0, mTileMouseLabel, 2);
+ place(0, 1, mMusicFileLabel, 3);
+ place(4, 1, mParticleCountLabel, 2);
+ place(4, 2, mMapActorCountLabel, 2);
+ place(0, 2, mMapLabel, 4);
+ place(0, 3, mMinimapLabel, 4);
+ place(0, 4, mXYLabel, 4);
+ place(4, 3, mPingLabel, 2);
+ place(4, 4, mInPackets1Label, 2);
+ place(4, 5, mOutPackets1Label, 2);
+ place(0, 5, mTargetLabel, 4);
+ place(0, 6, mTargetIdLabel, 4);
+ place(0, 7, mTargetLevelLabel, 4);
+ place(0, 8, mTargetPartyLabel, 4);
+ place(0, 9, mTargetGuildLabel, 4);
+
+ loadWindowState();
+}
+
+void DebugWindow::logic()
+{
+ if (!isVisible())
+ return;
+
+ mFPSLabel->setCaption(strprintf(mFPSText.c_str(), fps));
+
+ if (player_node)
+ {
+ mXYLabel->setCaption(strprintf("%s (%d, %d)", _("Player Position:"),
+ player_node->getTileX(), player_node->getTileY()));
+ }
+ else
+ {
+ mXYLabel->setCaption(strprintf("%s (?, ?)", _("Player Position:")));
+ }
+
+ if (player_node && player_node->getTarget())
+ {
+ Being *target = player_node->getTarget();
+
+ mTargetLabel->setCaption(strprintf("%s %s (%d, %d)", _("Target:"),
+ target->getName().c_str(), target->getTileX(),
+ target->getTileY()));
+
+ mTargetIdLabel->setCaption(strprintf("%s %d",
+ _("Target Id:"), target->getId()));
+ if (target->getLevel())
+ {
+ mTargetLevelLabel->setCaption(strprintf("%s %d",
+ _("Target Level:"), target->getLevel()));
+ }
+ else
+ {
+ mTargetLevelLabel->setCaption(strprintf("%s ?",
+ _("Target Level:")));
+ }
+
+ mTargetPartyLabel->setCaption(strprintf("%s %s", _("Target Party:"),
+ target->getPartyName().c_str()));
+
+ mTargetGuildLabel->setCaption(strprintf("%s %s", _("Target Guild:"),
+ target->getGuildName().c_str()));
+ }
+ else
+ {
+ mTargetLabel->setCaption(strprintf("%s ?", _("Target:")));
+ mTargetIdLabel->setCaption(strprintf("%s ?", _("Target Id:")));
+ mTargetLevelLabel->setCaption(strprintf("%s ?", _("Target Level:")));
+ mTargetPartyLabel->setCaption(strprintf("%s ?", _("Target Party:")));
+ mTargetGuildLabel->setCaption(strprintf("%s ?", _("Target Guild:")));
+ }
+
+ const Map *map = Game::instance()->getCurrentMap();
+ if (map && viewport)
+ {
+ // Get the current mouse position
+ int mouseTileX = (viewport->getMouseX() + viewport->getCameraX())
+ / map->getTileWidth();
+ int mouseTileY = (viewport->getMouseY() + viewport->getCameraY())
+ / map->getTileHeight();
+ mTileMouseLabel->setCaption(strprintf("%s (%d, %d)",
+ _("Cursor:"), mouseTileX, mouseTileY));
+
+ mMusicFileLabel->setCaption(strprintf("%s %s", _("Music:"),
+ map->getProperty("music").c_str()));
+ mMinimapLabel->setCaption(strprintf("%s %s", _("Minimap:"),
+ map->getProperty("minimap").c_str()));
+ mMapLabel->setCaption(strprintf("%s %s", _("Map:"),
+ map->getProperty("_filename").c_str()));
+
+
+ if (mUpdateTime != cur_time)
+ {
+ mUpdateTime = cur_time;
+ mParticleCountLabel->setCaption(strprintf(_("Particle count: %d"),
+ Particle::particleCount));
+
+ mMapActorCountLabel->setCaption(
+ strprintf("%s %d", _("Map actors count:"),
+ map->getActorsCount()));
+ }
+ }
+ else
+ {
+ mTileMouseLabel->setCaption(strprintf("%s (?, ?)", _("Cursor:")));
+
+ mMusicFileLabel->setCaption(strprintf("%s ?", _("Music:")));
+ mMinimapLabel->setCaption(strprintf("%s ?", _("Minimap:")));
+ mMapLabel->setCaption(strprintf("%s ?", _("Map:")));
+
+ mMapActorCountLabel->setCaption(
+ strprintf("%s ?", _("Map actors count:")));
+ }
+
+ mMapActorCountLabel->adjustSize();
+ mParticleCountLabel->adjustSize();
+
+ if (player_node && player_node->getPingTime() != 0)
+ {
+ mPingLabel->setCaption(strprintf(_("Ping: %d ms"),
+ player_node->getPingTime()));
+ }
+ else
+ {
+ mPingLabel->setCaption(_("Ping: ? ms"));
+ }
+
+ mInPackets1Label->setCaption(strprintf(_("In: %d bytes/s"),
+ PacketCounters::getInBytes()));
+ mOutPackets1Label->setCaption(strprintf(_("Out: %d bytes/s"),
+ PacketCounters::getOutBytes()));
+
+ if (player_node)
+ player_node->tryPingRequest();
+}
+
+void DebugWindow::draw(gcn::Graphics *g)
+{
+ Window::draw(g);
+
+ if (player_node)
+ {
+ Being *target = player_node->getTarget();
+ if (target)
+ {
+ Graphics *g2 = static_cast<Graphics*>(g);
+ target->draw(g2, -target->getPixelX() + 16 + getWidth() / 2,
+ -target->getPixelY() + 32 + getHeight() / 2);
+ }
+ }
+}
diff --git a/src/gui/debugwindow.h b/src/gui/debugwindow.h
new file mode 100644
index 000000000..1c97f8ca9
--- /dev/null
+++ b/src/gui/debugwindow.h
@@ -0,0 +1,72 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DEBUGWINDOW_H
+#define DEBUGWINDOW_H
+
+#include "gui/widgets/window.h"
+
+class Label;
+
+/**
+ * The debug window.
+ *
+ * \ingroup Interface
+ */
+class DebugWindow : public Window
+{
+ public:
+ /**
+ * Constructor.
+ */
+ DebugWindow();
+
+ /**
+ * Logic (updates components' size and infos)
+ */
+ void logic();
+
+ void draw(gcn::Graphics *g);
+
+ void setPing(int pingTime);
+
+ private:
+ Label *mMusicFileLabel, *mMapLabel, *mMinimapLabel;
+ Label *mTileMouseLabel, *mFPSLabel;
+ Label *mParticleCountLabel;
+ Label *mMapActorCountLabel;
+ Label *mXYLabel;
+ Label *mTargetLabel;
+ Label *mTargetIdLabel;
+ Label *mTargetLevelLabel;
+ Label *mTargetPartyLabel;
+ Label *mTargetGuildLabel;
+ Label *mPingLabel;
+ Label *mInPackets1Label;
+ Label *mOutPackets1Label;
+
+ std::string mFPSText;
+ int mUpdateTime;
+};
+
+extern DebugWindow *debugWindow;
+
+#endif
diff --git a/src/gui/editdialog.cpp b/src/gui/editdialog.cpp
new file mode 100644
index 000000000..b8d999ce7
--- /dev/null
+++ b/src/gui/editdialog.cpp
@@ -0,0 +1,73 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/editdialog.h"
+
+#include "gui/gui.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/textfield.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+EditDialog::EditDialog(const std::string &title, const std::string &msg,
+ std::string eventOk, int width,
+ Window *parent, bool modal):
+ Window(title, modal, parent)
+{
+ mTextField = new TextField;
+ mTextField->setText(msg);
+
+ mEventOk = eventOk;
+
+ gcn::Button *okButton = new Button(_("OK"), mEventOk, this);
+
+ const int numRows = 1;
+ const int fontHeight = getFont()->getHeight();
+ const int height = numRows * fontHeight;
+
+ setContentSize(width, height + fontHeight + okButton->getHeight());
+ mTextField->setPosition(getPadding(), getPadding());
+ mTextField->setWidth(width - (2 * getPadding()));
+
+ okButton->setPosition((width - okButton->getWidth()) / 2, height + 8);
+
+ add(mTextField);
+ add(okButton);
+
+ center();
+ setVisible(true);
+ okButton->requestFocus();
+}
+
+void EditDialog::action(const gcn::ActionEvent &event)
+{
+ // Proxy button events to our listeners
+ ActionListenerIterator i;
+ for (i = mActionListeners.begin(); i != mActionListeners.end(); ++i)
+ (*i)->action(event);
+
+ if (event.getId() == mEventOk)
+ scheduleDelete();
+}
diff --git a/src/gui/editdialog.h b/src/gui/editdialog.h
new file mode 100644
index 000000000..55947b23d
--- /dev/null
+++ b/src/gui/editdialog.h
@@ -0,0 +1,66 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef EDIT_DIALOG_H
+#define EDIT_DIALOG_H
+
+#include "gui/widgets/window.h"
+#include "gui/widgets/textfield.h"
+
+#include <guichan/actionlistener.hpp>
+
+#define ACTION_EDIT_OK "edit ok"
+
+class TextField;
+
+/**
+ * An 'Ok' button dialog.
+ *
+ * \ingroup GUI
+ */
+class EditDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ EditDialog(const std::string &title, const std::string &msg,
+ std::string eventOk = ACTION_EDIT_OK, int width = 300,
+ Window *parent = NULL, bool modal = true);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ std::string getMsg()
+ { return mTextField->getText(); }
+
+ private:
+ std::string mEventOk;
+
+ TextField *mTextField;
+};
+
+#endif
diff --git a/src/gui/emotepopup.cpp b/src/gui/emotepopup.cpp
new file mode 100644
index 000000000..2a0d94aca
--- /dev/null
+++ b/src/gui/emotepopup.cpp
@@ -0,0 +1,214 @@
+/*
+ * Extended support for activating emotes
+ * Copyright (C) 2009 Aethyra Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/emotepopup.h"
+
+#include "animatedsprite.h"
+#include "configuration.h"
+#include "emoteshortcut.h"
+#include "graphics.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "gui/theme.h"
+
+#include "resources/emotedb.h"
+#include "resources/image.h"
+#include "resources/iteminfo.h"
+
+#include "utils/dtor.h"
+
+#include <guichan/mouseinput.hpp>
+#include <guichan/selectionlistener.hpp>
+
+const int EmotePopup::gridWidth = 34; // emote icon width + 4
+const int EmotePopup::gridHeight = 36; // emote icon height + 4
+
+static const int MAX_COLUMNS = 6;
+
+EmotePopup::EmotePopup():
+ mSelectedEmoteIndex(-1),
+ mHoveredEmoteIndex(-1),
+ mRowCount(1),
+ mColumnCount(1)
+{
+ // Setup emote sprites
+ for (int i = 0; i <= EmoteDB::getLast(); ++i)
+ {
+ const AnimatedSprite *sprite = EmoteDB::getAnimation(i, true);
+ if (sprite)
+ mEmotes.push_back(sprite);
+ }
+
+ mSelectionImage = Theme::getImageFromTheme("selection.png");
+ if (!mSelectionImage)
+ logger->log1("Error: Unable to load selection.png");
+
+ if (mSelectionImage)
+ mSelectionImage->setAlpha(Client::getGuiAlpha());
+
+ addMouseListener(this);
+ recalculateSize();
+ setVisible(true);
+}
+
+EmotePopup::~EmotePopup()
+{
+ if (mSelectionImage)
+ mSelectionImage->decRef();
+}
+
+void EmotePopup::draw(gcn::Graphics *graphics)
+{
+ Popup::draw(graphics);
+
+ if (!mColumnCount)
+ return;
+
+ const unsigned int emoteCount = static_cast<unsigned>(mEmotes.size());
+ const unsigned int emotesLeft
+ = static_cast<unsigned>(mEmotes.size() % mColumnCount);
+
+ for (unsigned int i = 0; i < emoteCount ; i++)
+ {
+ int row = i / mColumnCount;
+ int column = i % mColumnCount;
+
+ unsigned int emoteX = 4 + column * gridWidth;
+ unsigned int emoteY = 4 + row * gridHeight;
+
+ // Center the last row when there are less emotes than columns
+ if (emotesLeft > 0 && row == mRowCount - 1)
+ emoteX += (mColumnCount - emotesLeft) * gridWidth / 2;
+
+ // Draw selection image below hovered item
+ if (mSelectionImage && static_cast<int>(i) == mHoveredEmoteIndex)
+ {
+ static_cast<Graphics*>(graphics)->drawImage(
+ mSelectionImage, emoteX, emoteY + 4);
+ }
+
+ // Draw emote icon
+ if (mEmotes[i])
+ mEmotes[i]->draw(static_cast<Graphics*>(graphics), emoteX, emoteY);
+ }
+}
+
+void EmotePopup::mousePressed(gcn::MouseEvent &event)
+{
+ if (event.getButton() != gcn::MouseEvent::LEFT)
+ return;
+
+ const int index = getIndexAt(event.getX(), event.getY());
+ if (index != -1)
+ {
+ setSelectedEmoteIndex(index);
+ if (emoteShortcut)
+ {
+ emoteShortcut->setEmoteSelected(
+ static_cast<unsigned char>(index + 1));
+ }
+ }
+}
+
+void EmotePopup::mouseMoved(gcn::MouseEvent &event)
+{
+ Popup::mouseMoved(event);
+
+ mHoveredEmoteIndex = getIndexAt(event.getX(), event.getY());
+}
+
+int EmotePopup::getSelectedEmote() const
+{
+ return 1 + mSelectedEmoteIndex;
+}
+
+void EmotePopup::setSelectedEmoteIndex(int index)
+{
+ if (index == mSelectedEmoteIndex)
+ return;
+
+ mSelectedEmoteIndex = index;
+ distributeValueChangedEvent();
+}
+
+int EmotePopup::getIndexAt(int x, int y) const
+{
+ if (!gridWidth || !gridHeight)
+ return -1;
+
+ const unsigned int emotesLeft
+ = static_cast<unsigned>(mEmotes.size() % mColumnCount);
+ const unsigned int row = y / gridHeight;
+ unsigned int column;
+
+ // Take into account that the last row is centered
+ if (emotesLeft > 0 && static_cast<signed>(row) == mRowCount - 1)
+ {
+ int unsigned emotesMissing = mColumnCount - emotesLeft;
+ column = std::min((x - emotesMissing * gridWidth / 2) / gridWidth,
+ emotesLeft - 1);
+ }
+ else
+ {
+ column = std::min(x / gridWidth, mColumnCount - 1);
+ }
+
+ int unsigned index = column + (row * mColumnCount);
+
+ if (index < mEmotes.size())
+ return index;
+
+ return -1;
+}
+
+void EmotePopup::recalculateSize()
+{
+ const unsigned emoteCount = static_cast<unsigned>(mEmotes.size());
+
+ mRowCount = emoteCount / MAX_COLUMNS;
+ if (emoteCount % MAX_COLUMNS > 0)
+ ++mRowCount;
+
+ if (mRowCount)
+ mColumnCount = emoteCount / mRowCount;
+ else
+ mColumnCount = 1;
+
+ if (emoteCount % mRowCount > 0)
+ ++mColumnCount;
+
+ setContentSize(mColumnCount * gridWidth, mRowCount * gridHeight);
+}
+
+void EmotePopup::distributeValueChangedEvent()
+{
+ gcn::SelectionEvent event(this);
+ Listeners::const_iterator i_end = mListeners.end();
+ Listeners::const_iterator i;
+
+ for (i = mListeners.begin(); i != i_end; ++i)
+ {
+ if (*i)
+ (*i)->valueChanged(event);
+ }
+}
diff --git a/src/gui/emotepopup.h b/src/gui/emotepopup.h
new file mode 100644
index 000000000..c1026d0a5
--- /dev/null
+++ b/src/gui/emotepopup.h
@@ -0,0 +1,121 @@
+/*
+ * Extended support for activating emotes
+ * Copyright (C) 2009 Aethyra Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EMOTEPOPUP_H
+#define EMOTEPOPUP_H
+
+#include "gui/widgets/popup.h"
+
+#include <guichan/mouselistener.hpp>
+
+#include <list>
+#include <vector>
+
+class AnimatedSprite;
+class Image;
+
+namespace gcn
+{
+ class SelectionListener;
+}
+
+/**
+ * An emote popup. Used to activate emotes and assign them to shortcuts.
+ *
+ * \ingroup GUI
+ */
+class EmotePopup : public Popup
+{
+ public:
+ /**
+ * Constructor. Initializes the graphic.
+ */
+ EmotePopup();
+
+ virtual ~EmotePopup();
+
+ /**
+ * Draws the emotes.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ void mousePressed(gcn::MouseEvent &event);
+ void mouseMoved(gcn::MouseEvent &event);
+
+ /**
+ * Returns the selected emote.
+ */
+ int getSelectedEmote() const;
+
+ /**
+ * Adds a listener to the list that's notified each time a change to
+ * the selection occurs.
+ */
+ void addSelectionListener(gcn::SelectionListener *listener)
+ { mListeners.push_back(listener); }
+
+ /**
+ * Removes a listener from the list that's notified each time a change
+ * to the selection occurs.
+ */
+ void removeSelectionListener(gcn::SelectionListener *listener)
+ { mListeners.remove(listener); }
+
+ private:
+ /**
+ * Sets the index of the currently selected emote.
+ */
+ void setSelectedEmoteIndex(int index);
+
+ /**
+ * Returns the index at the specified coordinates. Returns -1 when
+ * there is no valid index.
+ */
+ int getIndexAt(int x, int y) const;
+
+ /**
+ * Determine and set the size of the container.
+ */
+ void recalculateSize();
+
+ /**
+ * Sends out selection events to the list of selection listeners.
+ */
+ void distributeValueChangedEvent();
+
+ std::vector<const AnimatedSprite*> mEmotes;
+ Image *mSelectionImage;
+ int mSelectedEmoteIndex;
+ int mHoveredEmoteIndex;
+
+ int mRowCount;
+ int mColumnCount;
+
+ typedef std::list<gcn::SelectionListener*> Listeners;
+
+ Listeners mListeners;
+
+ static const int gridWidth;
+ static const int gridHeight;
+};
+
+#endif
diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp
new file mode 100644
index 000000000..dc5dc8c04
--- /dev/null
+++ b/src/gui/equipmentwindow.cpp
@@ -0,0 +1,260 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/button.h"
+
+#include "equipment.h"
+#include "graphics.h"
+#include "inventory.h"
+#include "item.h"
+#include "localplayer.h"
+
+#include "gui/equipmentwindow.h"
+#include "gui/itempopup.h"
+#include "gui/theme.h"
+#include "gui/setup.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/playerbox.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+
+#include "resources/image.h"
+#include "resources/iteminfo.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+
+static const int BOX_WIDTH = 36;
+static const int BOX_HEIGHT = 36;
+
+// Positions of the boxes, 2nd dimension is X and Y respectively.
+static const int boxPosition[][2] =
+{
+ { 90, 40 }, // EQUIP_TORSO_SLOT
+ { 8, 78 }, // EQUIP_GLOVES_SLOT
+ { 70, 0 }, // EQUIP_HEAD_SLOT
+ { 50, 253 }, // EQUIP_LEGS_SLOT
+ { 90, 253 }, // EQUIP_FEET_SLOT
+ { 8, 213 }, // EQUIP_RING1_SLOT
+ { 129, 213 }, // EQUIP_RING2_SLOT
+ { 50, 40 }, // EQUIP_NECK_SLOT
+ { 8, 168 }, // EQUIP_FIGHT1_SLOT
+ { 129, 168 }, // EQUIP_FIGHT2_SLOT
+ { 129, 78 }, // EQUIP_PROJECTILE_SLOT
+ { 8, 123 }, // EQUIP_EVOL_RING1_SLOT
+ { 129, 123 }, // EQUIP_EVOL_RING2_SLOT
+};
+
+EquipmentWindow::EquipmentWindow(Equipment *equipment):
+ Window(_("Equipment")),
+ mEquipment(equipment),
+ mSelected(-1)
+{
+ mItemPopup = new ItemPopup;
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ // Control that shows the Player
+ PlayerBox *playerBox = new PlayerBox;
+ playerBox->setDimension(gcn::Rectangle(50, 80, 74, 168));
+ playerBox->setPlayer(player_node);
+
+ setWindowName("Equipment");
+ setCloseButton(true);
+ setSaveVisible(true);
+ setDefaultSize(180, 345, ImageRect::CENTER);
+ loadWindowState();
+
+ mUnequip = new Button(_("Unequip"), "unequip", this);
+ const gcn::Rectangle &area = getChildrenArea();
+ mUnequip->setPosition(area.width - mUnequip->getWidth() - 5,
+ area.height - mUnequip->getHeight() - 5);
+ mUnequip->setEnabled(false);
+
+ add(playerBox);
+ add(mUnequip);
+
+ for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++)
+ {
+ mEquipBox[i].posX = boxPosition[i][0] + getPadding();
+ mEquipBox[i].posY = boxPosition[i][1] + getTitleBarHeight();
+ }
+}
+
+EquipmentWindow::~EquipmentWindow()
+{
+ delete mItemPopup;
+ mItemPopup = 0;
+}
+
+void EquipmentWindow::draw(gcn::Graphics *graphics)
+{
+ // Draw window graphics
+ Window::draw(graphics);
+
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ Window::drawChildren(graphics);
+
+ for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++)
+ {
+ if (i == mSelected)
+ {
+ const gcn::Color color = Theme::getThemeColor(Theme::HIGHLIGHT);
+
+ // Set color to the highlight color
+ g->setColor(gcn::Color(color.r, color.g, color.b, getGuiAlpha()));
+ g->fillRectangle(gcn::Rectangle(mEquipBox[i].posX,
+ mEquipBox[i].posY, BOX_WIDTH, BOX_HEIGHT));
+ }
+
+ // Set color black
+ g->setColor(gcn::Color(0, 0, 0));
+ // Draw box border
+ g->drawRectangle(gcn::Rectangle(mEquipBox[i].posX, mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT));
+
+ Item *item = mEquipment->getEquipment(i);
+ if (item)
+ {
+ // Draw Item.
+ Image *image = item->getImage();
+ if (image)
+ {
+ image->setAlpha(1.0f); // Ensure the image is drawn
+ // with maximum opacity
+ g->drawImage(image,
+ mEquipBox[i].posX + 2,
+ mEquipBox[i].posY + 2);
+ if (i == EQUIP_PROJECTILE_SLOT)
+ {
+ g->setColor(Theme::getThemeColor(Theme::TEXT));
+ graphics->drawText(toString(item->getQuantity()),
+ mEquipBox[i].posX + (BOX_WIDTH / 2),
+ mEquipBox[i].posY - getFont()->getHeight(),
+ gcn::Graphics::CENTER);
+ }
+ }
+ }
+ }
+}
+
+void EquipmentWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "unequip" && mSelected > -1)
+ {
+ Item *item = mEquipment->getEquipment(mSelected);
+ Net::getInventoryHandler()->unequipItem(item);
+ setSelected(-1);
+ }
+}
+
+Item *EquipmentWindow::getItem(int x, int y) const
+{
+ for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++)
+ {
+ gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT);
+
+ if (tRect.isPointInRect(x, y))
+ return mEquipment->getEquipment(i);
+ }
+ return NULL;
+}
+
+void EquipmentWindow::mousePressed(gcn::MouseEvent& mouseEvent)
+{
+ Window::mousePressed(mouseEvent);
+
+ const int x = mouseEvent.getX();
+ const int y = mouseEvent.getY();
+
+ if (mouseEvent.getButton() == gcn::MouseEvent::LEFT)
+ {
+ // Checks if any of the presses were in the equip boxes.
+ for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++)
+ {
+ Item *item = mEquipment->getEquipment(i);
+ gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT);
+
+ if (tRect.isPointInRect(x, y) && item)
+ setSelected(i);
+ }
+ }
+ else if (mouseEvent.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (Item *item = getItem(x, y))
+ {
+ /* Convert relative to the window coordinates to absolute screen
+ * coordinates.
+ */
+ const int mx = x + getX();
+ const int my = y + getY();
+ if (viewport)
+ viewport->showPopup(this, mx, my, item, true);
+ }
+ }
+}
+
+// Show ItemTooltip
+void EquipmentWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!mItemPopup)
+ return;
+
+ const int x = event.getX();
+ const int y = event.getY();
+
+ Item *item = getItem(x, y);
+
+ if (item)
+ {
+ int mouseX, mouseY;
+ SDL_GetMouseState(&mouseX, &mouseY);
+
+ mItemPopup->setItem(item);
+ mItemPopup->position(x + getX(), y + getY());
+ }
+ else
+ {
+ mItemPopup->setVisible(false);
+ }
+}
+
+// Hide ItemTooltip
+void EquipmentWindow::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ if (mItemPopup)
+ mItemPopup->setVisible(false);
+}
+
+void EquipmentWindow::setSelected(int index)
+{
+ mSelected = index;
+ if (mUnequip)
+ mUnequip->setEnabled(mSelected != -1);
+}
diff --git a/src/gui/equipmentwindow.h b/src/gui/equipmentwindow.h
new file mode 100644
index 000000000..d80535ed6
--- /dev/null
+++ b/src/gui/equipmentwindow.h
@@ -0,0 +1,98 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EQUIPMENTWINDOW_H
+#define EQUIPMENTWINDOW_H
+
+#include "equipment.h"
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Inventory;
+class Item;
+class ItemPopup;
+
+/**
+ * Equipment dialog.
+ *
+ * \ingroup Interface
+ */
+class EquipmentWindow : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ EquipmentWindow(Equipment *equipment);
+
+ /**
+ * Destructor.
+ */
+ ~EquipmentWindow();
+
+ /**
+ * Draws the equipment window.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ void action(const gcn::ActionEvent &event);
+
+ void mousePressed(gcn::MouseEvent& mouseEvent);
+
+ private:
+ void mouseExited(gcn::MouseEvent &event);
+ void mouseMoved(gcn::MouseEvent &event);
+
+ Item *getItem(int x, int y) const;
+
+ void setSelected(int index);
+
+ Equipment *mEquipment;
+
+ /**
+ * Equipment box.
+ */
+ struct EquipBox
+ {
+ int posX;
+ int posY;
+ };
+
+ EquipBox mEquipBox[Equipment::EQUIP_VECTOREND]; /**<Equipment Boxes. */
+
+ ItemPopup *mItemPopup;
+ gcn::Button *mUnequip;
+
+ int mSelected; /**< Index of selected item. */
+};
+
+extern EquipmentWindow *equipmentWindow;
+
+#endif
diff --git a/src/gui/focushandler.cpp b/src/gui/focushandler.cpp
new file mode 100644
index 000000000..9a2a244cc
--- /dev/null
+++ b/src/gui/focushandler.cpp
@@ -0,0 +1,99 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "focushandler.h"
+
+#include "gui/widgets/window.h"
+
+void FocusHandler::requestModalFocus(gcn::Widget *widget)
+{
+ /* If there is another widget with modal focus, remove its modal focus
+ * and put it on the modal widget stack.
+ */
+ if (mModalFocusedWidget && mModalFocusedWidget != widget)
+ {
+ mModalStack.push_front(mModalFocusedWidget);
+ mModalFocusedWidget = NULL;
+ }
+
+ gcn::FocusHandler::requestModalFocus(widget);
+}
+
+void FocusHandler::releaseModalFocus(gcn::Widget *widget)
+{
+ mModalStack.remove(widget);
+
+ if (mModalFocusedWidget == widget)
+ {
+ gcn::FocusHandler::releaseModalFocus(widget);
+
+ /* Check if there were any previously modal widgets that'd still like
+ * to regain their modal focus.
+ */
+ if (!mModalStack.empty())
+ {
+ gcn::FocusHandler::requestModalFocus(mModalStack.front());
+ mModalStack.pop_front();
+ }
+ }
+}
+
+void FocusHandler::remove(gcn::Widget *widget)
+{
+ releaseModalFocus(widget);
+
+ gcn::FocusHandler::remove(widget);
+}
+
+void FocusHandler::tabNext()
+{
+ gcn::FocusHandler::tabNext();
+
+ checkForWindow();
+}
+
+void FocusHandler::tabPrevious()
+{
+ gcn::FocusHandler::tabPrevious();
+
+ checkForWindow();
+}
+
+void FocusHandler::checkForWindow()
+{
+ if (mFocusedWidget)
+ {
+ gcn::Widget *widget = mFocusedWidget->getParent();
+
+ while (widget)
+ {
+ Window *window = dynamic_cast<Window*>(widget);
+
+ if (window)
+ {
+ window->requestMoveToTop();
+ break;
+ }
+
+ widget = widget->getParent();
+ }
+ }
+}
diff --git a/src/gui/focushandler.h b/src/gui/focushandler.h
new file mode 100644
index 000000000..f933323ae
--- /dev/null
+++ b/src/gui/focushandler.h
@@ -0,0 +1,77 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FOCUSHANDLER_H
+#define FOCUSHANDLER_H
+
+#include <guichan/focushandler.hpp>
+
+#include <list>
+
+/**
+ * The focus handler. This focus handler does exactly the same as the Guichan
+ * focus handler, but keeps a stack of modal widgets to be able to handle
+ * multiple modal focus requests.
+ */
+class FocusHandler : public gcn::FocusHandler
+{
+ public:
+ /**
+ * Sets modal focus to a widget. When there is already a modal widget
+ * then that widget loses modal focus and will regain it after this
+ * widget releases his modal focus.
+ */
+ void requestModalFocus(gcn::Widget *widget);
+
+ /**
+ * Releases modal focus of a widget. When this widget had modal focus
+ * and there are other widgets that had also requested modal focus,
+ * then modal focus will be transfered to the last of those.
+ */
+ void releaseModalFocus(gcn::Widget *widget);
+
+ /**
+ * Removes a widget from the focus handler. Also makes sure no dangling
+ * pointers remain in modal focus stack.
+ */
+ void remove(gcn::Widget *widget);
+
+ /**
+ * Overloaded to allow windows to move to the top when one of their
+ * widgets is tabbed to when tabbing through focusable elements.
+ */
+ void tabNext();
+ void tabPrevious();
+
+ private:
+ /**
+ * Checks to see if the widget tabbed to is in a window, and if it is,
+ * it requests the window be moved to the top.
+ */
+ void checkForWindow();
+
+ /**
+ * Stack of widgets that have requested modal forcus.
+ */
+ std::list<gcn::Widget*> mModalStack;
+};
+
+#endif
diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp
new file mode 100644
index 000000000..57a94b3d1
--- /dev/null
+++ b/src/gui/gui.cpp
@@ -0,0 +1,310 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/gui.h"
+
+#include "gui/focushandler.h"
+#include "gui/palette.h"
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+#include "gui/truetypefont.h"
+
+#include "gui/widgets/window.h"
+#include "gui/widgets/windowcontainer.h"
+
+#include "configlistener.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+
+#include "resources/image.h"
+#include "resources/imageset.h"
+#include "resources/imageloader.h"
+#include "resources/resourcemanager.h"
+
+#include <guichan/exception.hpp>
+#include <guichan/image.hpp>
+
+// Guichan stuff
+Gui *gui = 0;
+SDLInput *guiInput = 0;
+
+// Bolded font
+gcn::Font *boldFont = 0;
+
+class GuiConfigListener : public ConfigListener
+{
+ public:
+ GuiConfigListener(Gui *g):
+ mGui(g)
+ {}
+
+ void optionChanged(const std::string &name)
+ {
+ if (name == "customcursor" && mGui)
+ {
+ bool bCustomCursor = config.getBoolValue("customcursor");
+ mGui->setUseCustomCursor(bCustomCursor);
+ }
+ }
+ private:
+ Gui *mGui;
+};
+
+Gui::Gui(Graphics *graphics):
+ mCustomCursor(false),
+ mMouseCursors(NULL),
+ mMouseCursorAlpha(1.0f),
+ mMouseInactivityTimer(0),
+ mCursorType(CURSOR_POINTER)
+{
+ logger->log1("Initializing GUI...");
+ // Set graphics
+ setGraphics(graphics);
+
+ // Set image loader
+ static ImageLoader imageLoader;
+ gcn::Image::setImageLoader(&imageLoader);
+
+ // Set input
+ guiInput = new SDLInput;
+ setInput(guiInput);
+
+ // Set focus handler
+ delete mFocusHandler;
+ mFocusHandler = new FocusHandler;
+
+ // Initialize top GUI widget
+ WindowContainer *guiTop = new WindowContainer;
+ guiTop->setFocusable(true);
+ guiTop->setDimension(gcn::Rectangle(0, 0,
+ graphics->getWidth(), graphics->getHeight()));
+ guiTop->setOpaque(false);
+ Window::setWindowContainer(guiTop);
+ setTop(guiTop);
+
+ // Set global font
+ const int fontSize = config.getIntValue("fontSize");
+ std::string fontFile = config.getValue("font", "");
+// may be here need get paths from paths.getValue?
+// std::string path = resman->getPath(fontFile);
+ if (fontFile.empty())
+ fontFile = branding.getStringValue("font");
+
+ try
+ {
+ mGuiFont = new TrueTypeFont(fontFile, fontSize);
+ }
+ catch (gcn::Exception e)
+ {
+ logger->error(std::string("Unable to load '") + fontFile +
+ std::string("': ") + e.getMessage());
+ }
+
+ // Set particle font
+ fontFile = config.getValue("particleFont", "");
+ if (fontFile.empty())
+ fontFile = branding.getStringValue("particleFont");
+
+ try
+ {
+ mInfoParticleFont = new TrueTypeFont(
+ fontFile, fontSize, TTF_STYLE_BOLD);
+ }
+ catch (gcn::Exception e)
+ {
+ logger->error(std::string("Unable to load '") + fontFile +
+ std::string("': ") + e.getMessage());
+ }
+
+ // Set bold font
+ fontFile = config.getValue("boldFont", "");
+ if (fontFile.empty())
+ fontFile = branding.getStringValue("boldFont");
+
+ try
+ {
+ boldFont = new TrueTypeFont(fontFile, fontSize);
+ }
+ catch (gcn::Exception e)
+ {
+ logger->error(std::string("Unable to load '") + fontFile +
+ std::string("': ") + e.getMessage());
+ }
+
+ // Set help font
+ fontFile = config.getValue("helpFont", "");
+ if (fontFile.empty())
+ fontFile = branding.getStringValue("helpFont");
+
+ try
+ {
+ mHelpFont = new TrueTypeFont(fontFile, fontSize);
+ }
+ catch (gcn::Exception e)
+ {
+ logger->error(std::string("Unable to load '") + fontFile +
+ std::string("': ") + e.getMessage());
+ }
+
+ gcn::Widget::setGlobalFont(mGuiFont);
+
+ // Initialize mouse cursor and listen for changes to the option
+ setUseCustomCursor(config.getBoolValue("customcursor"));
+ mConfigListener = new GuiConfigListener(this);
+ config.addListener("customcursor", mConfigListener);
+}
+
+Gui::~Gui()
+{
+ config.removeListener("customcursor", mConfigListener);
+ delete mConfigListener;
+ mConfigListener = 0;
+
+ if (mMouseCursors)
+ {
+ mMouseCursors->decRef();
+ mMouseCursors = 0;
+ }
+
+ delete mGuiFont;
+ mGuiFont = 0;
+ delete boldFont;
+ boldFont = 0;
+ delete mHelpFont;
+ mHelpFont = 0;
+ delete mInfoParticleFont;
+ mInfoParticleFont = 0;
+ delete getTop();
+
+ delete guiInput;
+ guiInput = 0;
+
+ Theme::deleteInstance();
+}
+
+void Gui::logic()
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+ resman->clearScheduled();
+
+ // Fade out mouse cursor after extended inactivity
+ if (mMouseInactivityTimer < 100 * 15)
+ {
+ ++mMouseInactivityTimer;
+ mMouseCursorAlpha = std::min(1.0f, mMouseCursorAlpha + 0.05f);
+ }
+ else
+ {
+ mMouseCursorAlpha = std::max(0.0f, mMouseCursorAlpha - 0.005f);
+ }
+
+ Palette::advanceGradients();
+
+ gcn::Gui::logic();
+}
+
+void Gui::draw()
+{
+ mGraphics->pushClipArea(getTop()->getDimension());
+ getTop()->draw(mGraphics);
+
+ int mouseX, mouseY;
+ Uint8 button = SDL_GetMouseState(&mouseX, &mouseY);
+
+ if ((SDL_GetAppState() & SDL_APPMOUSEFOCUS || button & SDL_BUTTON(1))
+ && mMouseCursors && mCustomCursor && mMouseCursorAlpha > 0.0f)
+ {
+ Image *mouseCursor = mMouseCursors->get(mCursorType);
+ if (mouseCursor)
+ {
+ mouseCursor->setAlpha(mMouseCursorAlpha);
+
+ static_cast<Graphics*>(mGraphics)->drawImage(
+ mouseCursor,
+ mouseX - 15,
+ mouseY - 17);
+ }
+ }
+
+ mGraphics->popClipArea();
+}
+
+void Gui::setUseCustomCursor(bool customCursor)
+{
+ if (customCursor != mCustomCursor)
+ {
+ mCustomCursor = customCursor;
+
+ if (mCustomCursor)
+ {
+ // Hide the SDL mouse cursor
+ SDL_ShowCursor(SDL_DISABLE);
+
+ // Load the mouse cursor
+ mMouseCursors = Theme::getImageSetFromTheme("mouse.png", 40, 40);
+
+ if (!mMouseCursors)
+ logger->log("Error: Unable to load mouse cursors.");
+ }
+ else
+ {
+ // Show the SDL mouse cursor
+ SDL_ShowCursor(SDL_ENABLE);
+
+ // Unload the mouse cursor
+ if (mMouseCursors)
+ {
+ mMouseCursors->decRef();
+ mMouseCursors = NULL;
+ }
+ }
+ }
+}
+
+void Gui::handleMouseMoved(const gcn::MouseInput &mouseInput)
+{
+ gcn::Gui::handleMouseMoved(mouseInput);
+ mMouseInactivityTimer = 0;
+}
+
+void Gui::updateFonts()
+{
+ const int fontSize = config.getIntValue("fontSize");
+ std::string fontFile = config.getValue("font", "");
+ if (fontFile.empty())
+ fontFile = branding.getStringValue("font");
+
+ static_cast<TrueTypeFont*>(mGuiFont)->loadFont(fontFile, fontSize);
+
+ fontFile = config.getValue("particleFont", "");
+ if (fontFile.empty())
+ fontFile = branding.getStringValue("particleFont");
+
+ static_cast<TrueTypeFont*>(mInfoParticleFont)->loadFont(
+ fontFile, fontSize, TTF_STYLE_BOLD);
+
+ fontFile = config.getValue("boldFont", "");
+ if (fontFile.empty())
+ fontFile = branding.getStringValue("boldFont");
+
+ static_cast<TrueTypeFont*>(boldFont)->loadFont(fontFile, fontSize);
+}
diff --git a/src/gui/gui.h b/src/gui/gui.h
new file mode 100644
index 000000000..b4ddfc299
--- /dev/null
+++ b/src/gui/gui.h
@@ -0,0 +1,148 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_H
+#define GUI_H
+
+#include "guichanfwd.h"
+
+#include <guichan/gui.hpp>
+
+class Graphics;
+class GuiConfigListener;
+class ImageSet;
+class SDLInput;
+
+/**
+ * \defgroup GUI Core GUI related classes (widgets)
+ */
+
+/**
+ * \defgroup Interface User interface related classes (windows, dialogs)
+ */
+
+/**
+ * Main GUI class.
+ *
+ * \ingroup GUI
+ */
+class Gui : public gcn::Gui
+{
+ public:
+ /**
+ * Constructor.
+ */
+ Gui(Graphics *screen);
+
+ /**
+ * Destructor.
+ */
+ ~Gui();
+
+ /**
+ * Performs logic of the GUI. Overridden to track mouse pointer
+ * activity.
+ */
+ void logic();
+
+ /**
+ * Draws the whole Gui by calling draw functions down in the
+ * Gui hierarchy. It also draws the mouse pointer.
+ */
+ void draw();
+
+ gcn::FocusHandler *getFocusHandler() const
+ { return mFocusHandler; }
+
+ /**
+ * Return game font.
+ */
+ gcn::Font *getFont() const
+ { return mGuiFont; }
+
+ /**
+ * Return help font.
+ */
+ gcn::Font *getHelpFont() const
+ { return mHelpFont; }
+
+ /**
+ * Return the Font used for "Info Particles", i.e. ones showing, what
+ * you picked up, etc.
+ */
+ gcn::Font *getInfoParticleFont() const
+ { return mInfoParticleFont; }
+
+ /**
+ * Sets whether a custom cursor should be rendered.
+ */
+ void setUseCustomCursor(bool customCursor);
+
+ /**
+ * Sets which cursor should be used.
+ */
+ void setCursorType(int index)
+ { mCursorType = index; }
+
+ void updateFonts();
+
+ /**
+ * Cursors are in graphic order from left to right.
+ * CURSOR_POINTER should be left untouched.
+ * CURSOR_TOTAL should always be last.
+ */
+ enum
+ {
+ CURSOR_POINTER = 0,
+ CURSOR_RESIZE_ACROSS,
+ CURSOR_RESIZE_DOWN,
+ CURSOR_RESIZE_DOWN_LEFT,
+ CURSOR_RESIZE_DOWN_RIGHT,
+ CURSOR_FIGHT,
+ CURSOR_PICKUP,
+ CURSOR_TALK,
+ CURSOR_TOTAL
+ };
+
+ protected:
+ void handleMouseMoved(const gcn::MouseInput &mouseInput);
+
+ private:
+ GuiConfigListener *mConfigListener;
+ gcn::Font *mGuiFont; /**< The global GUI font */
+ gcn::Font *mInfoParticleFont; /**< Font for Info Particles*/
+ gcn::Font *mHelpFont; /**< Font for Help Window*/
+ bool mCustomCursor; /**< Show custom cursor */
+ ImageSet *mMouseCursors; /**< Mouse cursor images */
+ float mMouseCursorAlpha;
+ int mMouseInactivityTimer;
+ int mCursorType;
+};
+
+extern Gui *gui; /**< The GUI system */
+extern SDLInput *guiInput; /**< GUI input */
+
+/**
+ * Bolded text font
+ */
+extern gcn::Font *boldFont;
+
+#endif // GUI_H
diff --git a/src/gui/help.cpp b/src/gui/help.cpp
new file mode 100644
index 000000000..aa114b99f
--- /dev/null
+++ b/src/gui/help.cpp
@@ -0,0 +1,106 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/help.h"
+
+#include "gui/gui.h"
+#include "gui/setup.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "resources/resourcemanager.h"
+#include "configuration.h"
+
+#include "utils/gettext.h"
+
+HelpWindow::HelpWindow():
+ Window(_("Help"))
+{
+ setMinWidth(300);
+ setMinHeight(250);
+ setContentSize(455, 350);
+ setWindowName("Help");
+ setResizable(true);
+ setupWindow->registerWindowForReset(this);
+
+ setDefaultSize(500, 400, ImageRect::CENTER);
+
+ mBrowserBox = new BrowserBox;
+ mBrowserBox->setOpaque(false);
+ mScrollArea = new ScrollArea(mBrowserBox);
+ Button *okButton = new Button(_("Close"), "close", this);
+
+ mScrollArea->setDimension(gcn::Rectangle(5, 5, 445,
+ 335 - okButton->getHeight()));
+ okButton->setPosition(450 - okButton->getWidth(),
+ 345 - okButton->getHeight());
+
+ mBrowserBox->setLinkHandler(this);
+ mBrowserBox->setFont(gui->getHelpFont());
+
+ place(0, 0, mScrollArea, 5, 3).setPadding(3);
+ place(4, 3, okButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ loadWindowState();
+}
+
+void HelpWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "close")
+ setVisible(false);
+}
+
+void HelpWindow::handleLink(const std::string &link,
+ gcn::MouseEvent *event _UNUSED_)
+{
+ std::string helpFile = link;
+ loadHelp(helpFile);
+}
+
+void HelpWindow::loadHelp(const std::string &helpFile)
+{
+ mBrowserBox->clearRows();
+
+ loadFile("header");
+ loadFile(helpFile);
+
+ mScrollArea->setVerticalScrollAmount(0);
+ setVisible(true);
+}
+
+void HelpWindow::loadFile(const std::string &file)
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+ std::string helpPath = branding.getStringValue("helpPath");
+ if (helpPath.empty())
+ helpPath = paths.getStringValue("help");
+ std::vector<std::string> lines =
+ resman->loadTextFile(helpPath + file + ".txt");
+
+ for (unsigned int i = 0; i < lines.size(); ++i)
+ mBrowserBox->addRow(lines[i]);
+}
diff --git a/src/gui/help.h b/src/gui/help.h
new file mode 100644
index 000000000..fe5cb9fe7
--- /dev/null
+++ b/src/gui/help.h
@@ -0,0 +1,76 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HELP_H
+#define HELP_H
+
+#include "gui/widgets/linkhandler.h"
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class BrowserBox;
+class LinkHandler;
+
+/**
+ * The help dialog.
+ */
+class HelpWindow : public Window, public LinkHandler,
+ public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ HelpWindow();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Handles link action.
+ */
+ void handleLink(const std::string &link,
+ gcn::MouseEvent *event _UNUSED_);
+
+ /**
+ * Loads help in the dialog.
+ */
+ void loadHelp(const std::string &helpFile);
+
+ private:
+ void loadFile(const std::string &file);
+
+ BrowserBox *mBrowserBox;
+ gcn::ScrollArea *mScrollArea;
+};
+
+extern HelpWindow *helpWindow;
+
+#endif
diff --git a/src/gui/inventorywindow.cpp b/src/gui/inventorywindow.cpp
new file mode 100644
index 000000000..abb702005
--- /dev/null
+++ b/src/gui/inventorywindow.cpp
@@ -0,0 +1,503 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/inventorywindow.h"
+
+#include "inventory.h"
+#include "item.h"
+#include "units.h"
+#include "keyboardconfig.h"
+#include "playerinfo.h"
+
+#include "gui/itemamount.h"
+#include "gui/setup.h"
+#include "gui/sdlinput.h"
+#include "gui/shopwindow.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/itemcontainer.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/progressbar.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+
+#include "resources/iteminfo.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+#include <guichan/mouseinput.hpp>
+
+#include <string>
+
+InventoryWindow::WindowList InventoryWindow::instances;
+
+InventoryWindow::InventoryWindow(Inventory *inventory):
+ Window(inventory ? (inventory->isMainInventory()
+ ? _("Inventory") : _("Storage")) : _("Inventory")),
+ mInventory(inventory),
+ mDropButton(0),
+ mSplit(false)
+{
+ listen(CHANNEL_ATTRIBUTES);
+
+ setWindowName(isMainInventory() ? "Inventory" : "Storage");
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ setResizable(true);
+ setCloseButton(true);
+ setSaveVisible(true);
+
+ setDefaultSize(387, 307, ImageRect::CENTER);
+ setMinWidth(316);
+ setMinHeight(179);
+ addKeyListener(this);
+
+ mItems = new ItemContainer(mInventory);
+ mItems->addSelectionListener(this);
+
+ gcn::ScrollArea *invenScroll = new ScrollArea(mItems);
+ invenScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mSlotsLabel = new Label(_("Slots:"));
+ mSlotsBar = new ProgressBar(0.0f, 100, 20, Theme::PROG_INVY_SLOTS);
+
+ if (isMainInventory())
+ {
+ std::string equip = _("Equip");
+ std::string use = _("Use");
+ std::string unequip = _("Unequip");
+
+ std::string longestUseString = getFont()->getWidth(equip) >
+ getFont()->getWidth(use) ? equip : use;
+
+ if (getFont()->getWidth(longestUseString) <
+ getFont()->getWidth(unequip))
+ {
+ longestUseString = unequip;
+ }
+
+ mUseButton = new Button(longestUseString, "use", this);
+ mUseButton2 = new Button(longestUseString, "equip", this);
+ mDropButton = new Button(_("Drop..."), "drop", this);
+ mSplitButton = new Button(_("Split"), "split", this);
+ mOutfitButton = new Button(_("Outfits"), "outfit", this);
+ mShopButton = new Button(_("Shop"), "shop", this);
+
+ mWeightLabel = new Label(_("Weight:"));
+ mWeightBar = new ProgressBar(0.0f, 100, 20, Theme::PROG_WEIGHT);
+
+ place(0, 0, mWeightLabel).setPadding(3);
+ place(1, 0, mWeightBar, 3);
+ place(4, 0, mSlotsLabel).setPadding(3);
+ place(5, 0, mSlotsBar, 2);
+ place(0, 1, invenScroll, 7).setPadding(3);
+ place(0, 2, mUseButton);
+ place(1, 2, mUseButton2);
+ place(2, 2, mDropButton);
+ place(4, 2, mSplitButton);
+ place(5, 2, mShopButton);
+ place(6, 2, mOutfitButton);
+
+ updateWeight();
+ }
+ else
+ {
+ mStoreButton = new Button(_("Store"), "store", this);
+ mRetrieveButton = new Button(_("Retrieve"), "retrieve", this);
+ mCloseButton = new Button(_("Close"), "close", this);
+
+ place(0, 0, mSlotsLabel).setPadding(3);
+ place(1, 0, mSlotsBar, 3);
+ place(0, 1, invenScroll, 4, 4);
+ place(0, 5, mStoreButton);
+ place(1, 5, mRetrieveButton);
+ place(3, 5, mCloseButton);
+ }
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(1, Layout::AUTO_SET);
+
+ mInventory->addInventoyListener(this);
+
+ instances.push_back(this);
+
+ if (inventory->isMainInventory())
+ {
+ updateDropButton();
+ }
+ else
+ {
+ if (!instances.empty())
+ instances.front()->updateDropButton();
+ }
+
+ loadWindowState();
+ slotsChanged(mInventory);
+
+ if (!isMainInventory())
+ setVisible(true);
+}
+
+InventoryWindow::~InventoryWindow()
+{
+ instances.remove(this);
+ mInventory->removeInventoyListener(this);
+ if (!instances.empty())
+ instances.front()->updateDropButton();
+}
+
+void InventoryWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "outfit")
+ {
+ extern Window *outfitWindow;
+ if (outfitWindow)
+ {
+ outfitWindow->setVisible(!outfitWindow->isVisible());
+ if (outfitWindow->isVisible())
+ outfitWindow->requestMoveToTop();
+ }
+ }
+
+ if (event.getId() == "shop")
+ {
+ if (shopWindow)
+ {
+ shopWindow->setVisible(!shopWindow->isVisible());
+ if (shopWindow->isVisible())
+ shopWindow->requestMoveToTop();
+ }
+ }
+ else if (event.getId() == "close")
+ {
+ close();
+ }
+ else if (event.getId() == "store")
+ {
+ if (!inventoryWindow || !inventoryWindow->isVisible())
+ return;
+
+ Item *item = inventoryWindow->getSelectedItem();
+
+ if (!item)
+ return;
+
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd, this, item);
+ }
+
+ Item *item = mItems->getSelectedItem();
+
+ if (!item)
+ return;
+
+ if (event.getId() == "use")
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ Net::getInventoryHandler()->unequipItem(item);
+ else
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ else
+ Net::getInventoryHandler()->useItem(item);
+ }
+ if (event.getId() == "equip")
+ {
+ if (!item->isEquipment())
+ {
+ if (item->isEquipped())
+ Net::getInventoryHandler()->unequipItem(item);
+ else
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ else
+ Net::getInventoryHandler()->useItem(item);
+ }
+ else if (event.getId() == "drop")
+ {
+ if (isStorageActive())
+ {
+ Item *item = mItems->getSelectedItem();
+
+ if (!item)
+ return;
+
+ Net::getInventoryHandler()->moveItem(Inventory::INVENTORY,
+ item->getInvIndex(), item->getQuantity(),
+ Inventory::STORAGE);
+ }
+ else
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop,
+ this, item);
+ }
+ }
+ else if (event.getId() == "split")
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item,
+ (item->getQuantity() - 1));
+ }
+ else if (event.getId() == "retrieve")
+ {
+ Item *item = mItems->getSelectedItem();
+
+ if (!item)
+ return;
+
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, this,
+ item);
+ }
+}
+
+Item *InventoryWindow::getSelectedItem() const
+{
+ return mItems->getSelectedItem();
+}
+
+void InventoryWindow::mouseClicked(gcn::MouseEvent &event)
+{
+ Window::mouseClicked(event);
+
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ Item *item = mItems->getSelectedItem();
+
+ if (!item)
+ return;
+
+ /* Convert relative to the window coordinates to absolute screen
+ * coordinates.
+ */
+ const int mx = event.getX() + getX();
+ const int my = event.getY() + getY();
+
+ if (viewport)
+ viewport->showPopup(this, mx, my, item, isMainInventory());
+ }
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (isStorageActive() && keyboard.isKeyActive(keyboard.KEY_EMOTE))
+ {
+ Item *item = mItems->getSelectedItem();
+
+ if (!item || !mInventory)
+ return;
+
+ if (mInventory->isMainInventory())
+ {
+ Net::getInventoryHandler()->moveItem(Inventory::INVENTORY,
+ item->getInvIndex(), item->getQuantity(),
+ Inventory::STORAGE);
+ }
+ else
+ {
+ Net::getInventoryHandler()->moveItem(Inventory::STORAGE,
+ item->getInvIndex(), item->getQuantity(),
+ Inventory::INVENTORY);
+ }
+ }
+ }
+}
+
+void InventoryWindow::keyPressed(gcn::KeyEvent &event)
+{
+ switch (event.getKey().getValue())
+ {
+ case Key::LEFT_SHIFT:
+ case Key::RIGHT_SHIFT:
+ mSplit = true;
+ break;
+ default:
+ break;
+ }
+}
+
+void InventoryWindow::keyReleased(gcn::KeyEvent &event)
+{
+ switch (event.getKey().getValue())
+ {
+ case Key::LEFT_SHIFT:
+ case Key::RIGHT_SHIFT:
+ mSplit = false;
+ break;
+ default:
+ break;
+ }
+}
+
+void InventoryWindow::valueChanged(const gcn::SelectionEvent &event _UNUSED_)
+{
+ if (!mInventory || !mInventory->isMainInventory())
+ return;
+
+ Item *item = mItems->getSelectedItem();
+
+ if (mSplit && item && Net::getInventoryHandler()->
+ canSplit(mItems->getSelectedItem()))
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item,
+ (item->getQuantity() - 1));
+ }
+
+ if (!item || item->getQuantity() == 0)
+ {
+ if (mUseButton)
+ mUseButton->setEnabled(true);
+ if (mUseButton2)
+ mUseButton2->setEnabled(true);
+ if (mDropButton)
+ mDropButton->setEnabled(true);
+ return;
+ }
+
+ if (mUseButton)
+ mUseButton->setEnabled(true);
+ if (mUseButton2)
+ mUseButton2->setEnabled(true);
+ if (mDropButton)
+ mDropButton->setEnabled(true);
+
+ if (mUseButton && item && item->isEquipment())
+ {
+ if (item->isEquipped())
+ mUseButton->setCaption(_("Unequip"));
+ else
+ mUseButton->setCaption(_("Equip"));
+ mUseButton2->setCaption(_("Use"));
+ }
+ else if (mUseButton2)
+ {
+ mUseButton->setCaption(_("Use"));
+ if (item->isEquipped())
+ mUseButton2->setCaption(_("Unequip"));
+ else
+ mUseButton2->setCaption(_("Equip"));
+ }
+
+ updateDropButton();
+
+ if (mSplitButton)
+ {
+ if (Net::getInventoryHandler()->canSplit(item))
+ mSplitButton->setEnabled(true);
+ else
+ mSplitButton->setEnabled(false);
+ }
+}
+
+
+void InventoryWindow::setSplitAllowed(bool allowed)
+{
+ mSplitButton->setVisible(allowed);
+}
+
+void InventoryWindow::close()
+{
+ if (this == inventoryWindow)
+ {
+ setVisible(false);
+ }
+ else
+ {
+ Net::getInventoryHandler()->closeStorage(Inventory::STORAGE);
+ scheduleDelete();
+ }
+}
+
+void InventoryWindow::event(Channels channel _UNUSED_,
+ const Mana::Event &event)
+{
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ int id = event.getInt("id");
+ if (id == TOTAL_WEIGHT || id == MAX_WEIGHT)
+ updateWeight();
+ }
+}
+
+void InventoryWindow::updateWeight()
+{
+ if (!isMainInventory())
+ return;
+
+ int total = PlayerInfo::getAttribute(TOTAL_WEIGHT);
+ int max = PlayerInfo::getAttribute(MAX_WEIGHT);
+
+ if (max <= 0)
+ return;
+
+ // Adjust progress bar
+ mWeightBar->setProgress(static_cast<float>(total)
+ / static_cast<float>(max));
+ mWeightBar->setText(strprintf("%s/%s", Units::formatWeight(total).c_str(),
+ Units::formatWeight(max).c_str()));
+}
+
+void InventoryWindow::slotsChanged(Inventory* inventory)
+{
+ if (inventory == mInventory)
+ {
+ const int usedSlots = mInventory->getNumberOfSlotsUsed();
+ const int maxSlots = mInventory->getSize();
+
+ if (maxSlots)
+ {
+ mSlotsBar->setProgress(static_cast<float>(usedSlots)
+ / static_cast<float>(maxSlots));
+ }
+
+ mSlotsBar->setText(strprintf("%d/%d", usedSlots, maxSlots));
+ }
+}
+
+void InventoryWindow::updateDropButton()
+{
+ if (!mDropButton)
+ return;
+
+ if (isStorageActive())
+ {
+ mDropButton->setCaption(_("Store"));
+ }
+ else
+ {
+ Item *item = 0;
+ if (mItems)
+ item = mItems->getSelectedItem();
+
+ if (item && item->getQuantity() > 1)
+ mDropButton->setCaption(_("Drop..."));
+ else
+ mDropButton->setCaption(_("Drop"));
+ }
+}
diff --git a/src/gui/inventorywindow.h b/src/gui/inventorywindow.h
new file mode 100644
index 000000000..87f57eb9d
--- /dev/null
+++ b/src/gui/inventorywindow.h
@@ -0,0 +1,155 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef INVENTORYWINDOW_H
+#define INVENTORYWINDOW_H
+
+#include "inventory.h"
+#include "listener.h"
+
+#include "gui/widgets/window.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Item;
+class ItemContainer;
+class ProgressBar;
+class TextBox;
+
+/**
+ * Inventory dialog.
+ *
+ * \ingroup Interface
+ */
+class InventoryWindow : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener,
+ public gcn::SelectionListener,
+ public InventoryListener,
+ public Mana::Listener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ InventoryWindow(Inventory *inventory);
+
+ /**
+ * Destructor.
+ */
+ ~InventoryWindow();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Returns the selected item.
+ */
+ Item* getSelectedItem() const;
+
+ /**
+ * Handles the mouse clicks.
+ */
+ void mouseClicked(gcn::MouseEvent &event);
+
+ /**
+ * Handles the key presses.
+ */
+ void keyPressed(gcn::KeyEvent &event);
+
+ /**
+ * Handles the key releases.
+ */
+ void keyReleased(gcn::KeyEvent &event);
+
+ /**
+ * Updates labels to currently selected item.
+ */
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ /**
+ * Sets whether the split button should be shown.
+ */
+ void setSplitAllowed(bool allowed);
+
+ /**
+ * Closes the Storage Window, as well as telling the server that the
+ * window has been closed.
+ */
+ void close();
+
+ void slotsChanged(Inventory* inventory);
+
+ bool isMainInventory()
+ { return mInventory->isMainInventory(); }
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isStorageActive()
+ { return instances.size() > 1; }
+
+ void updateDropButton();
+
+ void event(Channels channel, const Mana::Event &event);
+
+ private:
+ /**
+ * Updates the weight bar.
+ */
+ void updateWeight();
+
+
+ typedef std::list<InventoryWindow*> WindowList;
+ static WindowList instances;
+
+ Inventory *mInventory;
+ ItemContainer *mItems;
+
+ std::string mWeight, mSlots;
+
+ gcn::Button *mUseButton, *mUseButton2, *mDropButton,
+ *mSplitButton, *mOutfitButton, *mShopButton,
+ *mStoreButton, *mRetrieveButton, *mCloseButton;
+
+ gcn::Label *mWeightLabel, *mSlotsLabel;
+
+ ProgressBar *mWeightBar, *mSlotsBar;
+
+ bool mSplit;
+};
+
+extern InventoryWindow *inventoryWindow;
+
+#endif
diff --git a/src/gui/itemamount.cpp b/src/gui/itemamount.cpp
new file mode 100644
index 000000000..25356823f
--- /dev/null
+++ b/src/gui/itemamount.cpp
@@ -0,0 +1,432 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/itemamount.h"
+
+#include "item.h"
+#include "keyboardconfig.h"
+
+#include "gui/trade.h"
+#include "net/inventoryhandler.h"
+#include "gui/itempopup.h"
+#include "net/net.h"
+#include "gui/shopwindow.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/icon.h"
+#include "gui/widgets/inttextfield.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/slider.h"
+
+#include "resources/itemdb.h"
+
+#include "utils/gettext.h"
+
+#include <math.h>
+
+class ItemsModal : public gcn::ListModel
+{
+public:
+ ItemsModal()
+ {
+ std::map<int, ItemInfo*> info = ItemDB::getItemInfos();
+ std::list<std::string> tempStrings;
+
+ for (std::map<int, ItemInfo*>::const_iterator
+ i = info.begin(), i_end = info.end();
+ i != i_end; ++i)
+ {
+ if (i->first < 0)
+ continue;
+
+ ItemInfo info = (*i->second);
+ std::string name = info.getName();
+ if (name != "unnamed" && !info.getName().empty()
+ && info.getName() != "unnamed")
+ {
+ tempStrings.push_back(name);
+ }
+ }
+ tempStrings.sort();
+ for (std::list<std::string>::const_iterator i = tempStrings.begin(),
+ i_end = tempStrings.end(); i != i_end; ++i)
+ {
+ mStrings.push_back(*i);
+ }
+ }
+
+ virtual ~ItemsModal()
+ { }
+
+ virtual int getNumberOfElements()
+ {
+ return static_cast<int>(mStrings.size());
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i < 0 || i >= getNumberOfElements())
+ return _("???");
+
+ return mStrings.at(i);
+ }
+private:
+ std::vector<std::string> mStrings;
+};
+
+void ItemAmountWindow::finish(Item *item, int amount, int price, Usage usage)
+{
+ switch (usage)
+ {
+ case TradeAdd:
+ if (tradeWindow)
+ tradeWindow->tradeItem(item, amount);
+ break;
+ case ItemDrop:
+ Net::getInventoryHandler()->dropItem(item, amount);
+ break;
+ case ItemSplit:
+ Net::getInventoryHandler()->splitItem(item, amount);
+ break;
+ case StoreAdd:
+ Net::getInventoryHandler()->moveItem(Inventory::INVENTORY,
+ item->getInvIndex(), amount,
+ Inventory::STORAGE);
+ break;
+ case StoreRemove:
+ Net::getInventoryHandler()->moveItem(Inventory::STORAGE,
+ item->getInvIndex(), amount,
+ Inventory::INVENTORY);
+ break;
+ case ShopBuyAdd:
+ if (shopWindow)
+ shopWindow->addBuyItem(item, amount, price);
+ break;
+ case ShopSellAdd:
+ if (shopWindow)
+ shopWindow->addSellItem(item, amount, price);
+ break;
+ default:
+ break;
+ }
+}
+
+ItemAmountWindow::ItemAmountWindow(Usage usage, Window *parent, Item *item,
+ int maxRange):
+ Window("", true, parent),
+ mItemPriceTextField(0),
+ mGPLabel(0),
+ mItem(item),
+ mMax(maxRange),
+ mUsage(usage),
+ mItemPriceSlide(0),
+ mItemsModal(0),
+ mPrice(0)
+{
+ if (!mItem)
+ {
+ setVisible(false);
+ return;
+ }
+ if (usage == ShopBuyAdd)
+ mMax = 10000;
+ else if (!mMax)
+ mMax = mItem->getQuantity();
+
+ // Save keyboard state
+ mEnabledKeyboard = keyboard.isEnabled();
+ keyboard.setEnabled(false);
+
+ // Integer field
+ mItemAmountTextField = new IntTextField(1);
+ mItemAmountTextField->setRange(1, mMax);
+ mItemAmountTextField->setWidth(35);
+ mItemAmountTextField->addKeyListener(this);
+
+ // Slider
+ mItemAmountSlide = new Slider(1.0, mMax);
+ mItemAmountSlide->setHeight(10);
+ mItemAmountSlide->setActionEventId("slide");
+ mItemAmountSlide->addActionListener(this);
+
+ if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd)
+ {
+ // Integer field
+ mItemPriceTextField = new IntTextField(1);
+ mItemPriceTextField->setRange(1, 10000000);
+ mItemPriceTextField->setWidth(35);
+ mItemPriceTextField->addKeyListener(this);
+
+ // Slider
+ mItemPriceSlide = new Slider(1.0, 10000000);
+ mItemPriceSlide->setHeight(10);
+ mItemPriceSlide->setActionEventId("slidePrice");
+ mItemPriceSlide->addActionListener(this);
+
+ mGPLabel = new Label(" GP");
+ }
+
+ if (mUsage == ShopBuyAdd)
+ {
+ mItemsModal = new ItemsModal;
+ mItemDropDown = new DropDown(mItemsModal);
+ mItemDropDown->setActionEventId("itemType");
+ mItemDropDown->addActionListener(this);
+ }
+
+ //Item icon
+ Image *image = item->getImage();
+ mItemIcon = new Icon(image);
+
+ // Buttons
+ Button *minusAmountButton = new Button(_("-"), "dec", this);
+ Button *plusAmountButton = new Button(_("+"), "inc", this);
+ Button *okButton = new Button(_("OK"), "ok", this);
+ Button *cancelButton = new Button(_("Cancel"), "cancel", this);
+ Button *addAllButton = new Button(_("All"), "all", this);
+
+ minusAmountButton->adjustSize();
+ minusAmountButton->setWidth(plusAmountButton->getWidth());
+
+ // Set positions
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+ int n = 0;
+ if (mUsage == ShopBuyAdd)
+ {
+ place(0, n, mItemDropDown, 8);
+ n++;
+ }
+ place(1, n, minusAmountButton);
+ place(2, n, mItemAmountTextField, 3);
+ place(5, n, plusAmountButton);
+ place(6, n, addAllButton);
+
+ place(0, n, mItemIcon, 1, 3);
+ place(1, n + 1, mItemAmountSlide, 7);
+
+ if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd)
+ {
+ Button *minusPriceButton = new Button(_("-"), "decPrice", this);
+ Button *plusPriceButton = new Button(_("+"), "incPrice", this);
+ minusPriceButton->adjustSize();
+ minusPriceButton->setWidth(plusPriceButton->getWidth());
+
+ place(1, n + 2, minusPriceButton);
+ place(2, n + 2, mItemPriceTextField, 3);
+ place(5, n + 2, plusPriceButton);
+ place(6, n + 2, mGPLabel);
+
+ place(1, n + 3, mItemPriceSlide, 7);
+ place(4, n + 5, cancelButton);
+ place(5, n + 5, okButton);
+ }
+ else
+ {
+ place(4, n + 2, cancelButton);
+ place(5, n + 2, okButton);
+ }
+
+ reflowLayout(225, 0);
+
+ resetAmount();
+
+ switch (usage)
+ {
+ case TradeAdd:
+ setCaption(_("Select amount of items to trade."));
+ break;
+ case ItemDrop:
+ setCaption(_("Select amount of items to drop."));
+ break;
+ case StoreAdd:
+ setCaption(_("Select amount of items to store."));
+ break;
+ case StoreRemove:
+ setCaption(_("Select amount of items to retrieve."));
+ break;
+ case ItemSplit:
+ setCaption(_("Select amount of items to split."));
+ break;
+ case ShopBuyAdd:
+ setCaption(_("Add to buy shop."));
+ break;
+ case ShopSellAdd:
+ setCaption(_("Add to sell shop."));
+ break;
+ default:
+ setCaption(_("Unknown."));
+ break;
+ }
+
+ setLocationRelativeTo(getParentWindow());
+ setVisible(true);
+
+ mItemPopup = new ItemPopup;
+ mItemIcon->addMouseListener(this);
+}
+
+ItemAmountWindow::~ItemAmountWindow()
+{
+ delete mItemPopup;
+ mItemPopup = 0;
+}
+
+// Show ItemTooltip
+void ItemAmountWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!viewport || !mItemPopup)
+ return;
+
+ if (event.getSource() == mItemIcon)
+ {
+ mItemPopup->setItem(mItem);
+ mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
+ }
+}
+
+// Hide ItemTooltip
+void ItemAmountWindow::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ if (mItemPopup)
+ mItemPopup->setVisible(false);
+}
+
+void ItemAmountWindow::resetAmount()
+{
+ mItemAmountTextField->setValue(1);
+}
+
+void ItemAmountWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "cancel")
+ {
+ close();
+ return;
+ }
+ else if (event.getId() == "ok")
+ {
+ if (mItemPriceTextField)
+ {
+ finish(mItem, mItemAmountTextField->getValue(),
+ mItemPriceTextField->getValue(), mUsage);
+ }
+ else
+ {
+ finish(mItem, mItemAmountTextField->getValue(),
+ 0, mUsage);
+ }
+ close();
+ return;
+ }
+ else if (event.getId() == "itemType")
+ {
+ if (!mItemDropDown || !mItemsModal)
+ return;
+
+ std::string str = mItemsModal->getElementAt(
+ mItemDropDown->getSelected());
+ int id = ItemDB::get(str).getId();
+
+ mItem = new Item(id, 10000);
+
+ if (mUsage == ShopBuyAdd)
+ mMax = 10000;
+ else if (!mMax)
+ mMax = mItem->getQuantity();
+
+ Image *image = mItem->getImage();
+ mItemIcon->setImage(image);
+ }
+
+ int amount = mItemAmountTextField->getValue();
+
+ if (event.getId() == "inc" && amount < mMax)
+ amount++;
+ else if (event.getId() == "dec" && amount > 1)
+ amount--;
+ else if (event.getId() == "all")
+ amount = mMax;
+ else if (event.getId() == "slide")
+ amount = static_cast<int>(mItemAmountSlide->getValue());
+ mItemAmountTextField->setValue(amount);
+ mItemAmountSlide->setValue(amount);
+
+ if (mItemPriceTextField && mItemPriceSlide)
+ {
+ int price = 0;
+
+ if (mPrice > 7)
+ mPrice = 7;
+ else if (mPrice < 0)
+ mPrice = 0;
+
+ if (event.getId() == "incPrice")
+ {
+ mPrice++;
+ price = static_cast<int>(pow(10, mPrice));
+ }
+ else if (event.getId() == "decPrice")
+ {
+ mPrice--;
+ price = static_cast<int>(pow(10, mPrice));
+ }
+ else if (event.getId() == "slidePrice")
+ {
+ price = static_cast<int>(mItemPriceSlide->getValue());
+ if (price)
+ mPrice = static_cast<int>(log(price));
+ else
+ mPrice = 0;
+ }
+ mItemPriceTextField->setValue(price);
+ mItemPriceSlide->setValue(price);
+ }
+}
+
+void ItemAmountWindow::close()
+{
+ keyboard.setEnabled(mEnabledKeyboard);
+ scheduleDelete();
+}
+
+void ItemAmountWindow::keyReleased(gcn::KeyEvent &keyEvent _UNUSED_)
+{
+ mItemAmountSlide->setValue(mItemAmountTextField->getValue());
+}
+
+void ItemAmountWindow::showWindow(Usage usage, Window *parent, Item *item,
+ int maxRange)
+{
+ if (!item)
+ return;
+
+ if (!maxRange)
+ maxRange = item->getQuantity();
+
+ if (usage != ShopBuyAdd && usage != ShopSellAdd && maxRange <= 1)
+ finish(item, maxRange, 0, usage);
+ else
+ new ItemAmountWindow(usage, parent, item, maxRange);
+}
diff --git a/src/gui/itemamount.h b/src/gui/itemamount.h
new file mode 100644
index 000000000..f28581294
--- /dev/null
+++ b/src/gui/itemamount.h
@@ -0,0 +1,124 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ITEM_AMOUNT_WINDOW_H
+#define ITEM_AMOUNT_WINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/keylistener.hpp>
+#include <guichan/actionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Icon;
+class IntTextField;
+class Item;
+class ItemsModal;
+class ItemPopup;
+class Label;
+
+/**
+ * Window used for selecting the amount of items to drop, trade or split.
+ *
+ * \ingroup Interface
+ */
+class ItemAmountWindow : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ enum Usage
+ {
+ TradeAdd = 0,
+ ItemDrop,
+ StoreAdd,
+ StoreRemove,
+ ItemSplit,
+ ShopBuyAdd,
+ ShopSellAdd
+ };
+
+ /**
+ * Called when receiving actions from widget.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Sets default amount value.
+ */
+ void resetAmount();
+
+ // MouseListener
+ void mouseMoved(gcn::MouseEvent &event);
+ void mouseExited(gcn::MouseEvent &event);
+
+ /**
+ * Schedules the Item Amount window for deletion.
+ */
+ void close();
+
+ void keyReleased(gcn::KeyEvent &keyEvent);
+
+ /**
+ * Creates the dialog, or bypass it if there aren't enough items.
+ */
+ static void showWindow(Usage usage, Window *parent, Item *item,
+ int maxRange = 0);
+
+ ~ItemAmountWindow();
+
+ private:
+ static void finish(Item *item, int amount, int price, Usage usage);
+
+ ItemAmountWindow(Usage usage, Window *parent, Item *item,
+ int maxRange = 0);
+
+ IntTextField *mItemAmountTextField; /**< Item amount caption. */
+ IntTextField *mItemPriceTextField; /**< Item price caption. */
+ Label *mGPLabel;
+ Item *mItem;
+ Icon *mItemIcon;
+
+ int mMax;
+ Usage mUsage;
+ ItemPopup *mItemPopup;
+
+ /**
+ * Item Amount buttons.
+ */
+ gcn::Slider *mItemAmountSlide;
+
+ gcn::Slider *mItemPriceSlide;
+
+ gcn::DropDown *mItemDropDown;
+
+ ItemsModal *mItemsModal;
+
+ bool mEnabledKeyboard;
+ int mPrice;
+};
+
+#endif // ITEM_AMOUNT_WINDOW_H
diff --git a/src/gui/itempopup.cpp b/src/gui/itempopup.cpp
new file mode 100644
index 000000000..15a0be6bc
--- /dev/null
+++ b/src/gui/itempopup.cpp
@@ -0,0 +1,238 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/itempopup.h"
+
+#include "graphics.h"
+#include "item.h"
+#include "units.h"
+
+#include "gui/gui.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/icon.h"
+#include "gui/widgets/textbox.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include <guichan/font.hpp>
+
+#include <guichan/widgets/label.hpp>
+#include <guichan/widgets/container.hpp>
+
+ItemPopup::ItemPopup():
+ Popup("ItemPopup"),
+ mIcon(0)
+{
+ // Item Name
+ mItemName = new gcn::Label;
+ mItemName->setFont(boldFont);
+ mItemName->setPosition(getPadding(), getPadding());
+
+ const int fontHeight = getFont()->getHeight();
+
+ // Item Description
+ mItemDesc = new TextBox;
+ mItemDesc->setEditable(false);
+ mItemDesc->setPosition(getPadding(), fontHeight);
+
+ // Item Effect
+ mItemEffect = new TextBox;
+ mItemEffect->setEditable(false);
+ mItemEffect->setPosition(getPadding(), 2 * fontHeight + 2 * getPadding());
+
+ // Item Weight
+ mItemWeight = new TextBox;
+ mItemWeight->setEditable(false);
+ mItemWeight->setPosition(getPadding(), 3 * fontHeight + 4 * getPadding());
+
+ mIcon = new Icon(0);
+
+ add(mItemName);
+ add(mItemDesc);
+ add(mItemEffect);
+ add(mItemWeight);
+ add(mIcon);
+
+ addMouseListener(this);
+}
+
+ItemPopup::~ItemPopup()
+{
+ if (mIcon)
+ {
+ Image *image = mIcon->getImage();
+ if (image)
+ image->decRef();
+ }
+}
+
+void ItemPopup::setItem(const Item *item, bool showImage)
+{
+ if (!item)
+ return;
+
+ const ItemInfo &ii = item->getInfo();
+ setItem(ii, showImage);
+ if (item->getRefine() > 0)
+ {
+ mItemName->setCaption(strprintf("%s (+%d), %d", ii.getName().c_str(),
+ item->getRefine(), ii.getId()));
+ mItemName->adjustSize();
+ int minWidth = mItemName->getWidth() + 8;
+ if (getWidth() < minWidth)
+ setWidth(minWidth);
+ }
+}
+
+void ItemPopup::setItem(const ItemInfo &item, bool showImage)
+{
+ if (!mIcon || item.getName() == mItemName->getCaption())
+ return;
+
+ int space = 0;
+
+ Image *oldImage = mIcon->getImage();
+ if (oldImage)
+ oldImage->decRef();
+
+ if (showImage)
+ {
+ ResourceManager *resman = ResourceManager::getInstance();
+ Image *image = resman->getImage(
+ paths.getStringValue("itemIcons")
+ + item.getDisplay().image);
+
+ mIcon->setImage(image);
+ if (image)
+ {
+ int x = getPadding();
+ int y = getPadding();
+ mIcon->setPosition(x, y);
+ space = mIcon->getWidth();
+ }
+ }
+ else
+ {
+ mIcon->setImage(0);
+ }
+
+ mItemType = item.getType();
+
+ mItemName->setCaption(item.getName() + _(", ") + toString(item.getId()));
+ mItemName->adjustSize();
+ mItemName->setForegroundColor(getColor(mItemType));
+ mItemName->setPosition(getPadding() + space, getPadding());
+
+ mItemDesc->setTextWrapped(item.getDescription(), 196);
+ mItemEffect->setTextWrapped(item.getEffect(), 196);
+ mItemWeight->setTextWrapped(strprintf(_("Weight: %s"),
+ Units::formatWeight(item.getWeight()).c_str()),
+ 196);
+
+ int minWidth = mItemName->getWidth() + space;
+
+ if (mItemName->getWidth() + space > minWidth)
+ minWidth = mItemName->getWidth() + space;
+ if (mItemDesc->getMinWidth() > minWidth)
+ minWidth = mItemDesc->getMinWidth();
+ if (mItemEffect->getMinWidth() > minWidth)
+ minWidth = mItemEffect->getMinWidth();
+ if (mItemWeight->getMinWidth() > minWidth)
+ minWidth = mItemWeight->getMinWidth();
+
+ minWidth += 8;
+ setWidth(minWidth);
+
+ const int numRowsDesc = mItemDesc->getNumberOfRows();
+ const int numRowsEffect = mItemEffect->getNumberOfRows();
+ const int numRowsWeight = mItemWeight->getNumberOfRows();
+ const int height = getFont()->getHeight();
+
+ if (item.getEffect().empty())
+ {
+ setContentSize(minWidth, (numRowsDesc + numRowsWeight + getPadding()) *
+ height);
+
+ mItemWeight->setPosition(getPadding(), (numRowsDesc + getPadding()) *
+ height);
+ }
+ else
+ {
+ setContentSize(minWidth, (numRowsDesc + numRowsEffect + numRowsWeight +
+ getPadding()) * height);
+
+ mItemWeight->setPosition(getPadding(), (numRowsDesc + numRowsEffect +
+ getPadding()) * height);
+ }
+
+ mItemDesc->setPosition(getPadding(), 2 * height);
+ mItemEffect->setPosition(getPadding(),
+ (numRowsDesc + getPadding()) * height);
+}
+
+gcn::Color ItemPopup::getColor(ItemType type)
+{
+ switch (type)
+ {
+ case ITEM_UNUSABLE:
+ return Theme::getThemeColor(Theme::GENERIC);
+ case ITEM_USABLE:
+ return Theme::getThemeColor(Theme::USABLE);
+ case ITEM_EQUIPMENT_ONE_HAND_WEAPON:
+ return Theme::getThemeColor(Theme::ONEHAND);
+ case ITEM_EQUIPMENT_TWO_HANDS_WEAPON:
+ return Theme::getThemeColor(Theme::TWOHAND);
+ case ITEM_EQUIPMENT_TORSO:
+ return Theme::getThemeColor(Theme::TORSO);
+ case ITEM_EQUIPMENT_ARMS:
+ return Theme::getThemeColor(Theme::ARMS);
+ case ITEM_EQUIPMENT_HEAD:
+ return Theme::getThemeColor(Theme::HEAD);
+ case ITEM_EQUIPMENT_LEGS:
+ return Theme::getThemeColor(Theme::LEGS);
+ case ITEM_EQUIPMENT_SHIELD:
+ return Theme::getThemeColor(Theme::SHIELD);
+ case ITEM_EQUIPMENT_RING:
+ return Theme::getThemeColor(Theme::RING);
+ case ITEM_EQUIPMENT_NECKLACE:
+ return Theme::getThemeColor(Theme::NECKLACE);
+ case ITEM_EQUIPMENT_FEET:
+ return Theme::getThemeColor(Theme::FEET);
+ case ITEM_EQUIPMENT_AMMO:
+ return Theme::getThemeColor(Theme::AMMO);
+ default:
+ return Theme::getThemeColor(Theme::UNKNOWN_ITEM);
+ }
+}
+
+void ItemPopup::mouseMoved(gcn::MouseEvent &event)
+{
+ Popup::mouseMoved(event);
+
+ // When the mouse moved on top of the popup, hide it
+ setVisible(false);
+} \ No newline at end of file
diff --git a/src/gui/itempopup.h b/src/gui/itempopup.h
new file mode 100644
index 000000000..0baf8fb79
--- /dev/null
+++ b/src/gui/itempopup.h
@@ -0,0 +1,71 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ITEMPOPUP_H
+#define ITEMPOPUP_H
+
+#include "gui/widgets/popup.h"
+
+#include "resources/iteminfo.h"
+
+#include <guichan/mouselistener.hpp>
+
+class Icon;
+class TextBox;
+
+/**
+ * A popup that displays information about an item.
+ */
+class ItemPopup : public Popup
+{
+ public:
+ /**
+ * Constructor. Initializes the item popup.
+ */
+ ItemPopup();
+
+ /**
+ * Destructor. Cleans up the item popup on deletion.
+ */
+ ~ItemPopup();
+
+ /**
+ * Sets the info to be displayed given a particular item.
+ */
+ void setItem(const ItemInfo &item, bool showImage = false);
+
+ void setItem(const Item *item, bool showImage = false);
+
+ void mouseMoved(gcn::MouseEvent &mouseEvent);
+
+ private:
+ gcn::Label *mItemName;
+ TextBox *mItemDesc;
+ TextBox *mItemEffect;
+ TextBox *mItemWeight;
+ ItemType mItemType;
+ Icon *mIcon;
+
+ static gcn::Color getColor(ItemType type);
+};
+
+#endif // ITEMPOPUP_H
diff --git a/src/gui/killstats.cpp b/src/gui/killstats.cpp
new file mode 100644
index 000000000..8ce38c719
--- /dev/null
+++ b/src/gui/killstats.cpp
@@ -0,0 +1,424 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "killstats.h"
+
+#include <math.h>
+#include <guichan/widgets/label.hpp>
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/chattab.h"
+#include "gui/chat.h"
+
+#include "actorspritemanager.h"
+#include "event.h"
+#include "localplayer.h"
+#include "playerinfo.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+KillStats::KillStats():
+ Window(_("Kill stats")), mKillCounter(0), mExpCounter(0),
+ mKillTCounter(0), mExpTCounter(0), mKillTimer(0),
+ m1minExpTime(0), m1minExpNum(0), m1minSpeed(0),
+ m5minExpTime(0), m5minExpNum(0), m5minSpeed(0),
+ m15minExpTime(0), m15minExpNum(0), m15minSpeed(0),
+ mJackoSpawnTime(0), mValidateJackoTime(0), mJackoId(0),
+ mIsJackoAlive(false), mIsJackoMustSpawn(true),
+ mIsJackoSpawnTimeUnknown(true)
+{
+ setWindowName("Kill stats");
+ setCloseButton(true);
+ setResizable(true);
+ setDefaultSize(250, 250, 350, 300);
+
+ listen(CHANNEL_ATTRIBUTES);
+ int xp(PlayerInfo::getAttribute(EXP));
+ int xpNextLevel(PlayerInfo::getAttribute(EXP_NEEDED));
+
+ mResetButton = new Button(_("Reset stats"), "reset", this);
+ mTimerButton = new Button(_("Reset timer"), "timer", this);
+ if (!xpNextLevel)
+ xpNextLevel = 1;
+
+ mLine1 = new Label(_("Level: ") + toString(player_node->getLevel())
+ + " at " + toString(static_cast<float>(xp)
+ / static_cast<float>(xpNextLevel) * 100.0f) + "%");
+
+ mLine2 = new Label(_("Exp: ") + toString(xp) + "/" +
+ toString(xpNextLevel) + _(" Left: ") +
+ toString(xpNextLevel-xp));
+ mLine3 = new Label("1% = " + toString(xpNextLevel / 100) +
+ _(" exp, Avg Mob for 1%: ?"));
+ mLine4 = new Label(_("Kills: ?, Total Exp: ?"));
+ mLine5 = new Label(_("Avg Exp: ?, No. of Avg mob to next level: ?"));
+ mLine6 = new Label(_("Kills/Min: ?, Exp/Min: ?"));
+
+ mExpSpeed1Label = new Label(_("Exp speed per 1 min: ?"));
+ mExpTime1Label = new Label(_("Time for next level per 1 min: ?"));
+ mExpSpeed5Label = new Label(_("Exp speed per 5 min: ?"));
+ mExpTime5Label = new Label(_("Time for next level per 5 min: ?"));
+ mExpSpeed15Label = new Label(_("Exp speed per 15 min: ?"));
+ mExpTime15Label = new Label(_("Time for Next level per 15 min: ?"));
+
+ mLastKillExpLabel = new Label(_("Last kill exp: ?"));
+ mTimeBeforeJackoLabel = new Label(_("Time before jacko spawn: ?"));
+
+ place(0, 0, mLine1, 6).setPadding(0);
+ place(0, 1, mLine2, 6).setPadding(0);
+ place(0, 2, mLine3, 6).setPadding(0);
+ place(0, 3, mLine4, 6).setPadding(0);
+ place(0, 4, mLine5, 6).setPadding(0);
+ place(0, 5, mLine6, 6).setPadding(0);
+
+ place(0, 6, mLastKillExpLabel, 6).setPadding(0);
+ place(0, 7, mTimeBeforeJackoLabel, 6).setPadding(0);
+ place(0, 8, mExpSpeed1Label, 6).setPadding(0);
+ place(0, 9, mExpTime1Label, 6).setPadding(0);
+ place(0, 10, mExpSpeed5Label, 6).setPadding(0);
+ place(0, 11, mExpTime5Label, 6).setPadding(0);
+ place(0, 12, mExpSpeed15Label, 6).setPadding(0);
+ place(0, 13, mExpTime15Label, 6).setPadding(0);
+
+ place(5, 12, mTimerButton).setPadding(0);
+ place(5, 13, mResetButton).setPadding(0);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ loadWindowState();
+
+}
+
+KillStats::~KillStats()
+{
+}
+
+void KillStats::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "reset")
+ {
+ mKillCounter = 0;
+ mExpCounter = 0;
+ mLine3->setCaption("1% = " + toString(
+ PlayerInfo::getAttribute(EXP_NEEDED) / 100) +
+ " exp, Avg Mob for 1%: ?");
+ mLine4->setCaption(_("Kills: ?, Total Exp: ?"));
+ mLine5->setCaption(_("Avg Exp: ?, No. of Avg mob to next level: ?"));
+
+ m1minExpTime = 0;
+ m1minExpNum = 0;
+ m1minSpeed = 0;
+ m5minExpTime = 0;
+ m5minExpNum = 0;
+ m5minSpeed = 0;
+ m15minExpTime = 0;
+ m15minExpNum = 0;
+ m15minSpeed = 0;
+ }
+ else if (event.getId() == "timer")
+ {
+ mKillTimer = 0;
+ mKillTCounter = 0;
+ mExpTCounter = 0;
+ mLine6->setCaption(_("Kills/Min: ?, Exp/Min: ?"));
+
+ m1minExpTime = 0;
+ m1minExpNum = 0;
+ m1minSpeed = 0;
+ m5minExpTime = 0;
+ m5minExpNum = 0;
+ m5minSpeed = 0;
+ m15minExpTime = 0;
+ m15minExpNum = 0;
+ m15minSpeed = 0;
+ }
+}
+
+void KillStats::gainXp(int xp)
+{
+ if (xp == PlayerInfo::getAttribute(EXP_NEEDED))
+ xp = 0;
+ else if (!xp)
+ return;
+
+ mKillCounter++;
+ mKillTCounter++;
+
+ mExpCounter = mExpCounter + xp;
+ mExpTCounter = mExpTCounter + xp;
+ if (!mKillCounter)
+ mKillCounter = 1;
+
+ float AvgExp = static_cast<float>(mExpCounter / mKillCounter);
+ int xpNextLevel(PlayerInfo::getAttribute(EXP_NEEDED));
+
+ if (mKillTimer == 0)
+ mKillTimer = cur_time;
+
+ if (!xpNextLevel)
+ xpNextLevel = 1;
+
+ double timeDiff = difftime(cur_time, mKillTimer) / 60;
+
+ if (timeDiff <= 0.001)
+ timeDiff = 1;
+
+ mLine1->setCaption("Level: " + toString(player_node->getLevel()) + " at " +
+ toString(static_cast<float>(PlayerInfo::getAttribute(EXP))
+ / static_cast<float>(xpNextLevel) * 100.0f) + "%");
+
+ mLine2->setCaption("Exp: " + toString(PlayerInfo::getAttribute(EXP)) + "/"
+ + toString(xpNextLevel) + " Left: "
+ + toString(xpNextLevel - PlayerInfo::getAttribute(EXP)));
+ if (AvgExp >= 0.001 && AvgExp <= 0.001)
+ {
+ mLine3->setCaption("1% = " + toString(xpNextLevel / 100)
+ + " exp, Avg Mob for 1%: ?");
+
+ mLine5->setCaption("Avg Exp: " + toString(AvgExp) +
+ ", No. of Avg mob to next level: ?");
+ }
+ else
+ {
+ mLine3->setCaption("1% = " + toString(xpNextLevel / 100)
+ + " exp, Avg Mob for 1%: " +
+ toString((static_cast<float>(xpNextLevel) / 100) / AvgExp));
+
+ mLine5->setCaption("Avg Exp: " + toString(AvgExp) +
+ ", No. of Avg mob to next level: " +
+ toString(static_cast<float>(xpNextLevel -
+ PlayerInfo::getAttribute(EXP)) / AvgExp));
+ }
+ mLine4->setCaption("Kills: " + toString(mKillCounter) +
+ ", Total Exp: " + toString(mExpCounter));
+
+ mLine6->setCaption("Kills/Min: " + toString(mKillTCounter / timeDiff) +
+ ", Exp/Min: " + toString(mExpTCounter / timeDiff));
+
+ mLastKillExpLabel->setCaption("Last Kill Exp: " + toString(xp));
+
+ recalcStats();
+ update();
+}
+
+void KillStats::recalcStats()
+{
+ int curTime = cur_time;
+
+ // Need Update Exp Counter
+ if (curTime - m1minExpTime > 60)
+ {
+ int newExp = PlayerInfo::getAttribute(EXP);
+ if (m1minExpTime != 0)
+ m1minSpeed = newExp - m1minExpNum;
+ else
+ m1minSpeed = 0;
+ m1minExpTime = curTime;
+ m1minExpNum = newExp;
+ }
+
+ if (curTime - m5minExpTime > 60*5)
+ {
+ int newExp = PlayerInfo::getAttribute(EXP);
+ if (m5minExpTime != 0)
+ m5minSpeed = newExp - m5minExpNum;
+ else
+ m5minSpeed = 0;
+ m5minExpTime = curTime;
+ m5minExpNum = newExp;
+ }
+
+ if (curTime - m15minExpTime > 60*15)
+ {
+ int newExp = PlayerInfo::getAttribute(EXP);
+ if (m15minExpTime != 0)
+ m15minSpeed = newExp - m15minExpNum;
+ else
+ m15minSpeed = 0;
+ m15minExpTime = curTime;
+ m15minExpNum = newExp;
+ }
+ validateJacko();
+}
+
+void KillStats::update()
+{
+ mExpSpeed1Label->setCaption(
+ strprintf(_("Exp Speed per 1 min: %d"), m1minSpeed));
+ mExpSpeed1Label->adjustSize();
+
+ if (m1minSpeed != 0)
+ {
+ mExpTime1Label->setCaption(strprintf(_(" Time For Next Level: %f"),
+ static_cast<float>((PlayerInfo::getAttribute(EXP_NEEDED)
+ - PlayerInfo::getAttribute(EXP)) / m1minSpeed)));
+ }
+ else
+ {
+ mExpTime1Label->setCaption(_(" Time For Next Level: ?"));
+ }
+ mExpTime1Label->adjustSize();
+
+ mExpSpeed5Label->setCaption(
+ strprintf(_("Exp Speed per 5 min: %d"), m5minSpeed / 5));
+ mExpSpeed5Label->adjustSize();
+
+ if (m5minSpeed != 0)
+ {
+ mExpTime5Label->setCaption(strprintf(_(" Time For Next Level: %f"),
+ static_cast<float>((PlayerInfo::getAttribute(EXP_NEEDED)
+ - PlayerInfo::getAttribute(EXP)) / m5minSpeed * 5)));
+ }
+ else
+ {
+ mExpTime5Label->setCaption(_(" Time For Next Level: ?"));
+ }
+ mExpTime5Label->adjustSize();
+
+ mExpSpeed15Label->setCaption(
+ strprintf(_("Exp Speed per 15 min: %d"), m15minSpeed / 15));
+ mExpSpeed15Label->adjustSize();
+
+ if (m15minSpeed != 0)
+ {
+ mExpTime15Label->setCaption(strprintf(_(" Time For Next Level: %f"),
+ static_cast<float>((PlayerInfo::getAttribute(EXP_NEEDED)
+ - PlayerInfo::getAttribute(EXP)) / m15minSpeed * 15)));
+ }
+ else
+ {
+ mExpTime15Label->setCaption(_(" Time For Next Level: ?"));
+ }
+
+ validateJacko();
+ updateJackoLabel();
+}
+void KillStats::draw(gcn::Graphics *g)
+{
+ update();
+
+ Window::draw(g);
+}
+
+void KillStats::updateJackoLabel()
+{
+ if (mIsJackoAlive)
+ {
+ mTimeBeforeJackoLabel->setCaption(
+ _("Time before jacko spawn: jacko alive"));
+ }
+ else if (mIsJackoSpawnTimeUnknown && mJackoSpawnTime != 0)
+ {
+ mTimeBeforeJackoLabel->setCaption(_("Time before jacko spawn: ")
+ + toString(mJackoSpawnTime - cur_time) + _("?"));
+ }
+ else if (mIsJackoMustSpawn)
+ {
+ mTimeBeforeJackoLabel->setCaption(
+ _("Time before jacko spawn: jacko spawning"));
+ }
+ else
+ {
+ mTimeBeforeJackoLabel->setCaption(_("Time before jacko spawn: ")
+ + toString(mJackoSpawnTime - cur_time));
+ }
+}
+
+void KillStats::jackoDead(int id)
+{
+ if (id == mJackoId && mIsJackoAlive)
+ {
+ mIsJackoAlive = false;
+ mJackoSpawnTime = cur_time + 60*4;
+ mIsJackoSpawnTimeUnknown = false;
+ updateJackoLabel();
+ }
+}
+
+void KillStats::addLog(std::string str)
+{
+ if (debugChatTab)
+ debugChatTab->chatLog(str, BY_SERVER);
+}
+
+void KillStats::jackoAlive(int id)
+{
+ if (!mIsJackoAlive)
+ {
+ mJackoId = id;
+ mIsJackoAlive = true;
+ mIsJackoMustSpawn = false;
+ mJackoSpawnTime = 0;
+ mIsJackoSpawnTimeUnknown = false;
+ updateJackoLabel();
+ }
+}
+
+void KillStats::validateJacko()
+{
+ if (!actorSpriteManager || !player_node)
+ return;
+
+ Map *currentMap = Game::instance()->getCurrentMap();
+ if (currentMap)
+ {
+ if (currentMap->getProperty("_filename") == "018-1"
+ || currentMap->getProperty("_filename") == "maps/018-1.tmx")
+ {
+ if (player_node->getTileX() >= 167
+ && player_node->getTileX() <= 175
+ && player_node->getTileY() >= 21
+ && player_node->getTileY() <= 46)
+ {
+ Being *dstBeing = actorSpriteManager->findBeingByName(
+ "Jack O", Being::MONSTER);
+ if (mIsJackoAlive && !dstBeing)
+ {
+ mIsJackoAlive = false;
+ mJackoSpawnTime = cur_time + 60*4;
+ mIsJackoSpawnTimeUnknown = true;
+ }
+ }
+ }
+
+ if (!mIsJackoAlive && cur_time > mJackoSpawnTime + 15)
+ mIsJackoMustSpawn = true;
+ }
+}
+
+void KillStats::event(Channels channel _UNUSED_,
+ const Mana::Event &event)
+{
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ int id = event.getInt("id");
+ if (id == EXP || id == EXP_NEEDED)
+ {
+ gainXp(event.getInt("newValue") - event.getInt("oldValue"));
+// update();
+ }
+ }
+}
diff --git a/src/gui/killstats.h b/src/gui/killstats.h
new file mode 100644
index 000000000..df53aa319
--- /dev/null
+++ b/src/gui/killstats.h
@@ -0,0 +1,132 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef KILLSTATS_H
+#define KILLSTATS_H
+
+#include <guichan/actionlistener.hpp>
+
+#include "listener.h"
+
+#include "gui/widgets/window.h"
+
+class Label;
+class Button;
+
+class KillStats : public Window, gcn::ActionListener, public Mana::Listener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ KillStats();
+
+ /**
+ * Destructor.
+ */
+ ~KillStats();
+
+ /**
+ * Stuff.
+ */
+ void action(const gcn::ActionEvent &event);
+ void gainXp(int Xp);
+
+ /**
+ * Recalc stats if needed
+ */
+ void recalcStats();
+
+ /**
+ * Draw this window
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Updates this dialog
+ */
+ void update();
+
+ /**
+ * Updates jacko info
+ */
+ void updateJackoLabel();
+
+ void jackoDead(int id);
+
+ void jackoAlive(int id);
+
+ void addLog(std::string str);
+
+ void event(Channels channel _UNUSED_,
+ const Mana::Event &event);
+
+ private:
+ void validateJacko();
+
+ int mKillCounter; /**< Session Kill counter. */
+ int mExpCounter; /**< Session Exp counter. */
+ int mKillTCounter; /**< Timer Kill counter. */
+ int mExpTCounter; /**< Timer Exp counter. */
+ time_t mKillTimer; /**< Timer for kill stats. */
+ Button *mResetButton;
+ Button *mTimerButton;
+ Label *mLine1;
+ Label *mLine2;
+ Label *mLine3;
+ Label *mLine4;
+ Label *mLine5;
+ Label *mLine6;
+
+ gcn::Label *mExpSpeed1Label;
+ gcn::Label *mExpTime1Label;
+ gcn::Label *mExpSpeed5Label;
+ gcn::Label *mExpTime5Label;
+ gcn::Label *mExpSpeed15Label;
+ gcn::Label *mExpTime15Label;
+
+ gcn::Label *mLastKillExpLabel;
+ gcn::Label *mTimeBeforeJackoLabel;
+
+ int m1minExpTime;
+ int m1minExpNum;
+ int m1minSpeed;
+
+ int m5minExpTime;
+ int m5minExpNum;
+ int m5minSpeed;
+
+ int m15minExpTime;
+ int m15minExpNum;
+ int m15minSpeed;
+
+ int mJackoSpawnTime;
+ int mValidateJackoTime;
+ int mJackoId;
+ bool mIsJackoAlive;
+ bool mIsJackoMustSpawn;
+ bool mIsJackoSpawnTimeUnknown;
+};
+
+extern KillStats *killStats;
+
+#endif
diff --git a/src/gui/login.cpp b/src/gui/login.cpp
new file mode 100644
index 000000000..997895c95
--- /dev/null
+++ b/src/gui/login.cpp
@@ -0,0 +1,231 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/login.h"
+
+#include "client.h"
+#include "configuration.h"
+
+#include "gui/okdialog.h"
+#include "gui/sdlinput.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/passwordfield.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+static const int MAX_SERVER_LIST_SIZE = 15;
+static const int LOGIN_DIALOG_WIDTH = 300;
+static const int LOGIN_DIALOG_HEIGHT = 140;
+static const int FIELD_WIDTH = LOGIN_DIALOG_WIDTH - 70;
+
+std::string LoginDialog::savedPassword = "";
+std::string LoginDialog::savedPasswordKey = "";
+
+
+const char *UPDATE_TYPE_TEXT[3] =
+{
+ _("Normal"),
+ _("Auto Close"),
+ _("Skip"),
+};
+
+class UpdateTypeModel : public gcn::ListModel
+{
+public:
+ virtual ~UpdateTypeModel()
+ { }
+
+ virtual int getNumberOfElements()
+ {
+ return 3;
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return UPDATE_TYPE_TEXT[i];
+ }
+};
+
+LoginDialog::LoginDialog(LoginData *loginData, std::string serverName,
+ std::string *updateHost):
+ Window(_("Login")),
+ mLoginData(loginData),
+ mUpdateHost(updateHost)
+{
+ gcn::Label *serverLabel1 = new Label(_("Server:"));
+ gcn::Label *serverLabel2 = new Label(serverName);
+ serverLabel2->adjustSize();
+ gcn::Label *userLabel = new Label(_("Name:"));
+ gcn::Label *passLabel = new Label(_("Password:"));
+ mCustomUpdateHost = new CheckBox(_("Custom update host"),
+ loginData->updateType & LoginData::Upd_Custom, this, "customhost");
+
+ mUpdateHostText = new TextField(serverConfig.getValue(
+ "customUpdateHost", ""));
+
+ mUpdateHostText->adjustSize();
+
+ mUserField = new TextField(mLoginData->username);
+ mPassField = new PasswordField(mLoginData->password);
+
+ if (mPassField->getText().empty() && LoginDialog::savedPassword != "")
+ mPassField->setText(LoginDialog::savedPassword);
+
+ mKeepCheck = new CheckBox(_("Remember username"), mLoginData->remember);
+ mUpdateTypeLabel = new Label(_("Update:"));
+ mUpdateTypeDropDown = new DropDown(new UpdateTypeModel());
+ mUpdateTypeDropDown->setActionEventId("updatetype");
+ mUpdateTypeDropDown->setSelected((loginData->updateType
+ | LoginData::Upd_Custom) ^ LoginData::Upd_Custom);
+
+ if (!mCustomUpdateHost->isSelected())
+ mUpdateHostText->setVisible(false);
+
+ mRegisterButton = new Button(_("Register"), "register", this);
+ mServerButton = new Button(_("Change Server"), "server", this);
+ mLoginButton = new Button(_("Login"), "login", this);
+
+ mUserField->setActionEventId("login");
+ mPassField->setActionEventId("login");
+
+ mUserField->addKeyListener(this);
+ mPassField->addKeyListener(this);
+ mUserField->addActionListener(this);
+ mPassField->addActionListener(this);
+
+ place(0, 0, serverLabel1);
+ place(1, 0, serverLabel2, 8).setPadding(1);
+
+ place(0, 1, userLabel);
+ place(0, 2, passLabel);
+ place(1, 1, mUserField, 8).setPadding(1);
+ place(1, 2, mPassField, 8).setPadding(1);
+ place(0, 6, mUpdateTypeLabel, 1);
+ place(1, 6, mUpdateTypeDropDown, 8);
+ place(0, 7, mCustomUpdateHost, 9);
+ place(0, 8, mUpdateHostText, 9);
+ place(0, 9, mKeepCheck, 9);
+ place(0, 10, mRegisterButton).setHAlign(LayoutCell::LEFT);
+ place(2, 10, mServerButton);
+ place(3, 10, mLoginButton);
+
+ reflowLayout();
+
+ addKeyListener(this);
+
+ center();
+ setVisible(true);
+
+ if (mUserField->getText().empty())
+ mUserField->requestFocus();
+ else
+ mPassField->requestFocus();
+
+ mLoginButton->setEnabled(canSubmit());
+ mRegisterButton->setEnabled(Net::getLoginHandler()
+ ->isRegistrationEnabled());
+}
+
+LoginDialog::~LoginDialog()
+{
+}
+
+void LoginDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "login" && canSubmit())
+ {
+ mLoginData->username = mUserField->getText();
+ mLoginData->password = mPassField->getText();
+ mLoginData->remember = mKeepCheck->isSelected();
+ int updateType = mUpdateTypeDropDown->getSelected();
+
+ if (mCustomUpdateHost->isSelected())
+ {
+ updateType |= LoginData::Upd_Custom;
+ serverConfig.setValue("customUpdateHost",
+ mUpdateHostText->getText());
+
+ mLoginData->updateHost = mUpdateHostText->getText();
+ *mUpdateHost = mUpdateHostText->getText();
+ }
+ mLoginData->updateType = updateType;
+ serverConfig.setValue("updateType", updateType);
+
+ mLoginData->registerLogin = false;
+
+ mRegisterButton->setEnabled(false);
+ mServerButton->setEnabled(false);
+ mLoginButton->setEnabled(false);
+
+ if (mLoginData->remember)
+ LoginDialog::savedPassword = mPassField->getText();
+
+ Client::setState(STATE_LOGIN_ATTEMPT);
+ }
+ else if (event.getId() == "server")
+ {
+ Client::setState(STATE_SWITCH_SERVER);
+ }
+ else if (event.getId() == "register")
+ {
+ mLoginData->username = mUserField->getText();
+ mLoginData->password = mPassField->getText();
+
+ Client::setState(STATE_REGISTER_PREP);
+ }
+ else if (event.getId() == "customhost")
+ {
+ mUpdateHostText->setVisible(mCustomUpdateHost->isSelected());
+ }
+}
+
+void LoginDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ESCAPE)
+ action(gcn::ActionEvent(NULL, mServerButton->getActionEventId()));
+ else if (key.getValue() == Key::ENTER)
+ action(gcn::ActionEvent(NULL, mLoginButton->getActionEventId()));
+ else
+ mLoginButton->setEnabled(canSubmit());
+}
+
+bool LoginDialog::canSubmit() const
+{
+ return !mUserField->getText().empty() &&
+ !mPassField->getText().empty() &&
+ Client::getState() == STATE_LOGIN;
+}
diff --git a/src/gui/login.h b/src/gui/login.h
new file mode 100644
index 000000000..f20637032
--- /dev/null
+++ b/src/gui/login.h
@@ -0,0 +1,90 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOGIN_H
+#define LOGIN_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/listmodel.hpp>
+
+#include <string>
+#include <vector>
+
+class LoginData;
+
+/**
+ * The login dialog.
+ *
+ * \ingroup Interface
+ */
+class LoginDialog : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ LoginDialog(LoginData *loginData, std::string serverName,
+ std::string *updateHost);
+
+ ~LoginDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Called when a key is pressed in one of the text fields.
+ */
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ static std::string savedPasswordKey;
+ static std::string savedPassword;
+
+ private:
+ /**
+ * Returns whether submit can be enabled. This is true in the login
+ * state, when all necessary fields have some text.
+ */
+ bool canSubmit() const;
+
+ gcn::TextField *mUserField;
+ gcn::TextField *mPassField;
+ gcn::CheckBox *mKeepCheck;
+ gcn::Label *mUpdateTypeLabel;
+ gcn::DropDown *mUpdateTypeDropDown;
+ gcn::Button *mServerButton;
+ gcn::Button *mLoginButton;
+ gcn::Button *mRegisterButton;
+ gcn::CheckBox *mCustomUpdateHost;
+ gcn::TextField *mUpdateHostText;
+
+ LoginData *mLoginData;
+ std::string *mUpdateHost;
+};
+
+#endif
diff --git a/src/gui/minimap.cpp b/src/gui/minimap.cpp
new file mode 100644
index 000000000..6edefcfdd
--- /dev/null
+++ b/src/gui/minimap.cpp
@@ -0,0 +1,292 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/minimap.h"
+
+#include "actorspritemanager.h"
+#include "being.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "localplayer.h"
+#include "log.h"
+#include "map.h"
+
+#include "gui/userpalette.h"
+#include "gui/setup.h"
+#include "gui/viewport.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+bool Minimap::mShow = true;
+
+Minimap::Minimap():
+ Window(_("Map")),
+ mMapImage(0),
+ mWidthProportion(0.5),
+ mHeightProportion(0.5)
+{
+ setWindowName("Minimap");
+ mShow = config.getValueBool(getWindowName() + "Show", true);
+ setDefaultSize(5, 25, 100, 100);
+ // set this to false as the minimap window size is changed
+ //depending on the map size
+ setResizable(false);
+ setupWindow->registerWindowForReset(this);
+
+ setDefaultVisible(true);
+ setSaveVisible(true);
+
+ setStickyButton(true);
+ setSticky(false);
+
+ loadWindowState();
+ setVisible(mShow, isSticky());
+}
+
+Minimap::~Minimap()
+{
+ config.setValue(getWindowName() + "Show", mShow);
+
+ if (mMapImage)
+ mMapImage->decRef();
+}
+
+void Minimap::setMap(Map *map)
+{
+ std::string caption = "";
+ std::string minimapName;
+
+ if (map)
+ caption = map->getName();
+
+ if (caption.empty())
+ caption = _("Map");
+
+ setCaption(caption);
+
+ // Adapt the image
+ if (mMapImage)
+ {
+ mMapImage->decRef();
+ mMapImage = 0;
+ }
+
+ if (map)
+ {
+ std::string tempname =
+ "graphics/minimaps/" + map->getFilename() + ".png";
+ ResourceManager *resman = ResourceManager::getInstance();
+
+ minimapName = map->getProperty("minimap");
+
+ if (minimapName.empty() && resman->exists(tempname))
+ minimapName = tempname;
+
+ mMapImage = resman->getImage(minimapName);
+ }
+
+ if (mMapImage && map)
+ {
+ const int offsetX = 2 * getPadding();
+ const int offsetY = getTitleBarHeight() + getPadding();
+ const int titleWidth = getFont()->getWidth(getCaption()) + 15;
+ const int mapWidth = mMapImage->getWidth() < 100 ?
+ mMapImage->getWidth() + offsetX : 100;
+ const int mapHeight = mMapImage->getHeight() < 100 ?
+ mMapImage->getHeight() + offsetY : 100;
+
+ setMinWidth(mapWidth > titleWidth ? mapWidth : titleWidth);
+ setMinHeight(mapHeight);
+
+ mWidthProportion = static_cast<float>(
+ mMapImage->getWidth()) / static_cast<float>(map->getWidth());
+ mHeightProportion = static_cast<float>(
+ mMapImage->getHeight()) / static_cast<float>(map->getHeight());
+
+ setMaxWidth(mMapImage->getWidth() > titleWidth ?
+ mMapImage->getWidth() + offsetX : titleWidth);
+ setMaxHeight(mMapImage->getHeight() + offsetY);
+
+ setDefaultSize(getX(), getY(), getWidth(), getHeight());
+ resetToDefaultSize();
+
+ if (mShow)
+ setVisible(true);
+ }
+ else
+ {
+ if (!isSticky())
+ setVisible(false);
+ }
+}
+
+void Minimap::toggle()
+{
+ setVisible(!isVisible(), isSticky());
+ mShow = isVisible();
+}
+
+void Minimap::draw(gcn::Graphics *graphics)
+{
+ Window::draw(graphics);
+
+ if (!userPalette || !player_node || !viewport)
+ return;
+
+ Graphics *graph = static_cast<Graphics*>(graphics);
+
+ const gcn::Rectangle a = getChildrenArea();
+
+ graphics->pushClipArea(a);
+
+ int mapOriginX = 0;
+ int mapOriginY = 0;
+
+ if (mMapImage)
+ {
+ if (mMapImage->getWidth() > a.width ||
+ mMapImage->getHeight() > a.height)
+ {
+ const Vector &p = player_node->getPosition();
+ mapOriginX = ((a.width) / 2) - static_cast<int>((p.x
+ + viewport->getCameraRelativeX()) * mWidthProportion) / 32;
+ mapOriginY = ((a.height) / 2) - static_cast<int>((p.y
+ + viewport->getCameraRelativeX()) * mHeightProportion) / 32;
+
+ const int minOriginX = a.width - mMapImage->getWidth();
+ const int minOriginY = a.height - mMapImage->getHeight();
+
+ if (mapOriginX < minOriginX)
+ mapOriginX = minOriginX;
+ if (mapOriginY < minOriginY)
+ mapOriginY = minOriginY;
+ if (mapOriginX > 0)
+ mapOriginX = 0;
+ if (mapOriginY > 0)
+ mapOriginY = 0;
+ }
+
+ graph->drawImage(mMapImage, mapOriginX, mapOriginY);
+ }
+
+ if (!actorSpriteManager)
+ return;
+
+ const ActorSprites &actors = actorSpriteManager->getAll();
+
+ for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end();
+ it != it_end; it++)
+ {
+ if (!(*it) || (*it)->getType() == ActorSprite::FLOOR_ITEM)
+ continue;
+
+ const Being *being = static_cast<Being*>(*it);
+ if (!being)
+ continue;
+
+ int dotSize = 2;
+
+ int type = UserPalette::PC;
+
+ if (being == player_node)
+ {
+ type = UserPalette::SELF;
+ dotSize = 3;
+ }
+ else if (being->isGM())
+ {
+ type = UserPalette::GM;
+ }
+ else if (being->isInParty())
+ {
+ type = UserPalette::PARTY;
+ }
+ else if (being)
+ {
+ switch (being->getType())
+ {
+ case ActorSprite::MONSTER:
+ type = UserPalette::MONSTER;
+ break;
+
+ case ActorSprite::NPC:
+ type = UserPalette::NPC;
+ break;
+
+ default:
+ continue;
+ }
+ }
+
+ if (userPalette)
+ graphics->setColor(userPalette->getColor(type));
+
+ const int offsetHeight = static_cast<int>(static_cast<float>(
+ dotSize - 1) * mHeightProportion);
+ const int offsetWidth = static_cast<int>(static_cast<float>(
+ dotSize - 1) * mWidthProportion);
+ const Vector &pos = being->getPosition();
+
+ graphics->fillRectangle(gcn::Rectangle(
+ static_cast<int>(pos.x * mWidthProportion) / 32
+ + mapOriginX - offsetWidth,
+ static_cast<int>(pos.y * mHeightProportion) / 32
+ + mapOriginY - offsetHeight,
+ dotSize, dotSize));
+ }
+
+ const Vector &pos = player_node->getPosition();
+// logger->log("width:" + toString(graph->getWidth()));
+
+ int x = static_cast<int>((pos.x - (graph->getWidth() / 2)
+ + viewport->getCameraRelativeX())
+ * mWidthProportion) / 32 + mapOriginX;
+ int y = static_cast<int>((pos.y - (graph->getHeight() / 2)
+ + viewport->getCameraRelativeY())
+ * mHeightProportion) / 32 + mapOriginY;
+
+ const int w = graph->getWidth() * mWidthProportion / 32;
+ const int h = graph->getHeight() * mHeightProportion / 32;
+
+ if (w <= a.width)
+ {
+ if (x < 0 && w)
+ x = 0;
+ if (x + w > a.width)
+ x = a.width - w;
+ }
+ if (h <= a.height)
+ {
+ if (y < 0 && h)
+ y = 0;
+ if (y + h > a.height)
+ y = a.height - h;
+ }
+
+ graphics->setColor(userPalette->getColor(UserPalette::PC));
+ graphics->drawRectangle(gcn::Rectangle(x, y, w, h));
+ graphics->popClipArea();
+}
diff --git a/src/gui/minimap.h b/src/gui/minimap.h
new file mode 100644
index 000000000..a376a15c1
--- /dev/null
+++ b/src/gui/minimap.h
@@ -0,0 +1,69 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MINIMAP_H
+#define MINIMAP_H
+
+#include "gui/widgets/window.h"
+
+class Image;
+class Map;
+
+/**
+ * Minimap window. Shows a minimap image and the name of the current map.
+ *
+ * The name of the map is defined by the map property "name". The minimap image
+ * is defined by the map property "minimap". The path to the image should be
+ * given relative to the root of the client data.
+ *
+ * \ingroup Interface
+ */
+class Minimap : public Window
+{
+ public:
+ Minimap();
+ ~Minimap();
+
+ /**
+ * Sets the map image that should be displayed.
+ */
+ void setMap(Map *map);
+
+ /**
+ * Toggles the displaying of the minimap.
+ */
+ void toggle();
+
+ /**
+ * Draws the minimap.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ private:
+ Image *mMapImage;
+ float mWidthProportion;
+ float mHeightProportion;
+ static bool mShow;
+};
+
+extern Minimap *minimap;
+
+#endif
diff --git a/src/gui/ministatus.cpp b/src/gui/ministatus.cpp
new file mode 100644
index 000000000..406ad7ddd
--- /dev/null
+++ b/src/gui/ministatus.cpp
@@ -0,0 +1,228 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/ministatus.h"
+
+#include "animatedsprite.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "playerinfo.h"
+
+#include "gui/chat.h"
+#include "gui/gui.h"
+#include "gui/statuswindow.h"
+#include "gui/statuspopup.h"
+#include "gui/textpopup.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/progressbar.h"
+
+#include "net/net.h"
+#include "net/playerhandler.h"
+#include "net/gamehandler.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+extern volatile int tick_time;
+
+MiniStatusWindow::MiniStatusWindow():
+ Popup("MiniStatus")
+{
+ listen(CHANNEL_ATTRIBUTES);
+
+ mHpBar = new ProgressBar(0, 100, 20, Theme::PROG_HP);
+ StatusWindow::updateHPBar(mHpBar);
+
+ if (Net::getGameHandler()->canUseMagicBar())
+ {
+ mMpBar = new ProgressBar(0, 100, 20,
+ Net::getPlayerHandler()->canUseMagic()
+ ? Theme::PROG_MP : Theme::PROG_NO_MP);
+
+ StatusWindow::updateMPBar(mMpBar);
+ }
+ else
+ {
+ mMpBar = 0;
+ }
+
+ mXpBar = new ProgressBar(0, 100, 20, Theme::PROG_EXP);
+ StatusWindow::updateXPBar(mXpBar);
+
+ mStatusBar = new ProgressBar(100, 150, 20, Theme::PROG_EXP);
+
+ mHpBar->setPosition(0, 3);
+ if (mMpBar)
+ mMpBar->setPosition(mHpBar->getWidth() + 3, 3);
+ mXpBar->setPosition(mMpBar ? mMpBar->getX() + mMpBar->getWidth() + 3 :
+ mHpBar->getX() + mHpBar->getWidth() + 3, 3);
+ mStatusBar->setPosition(mXpBar->getX() + mXpBar->getWidth() + 3, 3);
+
+ add(mHpBar);
+ if (mMpBar)
+ add(mMpBar);
+ add(mXpBar);
+ add(mStatusBar);
+
+ setContentSize(mStatusBar->getX() + mStatusBar->getWidth(),
+ mXpBar->getY() + mXpBar->getHeight());
+
+ setVisible(config.getValueBool(getPopupName() + "Visible", true));
+
+ mStatusPopup = new StatusPopup();
+ mTextPopup = new TextPopup();
+
+ addMouseListener(this);
+ updateStatus();
+}
+
+MiniStatusWindow::~MiniStatusWindow()
+{
+ delete mTextPopup;
+ mTextPopup = 0;
+ delete mStatusPopup;
+ mStatusPopup = 0;
+}
+
+void MiniStatusWindow::setIcon(int index, AnimatedSprite *sprite)
+{
+ if (index >= static_cast<int>(mIcons.size()))
+ mIcons.resize(index + 1, 0);
+
+ delete mIcons[index];
+
+ mIcons[index] = sprite;
+}
+
+void MiniStatusWindow::eraseIcon(int index)
+{
+ mIcons.erase(mIcons.begin() + index);
+}
+
+void MiniStatusWindow::drawIcons(Graphics *graphics)
+{
+ // Draw icons
+ int icon_x = mStatusBar->getX() + mStatusBar->getWidth() + 4;
+ for (unsigned int i = 0; i < mIcons.size(); i++)
+ {
+ if (mIcons[i])
+ {
+ mIcons[i]->draw(graphics, icon_x, 3);
+ icon_x += 2 + mIcons[i]->getWidth();
+ }
+ }
+}
+
+void MiniStatusWindow::event(Channels channel _UNUSED_,
+ const Mana::Event &event)
+{
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ int id = event.getInt("id");
+ if (id == HP || id == MAX_HP)
+ StatusWindow::updateHPBar(mHpBar);
+ else if (id == MP || id == MAX_MP)
+ StatusWindow::updateMPBar(mMpBar);
+ else if (id == EXP || id == EXP_NEEDED)
+ StatusWindow::updateXPBar(mXpBar);
+ }
+ else if (event.getName() == EVENT_UPDATESTAT)
+ {
+ StatusWindow::updateMPBar(mMpBar);
+ }
+}
+
+void MiniStatusWindow::updateStatus()
+{
+ StatusWindow::updateStatusBar(mStatusBar);
+ if (mStatusPopup && mStatusPopup->isVisible())
+ mStatusPopup->update();
+}
+
+void MiniStatusWindow::logic()
+{
+ Popup::logic();
+
+ for (unsigned int i = 0; i < mIcons.size(); i++)
+ {
+ if (mIcons[i])
+ mIcons[i]->update(tick_time * 10);
+ }
+}
+
+void MiniStatusWindow::draw(gcn::Graphics *graphics)
+{
+ drawChildren(graphics);
+}
+
+void MiniStatusWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ Popup::mouseMoved(event);
+
+ const int x = event.getX();
+ const int y = event.getY();
+
+ if (event.getSource() == mStatusBar)
+ {
+ mStatusPopup->view(x + getX(), y + getY());
+ mTextPopup->hide();
+ }
+ else if (event.getSource() == mXpBar)
+ {
+ mTextPopup->show(x + getX(), y + getY(),
+ strprintf("%u/%u", PlayerInfo::getAttribute(EXP),
+ PlayerInfo::getAttribute(EXP_NEEDED)),
+ strprintf("%s: %u", _("Need"),
+ PlayerInfo::getAttribute(EXP_NEEDED)
+ - PlayerInfo::getAttribute(EXP)));
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mHpBar)
+ {
+ mTextPopup->show(x + getX(), y + getY(),
+ strprintf("%u/%u", PlayerInfo::getAttribute(HP),
+ PlayerInfo::getAttribute(MAX_HP)));
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mMpBar)
+ {
+ mTextPopup->show(x + getX(), y + getY(),
+ strprintf("%u/%u", PlayerInfo::getAttribute(MP),
+ PlayerInfo::getAttribute(MAX_MP)));
+ mStatusPopup->hide();
+ }
+ else
+ {
+ mTextPopup->hide();
+ mStatusPopup->hide();
+ }
+}
+
+void MiniStatusWindow::mouseExited(gcn::MouseEvent &event)
+{
+ Popup::mouseExited(event);
+
+ mTextPopup->hide();
+ mStatusPopup->hide();
+}
diff --git a/src/gui/ministatus.h b/src/gui/ministatus.h
new file mode 100644
index 000000000..080beece3
--- /dev/null
+++ b/src/gui/ministatus.h
@@ -0,0 +1,94 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MINISTATUS_H
+#define MINISTATUS_H
+
+#include "listener.h"
+
+#include "gui/widgets/popup.h"
+#include "gui/widgets/window.h"
+
+#include <vector>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class AnimatedSprite;
+class Graphics;
+class ProgressBar;
+class StatusPopup;
+class TextPopup;
+
+/**
+ * The player mini-status dialog.
+ *
+ * \ingroup Interface
+ */
+class MiniStatusWindow : public Popup, public Mana::Listener
+{
+ public:
+ MiniStatusWindow();
+
+ ~MiniStatusWindow();
+
+ /**
+ * Sets one of the icons.
+ */
+ void setIcon(int index, AnimatedSprite *sprite);
+
+ void eraseIcon(int index);
+
+ void drawIcons(Graphics *graphics);
+
+ void event(Channels channel, const Mana::Event &event);
+
+ void updateStatus();
+
+ void logic(); // Updates icons
+
+ void draw(gcn::Graphics *graphics);
+
+ void mouseMoved(gcn::MouseEvent &mouseEvent);
+ void mouseExited(gcn::MouseEvent &event);
+
+ private:
+ bool isInBar(ProgressBar *bar, int x, int y) const;
+
+ /*
+ * Mini Status Bars
+ */
+ ProgressBar *mHpBar;
+ ProgressBar *mMpBar;
+ ProgressBar *mXpBar;
+ ProgressBar *mStatusBar;
+ TextPopup *mTextPopup;
+ StatusPopup *mStatusPopup;
+
+ std::vector<AnimatedSprite *> mIcons;
+};
+
+extern MiniStatusWindow *miniStatusWindow;
+
+#endif
diff --git a/src/gui/npcdialog.cpp b/src/gui/npcdialog.cpp
new file mode 100644
index 000000000..6414da6ed
--- /dev/null
+++ b/src/gui/npcdialog.cpp
@@ -0,0 +1,483 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/npcdialog.h"
+
+#include "configuration.h"
+#include "client.h"
+
+#include "gui/setup.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/inttextfield.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/textbox.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+
+#define CAPTION_WAITING _("Waiting for server")
+#define CAPTION_NEXT _("Next")
+#define CAPTION_CLOSE _("Close")
+#define CAPTION_SUBMIT _("Submit")
+
+NpcDialog::DialogList NpcDialog::instances;
+
+NpcDialog::NpcDialog(int npcId)
+ : Window(_("NPC")),
+ mNpcId(npcId),
+ mLogInteraction(config.getBoolValue("logNpcInGui")),
+ mDefaultInt(0),
+ mInputState(NPC_INPUT_NONE),
+ mActionState(NPC_ACTION_WAIT),
+ mLastNextTime(0)
+{
+ // Basic Window Setup
+ setWindowName("NpcText");
+ setResizable(true);
+ //setupWindow->registerWindowForReset(this);
+ setFocusable(true);
+
+ setMinWidth(200);
+ setMinHeight(150);
+
+ setDefaultSize(260, 200, ImageRect::CENTER);
+
+ // Setup output text box
+ mTextBox = new TextBox;
+ mTextBox->setEditable(false);
+ mTextBox->setOpaque(false);
+
+ mScrollArea = new ScrollArea(mTextBox);
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mScrollArea->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ // Setup listbox
+ mItemList = new ListBox(this);
+ mItemList->setWrappingEnabled(true);
+ setContentSize(260, 175);
+
+ mListScrollArea = new ScrollArea(mItemList);
+ mListScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mItemList->setVisible(true);
+
+ // Setup string input box
+ mTextField = new TextField("");
+ mTextField->setVisible(true);
+
+ // Setup int input box
+ mIntField = new IntTextField;
+ mIntField->setVisible(true);
+
+ mClearButton = new Button(_("Clear"), "clear", this);
+
+ // Setup button
+ mButton = new Button("", "ok", this);
+
+ //Setup more and less buttons (int input)
+ mPlusButton = new Button(_("+"), "inc", this);
+ mMinusButton = new Button(_("-"), "dec", this);
+
+ int width = std::max(mButton->getFont()->getWidth(CAPTION_WAITING),
+ mButton->getFont()->getWidth(CAPTION_NEXT));
+ width = std::max(width, mButton->getFont()->getWidth(CAPTION_CLOSE));
+ width = std::max(width, mButton->getFont()->getWidth(CAPTION_SUBMIT));
+
+ mButton->setWidth(8 + width);
+
+ mResetButton = new Button(_("Reset"), "reset", this);
+
+ // Place widgets
+ buildLayout();
+
+ center();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(true);
+ requestFocus();
+
+ config.addListener("logNpcInGui", this);
+}
+
+NpcDialog::~NpcDialog()
+{
+ config.removeListener("logNpcInGui", this);
+
+ // These might not actually be in the layout, so lets be safe
+ delete mScrollArea;
+ mScrollArea = 0;
+ delete mItemList;
+ mItemList = 0;
+ delete mTextField;
+ mTextField = 0;
+ delete mIntField;
+ mIntField = 0;
+ delete mResetButton;
+ mResetButton = 0;
+ delete mPlusButton;
+ mPlusButton = 0;
+ delete mMinusButton;
+ mMinusButton = 0;
+
+ instances.remove(this);
+
+}
+
+void NpcDialog::setText(const std::string &text)
+{
+ mText = text;
+
+ mTextBox->setTextWrapped(mText, mScrollArea->getWidth() - 15);
+}
+
+void NpcDialog::addText(const std::string &text, bool save)
+{
+ if (save || mLogInteraction)
+ {
+ if (mText.size() > 5000)
+ mText = "";
+
+ mNewText += text + "\n";
+ setText(mText + text + "\n");
+ }
+ mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
+ mActionState = NPC_ACTION_WAIT;
+ buildLayout();
+}
+
+void NpcDialog::showNextButton()
+{
+ mActionState = NPC_ACTION_NEXT;
+ buildLayout();
+}
+
+void NpcDialog::showCloseButton()
+{
+ mActionState = NPC_ACTION_CLOSE;
+ buildLayout();
+}
+
+void NpcDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "ok")
+ {
+ if (mActionState == NPC_ACTION_NEXT)
+ {
+ if (!Client::limitPackets(PACKET_NPC_NEXT))
+ return;
+
+ nextDialog();
+ // TRANSLATORS: Please leave the \n sequences intact.
+ addText(_("\n> Next\n"), false);
+ }
+ else if (mActionState == NPC_ACTION_CLOSE)
+ {
+ closeDialog();
+ }
+ else if (mActionState == NPC_ACTION_INPUT)
+ {
+ std::string printText = ""; // Text that will get printed
+ // in the textbox
+
+ if (mInputState == NPC_INPUT_LIST)
+ {
+ unsigned char choice = 0;
+ int selectedIndex = mItemList->getSelected();
+
+ if (selectedIndex >= static_cast<int>(mItems.size())
+ || selectedIndex < 0
+ || !Client::limitPackets(PACKET_NPC_INPUT))
+ {
+ return;
+ }
+ choice = static_cast<unsigned char>(selectedIndex + 1);
+ printText = mItems[selectedIndex];
+
+ Net::getNpcHandler()->listInput(mNpcId, choice);
+ }
+ else if (mInputState == NPC_INPUT_STRING)
+ {
+ if (!Client::limitPackets(PACKET_NPC_INPUT))
+ return;
+ printText = mTextField->getText();
+ Net::getNpcHandler()->stringInput(mNpcId, printText);
+ }
+ else if (mInputState == NPC_INPUT_INTEGER)
+ {
+ if (!Client::limitPackets(PACKET_NPC_INPUT))
+ return;
+ printText = strprintf("%d", mIntField->getValue());
+ Net::getNpcHandler()->integerInput(
+ mNpcId, mIntField->getValue());
+ }
+ // addText will auto remove the input layout
+ addText(strprintf("\n> \"%s\"\n", printText.c_str()), false);
+
+ mNewText.clear();
+ }
+
+ if (!mLogInteraction)
+ setText("");
+ }
+ else if (event.getId() == "reset")
+ {
+ if (mInputState == NPC_INPUT_STRING)
+ mTextField->setText(mDefaultString);
+ else if (mInputState == NPC_INPUT_INTEGER)
+ mIntField->setValue(mDefaultInt);
+ }
+ else if (event.getId() == "inc")
+ {
+ mIntField->setValue(mIntField->getValue() + 1);
+ }
+ else if (event.getId() == "dec")
+ {
+ mIntField->setValue(mIntField->getValue() - 1);
+ }
+ else if (event.getId() == "clear")
+ {
+ setText(mNewText);
+ }
+}
+
+void NpcDialog::nextDialog()
+{
+ Net::getNpcHandler()->nextDialog(mNpcId);
+}
+
+void NpcDialog::closeDialog()
+{
+ Net::getNpcHandler()->closeDialog(mNpcId);
+}
+
+int NpcDialog::getNumberOfElements()
+{
+ return static_cast<int>(mItems.size());
+}
+
+std::string NpcDialog::getElementAt(int i)
+{
+ return mItems[i];
+}
+
+void NpcDialog::choiceRequest()
+{
+ mItems.clear();
+ mActionState = NPC_ACTION_INPUT;
+ mInputState = NPC_INPUT_LIST;
+ buildLayout();
+}
+
+void NpcDialog::addChoice(const std::string &choice)
+{
+ mItems.push_back(choice);
+}
+
+void NpcDialog::parseListItems(const std::string &itemString)
+{
+ std::istringstream iss(itemString);
+
+ std::string tmp;
+ while (getline(iss, tmp, ':'))
+ mItems.push_back(tmp);
+}
+
+void NpcDialog::textRequest(const std::string &defaultText)
+{
+ mActionState = NPC_ACTION_INPUT;
+ mInputState = NPC_INPUT_STRING;
+ mDefaultString = defaultText;
+ mTextField->setText(defaultText);
+ buildLayout();
+}
+
+bool NpcDialog::isTextInputFocused() const
+{
+ return mTextField->isFocused();
+}
+
+bool NpcDialog::isInputFocused() const
+{
+ return mTextField->isFocused() || mIntField->isFocused();
+}
+
+bool NpcDialog::isAnyInputFocused()
+{
+ DialogList::iterator it = instances.begin();
+ DialogList::iterator it_end = instances.end();
+
+ for (; it != it_end; it++)
+ {
+ if ((*it)->isInputFocused())
+ return true;
+ }
+
+ return false;
+}
+
+void NpcDialog::integerRequest(int defaultValue, int min, int max)
+{
+ mActionState = NPC_ACTION_INPUT;
+ mInputState = NPC_INPUT_INTEGER;
+ mDefaultInt = defaultValue;
+ mIntField->setRange(min, max);
+ mIntField->setValue(defaultValue);
+ buildLayout();
+}
+
+void NpcDialog::move(int amount)
+{
+ if (mActionState != NPC_ACTION_INPUT)
+ return;
+
+ switch (mInputState)
+ {
+ case NPC_INPUT_INTEGER:
+ mIntField->setValue(mIntField->getValue() + amount);
+ break;
+ case NPC_INPUT_LIST:
+ mItemList->setSelected(mItemList->getSelected() - amount);
+ break;
+ case NPC_INPUT_NONE:
+ case NPC_INPUT_STRING:
+ default:
+ break;
+ }
+}
+
+void NpcDialog::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+
+ setText(mText);
+}
+
+void NpcDialog::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (!visible)
+ scheduleDelete();
+}
+
+void NpcDialog::optionChanged(const std::string &name)
+{
+ if (name == "logNpcInGui")
+ mLogInteraction = config.getBoolValue("logNpcInGui");
+}
+
+NpcDialog *NpcDialog::getActive()
+{
+ if (instances.size() == 1)
+ return instances.front();
+
+ DialogList::iterator it = instances.begin();
+ DialogList::iterator it_end = instances.end();
+
+ for (; it != it_end; it++)
+ {
+ if ((*it)->isFocused())
+ return (*it);
+ }
+
+ return 0;
+}
+
+void NpcDialog::closeAll()
+{
+ DialogList::iterator it = instances.begin();
+ DialogList::iterator it_end = instances.end();
+
+ for (; it != it_end; it++)
+ (*it)->close();
+}
+
+void NpcDialog::buildLayout()
+{
+ clearLayout();
+
+ if (mActionState != NPC_ACTION_INPUT)
+ {
+ if (mActionState == NPC_ACTION_WAIT)
+ mButton->setCaption(CAPTION_WAITING);
+ else if (mActionState == NPC_ACTION_NEXT)
+ mButton->setCaption(CAPTION_NEXT);
+ else if (mActionState == NPC_ACTION_CLOSE)
+ mButton->setCaption(CAPTION_CLOSE);
+ place(0, 0, mScrollArea, 5, 3);
+ place(3, 3, mClearButton);
+ place(4, 3, mButton);
+ }
+ else if (mInputState != NPC_INPUT_NONE)
+ {
+ if (!mLogInteraction)
+ setText(mNewText);
+
+ mButton->setCaption(CAPTION_SUBMIT);
+ if (mInputState == NPC_INPUT_LIST)
+ {
+ place(0, 0, mScrollArea, 6, 3);
+ place(0, 3, mListScrollArea, 6, 3);
+ place(2, 6, mClearButton, 2);
+ place(4, 6, mButton, 2);
+
+ mItemList->setSelected(-1);
+ }
+ else if (mInputState == NPC_INPUT_STRING)
+ {
+ place(0, 0, mScrollArea, 6, 3);
+ place(0, 3, mTextField, 6);
+ place(0, 4, mResetButton, 2);
+ place(2, 4, mClearButton, 2);
+ place(4, 4, mButton, 2);
+ }
+ else if (mInputState == NPC_INPUT_INTEGER)
+ {
+ place(0, 0, mScrollArea, 6, 3);
+ place(0, 3, mMinusButton, 1);
+ place(1, 3, mIntField, 4);
+ place(5, 3, mPlusButton, 1);
+ place(0, 4, mResetButton, 2);
+ place(2, 4, mClearButton, 2);
+ place(4, 4, mButton, 2);
+ }
+ }
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ mButton->setEnabled(mActionState != NPC_ACTION_WAIT);
+
+ redraw();
+
+ mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
+} \ No newline at end of file
diff --git a/src/gui/npcdialog.h b/src/gui/npcdialog.h
new file mode 100644
index 000000000..eee4b32e2
--- /dev/null
+++ b/src/gui/npcdialog.h
@@ -0,0 +1,232 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NPCDIALOG_H
+#define NPCDIALOG_H
+
+#include "configlistener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/listmodel.hpp>
+
+#include <list>
+#include <string>
+#include <vector>
+
+class TextBox;
+class ListBox;
+class TextField;
+class IntTextField;
+class Button;
+
+/**
+ * The npc dialog.
+ *
+ * \ingroup Interface
+ */
+class NpcDialog : public Window, public gcn::ActionListener,
+ public gcn::ListModel, public ConfigListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ NpcDialog(int npcId);
+
+ ~NpcDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Sets the text shows in the dialog.
+ *
+ * @param string The new text.
+ */
+ void setText(const std::string &string);
+
+ /**
+ * Adds the text to the text shows in the dialog. Also adds a newline
+ * to the end.
+ *
+ * @param string The text to add.
+ */
+ void addText(const std::string &string, bool save = true);
+
+ /**
+ * When called, the widget will show a "Next" button.
+ */
+ void showNextButton();
+
+ /**
+ * When called, the widget will show a "Close" button and will close
+ * the dialog when clicked.
+ */
+ void showCloseButton();
+
+ /**
+ * Notifies the server that client has performed a next action.
+ */
+ void nextDialog();
+
+ /**
+ * Notifies the server that the client has performed a close action.
+ */
+ void closeDialog();
+
+ /**
+ * Returns the number of items in the choices list.
+ */
+ int getNumberOfElements();
+
+ /**
+ * Returns the name of item number i of the choices list.
+ */
+ std::string getElementAt(int i);
+
+ /**
+ * Makes this dialog request a choice selection from the user.
+ */
+ void choiceRequest();
+
+ /**
+ * Adds a choice to the list box.
+ */
+ void addChoice(const std::string &);
+
+ /**
+ * Fills the options list for an NPC dialog.
+ *
+ * @param itemString A string with the options separated with colons.
+ */
+ void parseListItems(const std::string &itemString);
+
+ /**
+ * Requests a text string from the user.
+ */
+ void textRequest(const std::string &defaultText = "");
+
+ bool isInputFocused() const;
+
+ bool isTextInputFocused() const;
+
+ static bool isAnyInputFocused();
+
+ /**
+ * Requests a interger from the user.
+ */
+ void integerRequest(int defaultValue = 0, int min = 0,
+ int max = 2147483647);
+
+ void move(int amount);
+
+ /**
+ * Called when resizing the window.
+ *
+ * @param event The calling event
+ */
+ void widgetResized(const gcn::Event &event);
+
+ void setVisible(bool visible);
+
+ void optionChanged(const std::string &name);
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive() { return !instances.empty(); }
+
+ /**
+ * Returns the first active instance. Useful for pushing user
+ * interaction.
+ */
+ static NpcDialog *getActive();
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+ private:
+ typedef std::list<NpcDialog*> DialogList;
+ static DialogList instances;
+
+ void buildLayout();
+
+ int mNpcId;
+ bool mLogInteraction;
+
+ int mDefaultInt;
+ std::string mDefaultString;
+
+ // Used for the main input area
+ gcn::ScrollArea *mScrollArea;
+ TextBox *mTextBox;
+ std::string mText;
+ std::string mNewText;
+
+ // Used for choice input
+ ListBox *mItemList;
+ gcn::ScrollArea *mListScrollArea;
+ std::vector<std::string> mItems;
+
+ // Used for string and integer input
+ TextField *mTextField;
+ IntTextField *mIntField;
+ Button *mPlusButton;
+ Button *mMinusButton;
+
+ Button *mClearButton;
+
+ // Used for the button
+ Button *mButton;
+
+ // Will reset the text and integer input to the provided default
+ Button *mResetButton;
+
+ enum NpcInputState
+ {
+ NPC_INPUT_NONE = 0,
+ NPC_INPUT_LIST,
+ NPC_INPUT_STRING,
+ NPC_INPUT_INTEGER
+ };
+
+ enum NpcActionState
+ {
+ NPC_ACTION_WAIT = 0,
+ NPC_ACTION_NEXT,
+ NPC_ACTION_INPUT,
+ NPC_ACTION_CLOSE
+ };
+
+ NpcInputState mInputState;
+ NpcActionState mActionState;
+ int mLastNextTime;
+};
+
+#endif // NPCDIALOG_H
diff --git a/src/gui/npcpostdialog.cpp b/src/gui/npcpostdialog.cpp
new file mode 100644
index 000000000..00bf55028
--- /dev/null
+++ b/src/gui/npcpostdialog.cpp
@@ -0,0 +1,128 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/npcpostdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/textbox.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "utils/gettext.h"
+
+NpcPostDialog::DialogList NpcPostDialog::instances;
+
+NpcPostDialog::NpcPostDialog(int npcId):
+ Window(_("NPC")),
+ mNpcId(npcId)
+{
+ setContentSize(400, 180);
+
+ // create text field for receiver
+ gcn::Label *senderText = new Label(_("To:"));
+ senderText->setPosition(5, 5);
+ mSender = new TextField;
+ mSender->setPosition(senderText->getWidth() + 5, 5);
+ mSender->setWidth(65);
+
+ // create button for sending
+ Button *sendButton = new Button(_("Send"), "send", this);
+ sendButton->setPosition(400 - sendButton->getWidth(),
+ 170 - sendButton->getHeight());
+ Button *cancelButton = new Button(_("Cancel"), "cancel", this);
+ cancelButton->setPosition(sendButton->getX()
+ - (cancelButton->getWidth() + 2), sendButton->getY());
+
+ // create textfield for letter
+ mText = new TextBox;
+ mText->setHeight(400 - (mSender->getHeight() + sendButton->getHeight()));
+ mText->setEditable(true);
+
+ // create scroll box for letter text
+ ScrollArea *scrollArea = new ScrollArea(mText);
+ scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ scrollArea->setDimension(gcn::Rectangle(
+ 5, mSender->getHeight() + 5,
+ 380, 140 - (mSender->getHeight() + sendButton->getHeight())));
+
+ add(senderText);
+ add(mSender);
+ add(scrollArea);
+ add(sendButton);
+ add(cancelButton);
+
+ setLocationRelativeTo(getParent());
+
+ instances.push_back(this);
+ setVisible(true);
+}
+
+NpcPostDialog::~NpcPostDialog()
+{
+ instances.remove(this);
+}
+
+void NpcPostDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "send")
+ {
+ if (mSender->getText().empty() || mText->getText().empty())
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(_("Failed to send as sender or letter "
+ "invalid."));
+ }
+ }
+ else
+ {
+ Net::getNpcHandler()->sendLetter(mNpcId, mSender->getText(),
+ mText->getText());
+ }
+ setVisible(false);
+ }
+ else if (event.getId() == "cancel")
+ {
+ setVisible(false);
+ }
+}
+
+void NpcPostDialog::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (!visible)
+ scheduleDelete();
+}
+
+void NpcPostDialog::closeAll()
+{
+ DialogList::iterator it = instances.begin();
+ DialogList::iterator it_end = instances.end();
+
+ for (; it != it_end; it++)
+ (*it)->close();
+} \ No newline at end of file
diff --git a/src/gui/npcpostdialog.h b/src/gui/npcpostdialog.h
new file mode 100644
index 000000000..6cc64d1e2
--- /dev/null
+++ b/src/gui/npcpostdialog.h
@@ -0,0 +1,70 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_NPCPOSTDIALOG_H
+#define GUI_NPCPOSTDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class TextBox;
+class TextField;
+
+class NpcPostDialog : public Window, public gcn::ActionListener
+{
+public:
+ /**
+ * Constructor
+ */
+ NpcPostDialog(int npcId);
+
+ ~NpcPostDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ void setVisible(bool visible);
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive()
+ { return !instances.empty(); }
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+private:
+ typedef std::list<NpcPostDialog*> DialogList;
+ static DialogList instances;
+
+ int mNpcId;
+
+ TextBox *mText;
+ TextField *mSender;
+};
+
+#endif
diff --git a/src/gui/okdialog.cpp b/src/gui/okdialog.cpp
new file mode 100644
index 000000000..9812a171f
--- /dev/null
+++ b/src/gui/okdialog.cpp
@@ -0,0 +1,81 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/okdialog.h"
+
+#include "gui/gui.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/textbox.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+OkDialog::OkDialog(const std::string &title, const std::string &msg,
+ bool modal, bool showCenter, Window *parent):
+ Window(title, modal, parent)
+{
+ mTextBox = new TextBox;
+ mTextBox->setEditable(false);
+ mTextBox->setOpaque(false);
+ mTextBox->setTextWrapped(msg, 260);
+
+ gcn::Button *okButton = new Button(_("OK"), "ok", this);
+
+ const int numRows = mTextBox->getNumberOfRows();
+ const int fontHeight = getFont()->getHeight();
+ const int height = numRows * fontHeight;
+ int width = getFont()->getWidth(title);
+
+ if (width < mTextBox->getMinWidth())
+ width = mTextBox->getMinWidth();
+ if (width < okButton->getWidth())
+ width = okButton->getWidth();
+
+ setContentSize(mTextBox->getMinWidth() + fontHeight, height +
+ fontHeight + okButton->getHeight());
+ mTextBox->setPosition(getPadding(), getPadding());
+
+ // 8 is the padding that GUIChan adds to button widgets
+ // (top and bottom combined)
+ okButton->setPosition((width - okButton->getWidth()) / 2, height + 8);
+
+ add(mTextBox);
+ add(okButton);
+
+ if (showCenter)
+ center();
+ else
+ centerHorisontally();
+ setVisible(true);
+ okButton->requestFocus();
+}
+
+void OkDialog::action(const gcn::ActionEvent &event)
+{
+ setActionEventId(event.getId());
+ distributeActionEvent();
+
+ // Can we receive anything else anyway?
+ if (event.getId() == "ok")
+ scheduleDelete();
+}
diff --git a/src/gui/okdialog.h b/src/gui/okdialog.h
new file mode 100644
index 000000000..b7db20d79
--- /dev/null
+++ b/src/gui/okdialog.h
@@ -0,0 +1,57 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OK_DIALOG_H
+#define OK_DIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class TextBox;
+
+/**
+ * An 'Ok' button dialog.
+ *
+ * \ingroup GUI
+ */
+class OkDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ OkDialog(const std::string &title, const std::string &msg,
+ bool modal = true, bool showCenter = true,
+ Window *parent = NULL);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ TextBox *mTextBox;
+};
+
+#endif
diff --git a/src/gui/outfitwindow.cpp b/src/gui/outfitwindow.cpp
new file mode 100644
index 000000000..770c77572
--- /dev/null
+++ b/src/gui/outfitwindow.cpp
@@ -0,0 +1,914 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/outfitwindow.h"
+
+#include "configuration.h"
+#include "equipment.h"
+#include "graphics.h"
+#include "inventory.h"
+#include "item.h"
+#include "localplayer.h"
+#include "log.h"
+#include "playerinfo.h"
+
+#include "gui/chat.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <vector>
+
+float OutfitWindow::mAlpha = 1.0;
+
+OutfitWindow::OutfitWindow():
+ Window(_("Outfits")),
+ mBoxWidth(33),
+ mBoxHeight(33),
+ mGridWidth(4),
+ mGridHeight(3),
+ mItemClicked(false),
+ mItemMoved(NULL),
+ mItemSelected(-1),
+ mCurrentOutfit(0),
+ mAwayOutfit(0)
+{
+ setWindowName("Outfits");
+ setResizable(true);
+ setCloseButton(true);
+ setDefaultSize(250, 400, 150, 230);
+ setMinWidth(145);
+ setMinHeight(220);
+
+ addMouseListener(this);
+
+ mPreviousButton = new Button(_("<"), "previous", this);
+ mNextButton = new Button(_(">"), "next", this);
+ mCurrentLabel = new Label(strprintf(_("Outfit: %d"), 1));
+ mCurrentLabel->setAlignment(gcn::Graphics::CENTER);
+ mKeyLabel = new Label(strprintf(_("Key: %s"),
+ keyName(mCurrentOutfit).c_str()));
+ mKeyLabel->setAlignment(gcn::Graphics::CENTER);
+ mUnequipCheck = new CheckBox(_("Unequip first"),
+ serverConfig.getValueBool("OutfitUnequip0", true));
+
+ mAwayOutfitCheck = new CheckBox(_("Away outfit"),
+ serverConfig.getValue("OutfitAwayIndex", OUTFITS_COUNT - 1));
+
+ mUnequipCheck->setActionEventId("unequip");
+ mUnequipCheck->addActionListener(this);
+
+ mAwayOutfitCheck->setActionEventId("away");
+ mAwayOutfitCheck->addActionListener(this);
+
+ place(0, 3, mKeyLabel, 4);
+ place(0, 4, mPreviousButton, 1);
+ place(1, 4, mCurrentLabel, 2);
+ place(3, 4, mNextButton, 1);
+ place(0, 5, mUnequipCheck, 4);
+ place(0, 6, mAwayOutfitCheck, 4);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+ layout.setColWidth(4, Layout::CENTER);
+
+ loadWindowState();
+
+ load();
+}
+
+OutfitWindow::~OutfitWindow()
+{
+ save();
+}
+
+void OutfitWindow::load(bool oldConfig)
+{
+ Configuration *cfg;
+ if (oldConfig)
+ cfg = &config;
+ else
+ cfg = &serverConfig;
+
+ memset(mItems, -1, sizeof(mItems));
+
+ for (int o = 0; o < OUTFITS_COUNT; o++)
+ {
+ std::string outfit = cfg->getValue("Outfit" + toString(o), "-1");
+ std::string buf;
+ std::stringstream ss(outfit);
+
+ std::vector<int> tokens;
+
+ while (ss >> buf)
+ tokens.push_back(atoi(buf.c_str()));
+
+ for (int i = 0; i < static_cast<int>(tokens.size())
+ && i < OUTFIT_ITEM_COUNT; i++)
+ {
+ mItems[o][i] = tokens[i];
+ }
+
+ mItemsUnequip[o] = cfg->getValueBool("OutfitUnequip" + toString(o),
+ true);
+ }
+ mAwayOutfit = cfg->getValue("OutfitAwayIndex", OUTFITS_COUNT - 1);
+ if (mAwayOutfit >= OUTFITS_COUNT)
+ mAwayOutfit = OUTFITS_COUNT - 1;
+
+ if (mAwayOutfitCheck)
+ mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit);
+}
+
+void OutfitWindow::save()
+{
+ std::string outfitStr;
+ for (int o = 0; o < OUTFITS_COUNT; o++)
+ {
+ for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ {
+ outfitStr += mItems[o][i] ? toString(mItems[o][i]) : toString(-1);
+ if (i < OUTFIT_ITEM_COUNT - 1)
+ outfitStr += " ";
+ }
+ serverConfig.setValue("Outfit" + toString(o), outfitStr);
+ serverConfig.setValue("OutfitUnequip" + toString(o), mItemsUnequip[o]);
+ outfitStr = "";
+ }
+ serverConfig.setValue("OutfitAwayIndex", mAwayOutfit);
+}
+
+void OutfitWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "next")
+ {
+ next();
+ }
+ else if (event.getId() == "previous")
+ {
+ previous();
+ }
+ else if (event.getId() == "unequip")
+ {
+ if (mCurrentOutfit < OUTFITS_COUNT)
+ mItemsUnequip[mCurrentOutfit] = mUnequipCheck->isSelected();
+ }
+ else if (event.getId() == "away")
+ {
+ mAwayOutfit = mCurrentOutfit;
+ if (!mAwayOutfitCheck->isSelected())
+ mAwayOutfitCheck->setSelected(true);
+ }
+}
+
+void OutfitWindow::wearOutfit(int outfit, bool unwearEmpty, bool select)
+{
+ bool isEmpty = true;
+
+ Item *item;
+ if (outfit < 0 || outfit > OUTFITS_COUNT)
+ return;
+
+ for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ {
+ item = PlayerInfo::getInventory()->findItem(mItems[outfit][i]);
+ if (item && !item->isEquipped() && item->getQuantity())
+ {
+ if (item->isEquipment())
+ {
+ Net::getInventoryHandler()->equipItem(item);
+ isEmpty = false;
+ }
+ }
+ }
+
+ if ((!isEmpty || unwearEmpty) && outfit < OUTFITS_COUNT
+ && mItemsUnequip[outfit])
+ {
+ unequipNotInOutfit(outfit);
+ }
+ if (select)
+ {
+ mCurrentOutfit = outfit;
+ showCurrentOutfit();
+ }
+}
+
+void OutfitWindow::copyOutfit(int outfit)
+{
+ copyOutfit(outfit, mCurrentOutfit);
+}
+
+void OutfitWindow::copyOutfit(int src, int dst)
+{
+ if (src < 0 || src > OUTFITS_COUNT
+ || dst < 0 || dst > OUTFITS_COUNT)
+ {
+ return;
+ }
+
+ for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ {
+ mItems[dst][i] = mItems[src][i];
+ }
+}
+
+void OutfitWindow::draw(gcn::Graphics *graphics)
+{
+ Window::draw(graphics);
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ {
+ const int itemX = 10 + ((i % mGridWidth) * mBoxWidth);
+ const int itemY = 25 + ((i / mGridWidth) * mBoxHeight);
+
+ graphics->setColor(gcn::Color(0, 0, 0, 64));
+ graphics->drawRectangle(gcn::Rectangle(itemX, itemY, 32, 32));
+ graphics->setColor(gcn::Color(255, 255, 255, 32));
+ graphics->fillRectangle(gcn::Rectangle(itemX, itemY, 32, 32));
+
+ if (mItems[mCurrentOutfit][i] < 0)
+ continue;
+
+ bool foundItem = false;
+ Inventory *inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ Item *item = inv->findItem(mItems[mCurrentOutfit][i]);
+ if (item)
+ {
+ // Draw item icon.
+ Image* image = item->getImage();
+ if (image)
+ {
+ g->drawImage(image, itemX, itemY);
+ foundItem = true;
+ }
+ }
+ }
+ if (!foundItem)
+ {
+ Image *image = Item::getImage(mItems[mCurrentOutfit][i]);
+ if (image)
+ g->drawImage(image, itemX, itemY);
+ }
+ }
+ if (mItemMoved)
+ {
+ // Draw the item image being dragged by the cursor.
+ Image* image = mItemMoved->getImage();
+ if (image)
+ {
+ const int tPosX = mCursorPosX - (image->getWidth() / 2);
+ const int tPosY = mCursorPosY - (image->getHeight() / 2);
+
+ g->drawImage(image, tPosX, tPosY);
+ }
+ }
+}
+
+
+void OutfitWindow::mouseDragged(gcn::MouseEvent &event)
+{
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (!mItemMoved && mItemClicked)
+ {
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+ if (index == -1)
+ {
+ Window::mouseDragged(event);
+ return;
+ }
+ const int itemId = mItems[mCurrentOutfit][index];
+ if (itemId < 0)
+ {
+ Window::mouseDragged(event);
+ return;
+ }
+ mMoved = false;
+ event.consume();
+ Inventory *inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ Item *item = inv->findItem(itemId);
+ if (item)
+ mItemMoved = item;
+ else
+ mItemMoved = 0;
+ mItems[mCurrentOutfit][index] = -1;
+ }
+ }
+ if (mItemMoved)
+ {
+ mCursorPosX = event.getX();
+ mCursorPosY = event.getY();
+ }
+ }
+ Window::mouseDragged(event);
+}
+
+void OutfitWindow::mousePressed(gcn::MouseEvent &event)
+{
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+ if (index == -1)
+ {
+ if (event.getButton() == gcn::MouseEvent::RIGHT && viewport)
+ viewport->showOutfitsPopup();
+ Window::mousePressed(event);
+ return;
+ }
+ mMoved = false;
+ event.consume();
+
+ // Stores the selected item if there is one.
+ if (isItemSelected())
+ {
+ mItems[mCurrentOutfit][index] = mItemSelected;
+ mItemSelected = -1;
+ }
+ else if (mItems[mCurrentOutfit][index])
+ {
+ mItemClicked = true;
+ }
+ Window::mousePressed(event);
+}
+
+void OutfitWindow::mouseReleased(gcn::MouseEvent &event)
+{
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (isItemSelected())
+ mItemSelected = -1;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+ if (index == -1)
+ {
+ mItemMoved = NULL;
+ Window::mouseReleased(event);
+ return;
+ }
+ mMoved = false;
+ event.consume();
+ if (mItemMoved)
+ {
+ mItems[mCurrentOutfit][index] = mItemMoved->getId();
+ mItemMoved = NULL;
+ }
+ if (mItemClicked)
+ mItemClicked = false;
+ }
+ Window::mouseReleased(event);
+}
+
+int OutfitWindow::getIndexFromGrid(int pointX, int pointY) const
+{
+ const gcn::Rectangle tRect = gcn::Rectangle(
+ 10, 25, mGridWidth * mBoxWidth, mGridHeight * mBoxHeight);
+ if (!tRect.isPointInRect(pointX, pointY))
+ return -1;
+ const int index = (((pointY - 25) / mBoxHeight) * mGridWidth) +
+ (pointX - 10) / mBoxWidth;
+ if (index >= OUTFIT_ITEM_COUNT || index < 0)
+ return -1;
+ return index;
+}
+
+void OutfitWindow::unequipNotInOutfit(int outfit)
+{
+ // here we think that outfit is correct index
+
+ Inventory *inventory = PlayerInfo::getInventory();
+ if (!inventory)
+ return;
+
+ for (unsigned i = 0; i < inventory->getSize(); i++)
+ {
+ if (inventory->getItem(i) && inventory->getItem(i)->isEquipped())
+ {
+ bool found = false;
+ for (unsigned f = 0; f < OUTFIT_ITEM_COUNT; f++)
+ {
+ if (inventory->getItem(i)->getId() == mItems[outfit][f])
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ Net::getInventoryHandler()->unequipItem(inventory->getItem(i));
+ }
+ }
+}
+
+int OutfitWindow::keyToNumber(SDLKey key)
+{
+ int outfitNum = -1;
+ switch (key)
+ {
+ case SDLK_1:
+ case SDLK_2:
+ case SDLK_3:
+ case SDLK_4:
+ case SDLK_5:
+ case SDLK_6:
+ case SDLK_7:
+ case SDLK_8:
+ case SDLK_9:
+ outfitNum = key - SDLK_1;
+ break;
+
+ case SDLK_0:
+ outfitNum = 9;
+ break;
+
+ case SDLK_MINUS:
+ outfitNum = 10;
+ break;
+
+ case SDLK_EQUALS:
+ outfitNum = 11;
+ break;
+
+ case SDLK_BACKSPACE:
+ outfitNum = 12;
+ break;
+
+ case SDLK_INSERT:
+ outfitNum = 13;
+ break;
+
+ case SDLK_HOME:
+ outfitNum = 14;
+ break;
+
+ case SDLK_q:
+ outfitNum = 15;
+ break;
+
+ case SDLK_w:
+ outfitNum = 16;
+ break;
+
+ case SDLK_e:
+ outfitNum = 17;
+ break;
+
+ case SDLK_r:
+ outfitNum = 18;
+ break;
+
+ case SDLK_t:
+ outfitNum = 19;
+ break;
+
+ case SDLK_y:
+ outfitNum = 20;
+ break;
+
+ case SDLK_u:
+ outfitNum = 21;
+ break;
+
+ case SDLK_i:
+ outfitNum = 22;
+ break;
+
+ case SDLK_o:
+ outfitNum = 23;
+ break;
+
+ case SDLK_p:
+ outfitNum = 24;
+ break;
+
+ case SDLK_LEFTBRACKET:
+ outfitNum = 25;
+ break;
+
+ case SDLK_RIGHTBRACKET:
+ outfitNum = 26;
+ break;
+
+ case SDLK_BACKSLASH:
+ outfitNum = 27;
+ break;
+
+ case SDLK_a:
+ outfitNum = 28;
+ break;
+
+ case SDLK_s:
+ outfitNum = 29;
+ break;
+
+ case SDLK_d:
+ outfitNum = 30;
+ break;
+
+ case SDLK_f:
+ outfitNum = 31;
+ break;
+
+ case SDLK_g:
+ outfitNum = 32;
+ break;
+
+ case SDLK_h:
+ outfitNum = 33;
+ break;
+
+ case SDLK_j:
+ outfitNum = 34;
+ break;
+
+ case SDLK_k:
+ outfitNum = 35;
+ break;
+
+ case SDLK_l:
+ outfitNum = 36;
+ break;
+
+ case SDLK_SEMICOLON:
+ outfitNum = 37;
+ break;
+
+ case SDLK_QUOTE:
+ outfitNum = 38;
+ break;
+
+ case SDLK_z:
+ outfitNum = 39;
+ break;
+
+
+ case SDLK_x:
+ outfitNum = 40;
+ break;
+
+ case SDLK_c:
+ outfitNum = 41;
+ break;
+
+ case SDLK_v:
+ outfitNum = 42;
+ break;
+
+ case SDLK_b:
+ outfitNum = 43;
+ break;
+
+ case SDLK_n:
+ outfitNum = 44;
+ break;
+
+ case SDLK_m:
+ outfitNum = 45;
+ break;
+
+ case SDLK_COMMA:
+ outfitNum = 46;
+ break;
+
+ case SDLK_PERIOD:
+ outfitNum = 47;
+ break;
+
+ case SDLK_SLASH:
+ outfitNum = 48;
+ break;
+
+ default:
+ break;
+ }
+
+ return outfitNum;
+}
+
+SDLKey OutfitWindow::numberToKey(int number)
+{
+ SDLKey key = SDLK_UNKNOWN;
+ switch (number)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ key = static_cast<SDLKey>(
+ static_cast<unsigned int>(SDLK_1) + number);
+ break;
+
+ case 9:
+ key = SDLK_0;
+ break;
+
+ case 10:
+ key = SDLK_MINUS;
+ break;
+
+ case 11:
+ key = SDLK_EQUALS;
+ break;
+
+ case 12:
+ key = SDLK_BACKSPACE;
+ break;
+
+ case 13:
+ key = SDLK_INSERT;
+ break;
+
+ case 14:
+ key = SDLK_HOME;
+ break;
+
+ case 15:
+ key = SDLK_q;
+ break;
+
+ case 16:
+ key = SDLK_w;
+ break;
+
+ case 17:
+ key = SDLK_e;
+ break;
+
+ case 18:
+ key = SDLK_r;
+ break;
+
+ case 19:
+ key = SDLK_t;
+ break;
+
+ case 20:
+ key = SDLK_y;
+ break;
+
+ case 21:
+ key = SDLK_u;
+ break;
+
+ case 22:
+ key = SDLK_i;
+ break;
+
+ case 23:
+ key = SDLK_o;
+ break;
+
+ case 24:
+ key = SDLK_p;
+ break;
+
+ case 25:
+ key = SDLK_LEFTBRACKET;
+ break;
+
+ case 26:
+ key = SDLK_RIGHTBRACKET;
+ break;
+
+ case 27:
+ key = SDLK_BACKSLASH;
+ break;
+
+ case 28:
+ key = SDLK_a;
+ break;
+
+ case 29:
+ key = SDLK_s;
+ break;
+
+ case 30:
+ key = SDLK_d;
+ break;
+
+ case 31:
+ key = SDLK_f;
+ break;
+
+ case 32:
+ key = SDLK_g;
+ break;
+
+ case 33:
+ key = SDLK_h;
+ break;
+
+ case 34:
+ key = SDLK_j;
+ break;
+
+ case 35:
+ key = SDLK_k;
+ break;
+
+ case 36:
+ key = SDLK_l;
+ break;
+
+ case 37:
+ key = SDLK_SEMICOLON;
+ break;
+
+ case 38:
+ key = SDLK_QUOTE;
+ break;
+
+ case 39:
+ key = SDLK_z;
+ break;
+
+
+ case 40:
+ key = SDLK_x;
+ break;
+
+ case 41:
+ key = SDLK_c;
+ break;
+
+ case 42:
+ key = SDLK_v;
+ break;
+
+ case 43:
+ key = SDLK_b;
+ break;
+
+ case 44:
+ key = SDLK_n;
+ break;
+
+ case 45:
+ key = SDLK_m;
+ break;
+
+ case 46:
+ key = SDLK_COMMA;
+ break;
+
+ case 47:
+ key = SDLK_PERIOD;
+ break;
+
+ case 48:
+ key = SDLK_SLASH;
+ break;
+
+ default:
+ break;
+ }
+
+ return key;
+}
+
+std::string OutfitWindow::keyName(int number)
+{
+ return SDL_GetKeyName(numberToKey(number));
+}
+
+void OutfitWindow::next()
+{
+ if (mCurrentOutfit < (OUTFITS_COUNT - 1))
+ mCurrentOutfit++;
+ else
+ mCurrentOutfit = 0;
+ showCurrentOutfit();
+}
+
+void OutfitWindow::previous()
+{
+ if (mCurrentOutfit > 0)
+ mCurrentOutfit--;
+ else
+ mCurrentOutfit = OUTFITS_COUNT - 1;
+ showCurrentOutfit();
+}
+
+void OutfitWindow::showCurrentOutfit()
+{
+ mCurrentLabel->setCaption(strprintf(_("Outfit: %d"), mCurrentOutfit + 1));
+ mUnequipCheck->setSelected(mItemsUnequip[mCurrentOutfit]);
+ mKeyLabel->setCaption(strprintf(_("Key: %s"),
+ keyName(mCurrentOutfit).c_str()));
+ mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit);
+}
+
+void OutfitWindow::wearNextOutfit(bool all)
+{
+ bool fromStart = false;
+ next();
+ if (!all && mCurrentOutfit < OUTFITS_COUNT)
+ {
+ while (!mItemsUnequip[mCurrentOutfit])
+ {
+ next();
+ if (mCurrentOutfit == 0)
+ {
+ if (!fromStart)
+ fromStart = true;
+ else
+ return;
+ }
+ }
+ }
+ wearOutfit(mCurrentOutfit);
+}
+
+void OutfitWindow::wearPreviousOutfit(bool all)
+{
+ bool fromStart = false;
+ previous();
+ if (!all && mCurrentOutfit < OUTFITS_COUNT)
+ {
+ while (!mItemsUnequip[mCurrentOutfit])
+ {
+ previous();
+ if (mCurrentOutfit == 0)
+ {
+ if (!fromStart)
+ fromStart = true;
+ else
+ return;
+ }
+ }
+ }
+ wearOutfit(mCurrentOutfit);
+}
+
+void OutfitWindow::copyFromEquiped()
+{
+ copyFromEquiped(mCurrentOutfit);
+}
+
+void OutfitWindow::copyFromEquiped(int dst)
+{
+ Inventory *inventory = PlayerInfo::getInventory();
+ if (!inventory)
+ return;
+
+ int outfitCell = 0;
+
+ for (unsigned i = 0; i < inventory->getSize(); i++)
+ {
+ if (inventory->getItem(i) && inventory->getItem(i)->isEquipped())
+ {
+ mItems[dst][outfitCell++] = inventory->getItem(i)->getId();
+ if (outfitCell > 8)
+ break;
+ }
+ }
+}
+
+void OutfitWindow::wearAwayOutfit()
+{
+ copyFromEquiped(OUTFITS_COUNT);
+ wearOutfit(mAwayOutfit, false);
+}
+
+void OutfitWindow::unwearAwayOutfit()
+{
+ wearOutfit(OUTFITS_COUNT);
+}
diff --git a/src/gui/outfitwindow.h b/src/gui/outfitwindow.h
new file mode 100644
index 000000000..684d1c6f1
--- /dev/null
+++ b/src/gui/outfitwindow.h
@@ -0,0 +1,135 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OUTFITWINDOW_H
+#define OUTFITWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/mouselistener.hpp>
+
+#define OUTFITS_COUNT 100
+#define OUTFIT_ITEM_COUNT 12
+
+class Button;
+class CheckBox;
+class Item;
+class Label;
+
+class OutfitWindow : public Window, gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ OutfitWindow();
+
+ /**
+ * Destructor.
+ */
+ ~OutfitWindow();
+
+ void action(const gcn::ActionEvent &event);
+
+ void draw(gcn::Graphics *graphics);
+
+ void mousePressed(gcn::MouseEvent &event);
+
+ void mouseDragged(gcn::MouseEvent &event);
+
+ void mouseReleased(gcn::MouseEvent &event);
+
+ void load(bool oldConfig = false);
+
+ void setItemSelected(int itemId)
+ { mItemSelected = itemId; }
+
+ bool isItemSelected()
+ { return mItemSelected > 0; }
+
+ void wearOutfit(int outfit, bool unwearEmpty = true,
+ bool select = false);
+
+ void copyOutfit(int outfit);
+
+ void copyOutfit(int src, int dst);
+
+ void copyFromEquiped();
+
+ void copyFromEquiped(int dst);
+
+ void unequipNotInOutfit(int outfit);
+
+ int keyToNumber(SDLKey key);
+
+ SDLKey numberToKey(int number);
+
+ void next();
+
+ void previous();
+
+ void wearNextOutfit(bool all = false);
+
+ void wearPreviousOutfit(bool all = false);
+
+ void wearAwayOutfit();
+
+ void unwearAwayOutfit();
+
+ void showCurrentOutfit();
+
+ std::string keyName(int number);
+
+ private:
+ Button *mPreviousButton;
+ Button *mNextButton;
+ Label *mCurrentLabel;
+ CheckBox *mUnequipCheck;
+ CheckBox *mAwayOutfitCheck;
+ Label *mKeyLabel;
+
+ int getIndexFromGrid(int pointX, int pointY) const;
+
+ int mBoxWidth;
+ int mBoxHeight;
+ int mCursorPosX, mCursorPosY;
+ int mGridWidth, mGridHeight;
+ bool mItemClicked;
+ Item *mItemMoved;
+
+ void save();
+
+ int mItems[OUTFITS_COUNT + 1][OUTFIT_ITEM_COUNT];
+ bool mItemsUnequip[OUTFITS_COUNT];
+ int mItemSelected;
+
+ int mCurrentOutfit;
+ int mAwayOutfit;
+
+ Image *mBackgroundImg;
+
+ static float mAlpha;
+};
+
+extern OutfitWindow *outfitWindow;
+
+#endif
diff --git a/src/gui/palette.cpp b/src/gui/palette.cpp
new file mode 100644
index 000000000..72c490912
--- /dev/null
+++ b/src/gui/palette.cpp
@@ -0,0 +1,274 @@
+/*
+ * Configurable text colors
+ * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net>
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "palette.h"
+
+#include "configuration.h"
+#include "client.h"
+
+#include "gui/gui.h"
+
+#include "utils/gettext.h"
+#include "utils/mathutils.h"
+#include "utils/stringutils.h"
+
+#include <math.h>
+
+const gcn::Color Palette::BLACK = gcn::Color(0, 0, 0);
+Palette::Palettes Palette::mInstances;
+
+const gcn::Color Palette::RAINBOW_COLORS[7] = {
+ gcn::Color(255, 0, 0),
+ gcn::Color(255, 153, 0),
+ gcn::Color(255, 255, 0),
+ gcn::Color(0, 153, 0),
+ gcn::Color(0, 204, 204),
+ gcn::Color(51, 0, 153),
+ gcn::Color(153, 0, 153)
+};
+/** Number of Elemets of RAINBOW_COLORS */
+const int Palette::RAINBOW_COLOR_COUNT = 7;
+
+Palette::Palette(int size) :
+ mRainbowTime(tick_time),
+ mColors(Colors(size))
+{
+ mInstances.insert(this);
+}
+
+Palette::~Palette()
+{
+ mInstances.erase(this);
+}
+
+const gcn::Color& Palette::getColor(char c, bool &valid)
+{
+ for (Colors::const_iterator col = mColors.begin(),
+ colEnd = mColors.end(); col != colEnd; ++col)
+ {
+ if (col->ch == c)
+ {
+ valid = true;
+ return col->color;
+ }
+ }
+ valid = false;
+ return BLACK;
+}
+
+void Palette::advanceGradients()
+{
+ Palettes::iterator it = mInstances.begin();
+ Palettes::iterator it_end = mInstances.end();
+
+ for (; it != it_end; it++)
+ (*it)->advanceGradient();
+}
+
+void Palette::advanceGradient()
+{
+ if (get_elapsed_time(mRainbowTime) > 5)
+ {
+ int pos, colIndex, colVal, delay, numOfColors;
+ // For slower systems, advance can be greater than one (advance > 1
+ // skips advance-1 steps). Should make gradient look the same
+ // independent of the framerate.
+ int advance = get_elapsed_time(mRainbowTime) / 5;
+ double startColVal, destColVal;
+
+ for (size_t i = 0; i < mGradVector.size(); i++)
+ {
+ if (!mGradVector[i])
+ continue;
+
+ delay = mGradVector[i]->delay;
+
+ if (mGradVector[i]->grad == PULSE)
+ delay = delay / 20;
+
+ numOfColors = (mGradVector[i]->grad == SPECTRUM ? 6 :
+ mGradVector[i]->grad == PULSE ? 127 :
+ RAINBOW_COLOR_COUNT);
+
+ mGradVector[i]->gradientIndex =
+ (mGradVector[i]->gradientIndex + advance) %
+ (delay * numOfColors);
+
+ pos = mGradVector[i]->gradientIndex % delay;
+ if (delay)
+ colIndex = mGradVector[i]->gradientIndex / delay;
+ else
+ colIndex = mGradVector[i]->gradientIndex;
+
+ if (mGradVector[i]->grad == PULSE)
+ {
+ colVal = static_cast<int>(255.0 *
+ sin(M_PI * colIndex / numOfColors));
+
+ const gcn::Color &col = mGradVector[i]->testColor;
+
+ mGradVector[i]->color.r =
+ ((colVal * col.r) / 255) % (col.r + 1);
+ mGradVector[i]->color.g =
+ ((colVal * col.g) / 255) % (col.g + 1);
+ mGradVector[i]->color.b =
+ ((colVal * col.b) / 255) % (col.b + 1);
+ }
+ if (mGradVector[i]->grad == SPECTRUM)
+ {
+ if (colIndex % 2)
+ { // falling curve
+ if (delay)
+ {
+ colVal = static_cast<int>(255.0 *
+ (cos(M_PI * pos / delay) + 1) / 2);
+ }
+ else
+ {
+ colVal = static_cast<int>(255.0 *
+ (cos(M_PI * pos) + 1) / 2);
+ }
+ }
+ else
+ { // ascending curve
+ if (delay)
+ {
+ colVal = static_cast<int>(255.0 * (cos(M_PI *
+ (delay - pos) / delay) + 1) / 2);
+ }
+ else
+ {
+ colVal = static_cast<int>(255.0 * (cos(M_PI *
+ (delay - pos)) + 1) / 2);
+ }
+ }
+
+ mGradVector[i]->color.r =
+ (colIndex == 0 || colIndex == 5) ? 255 :
+ (colIndex == 1 || colIndex == 4) ? colVal : 0;
+ mGradVector[i]->color.g =
+ (colIndex == 1 || colIndex == 2) ? 255 :
+ (colIndex == 0 || colIndex == 3) ? colVal : 0;
+ mGradVector[i]->color.b =
+ (colIndex == 3 || colIndex == 4) ? 255 :
+ (colIndex == 2 || colIndex == 5) ? colVal : 0;
+ }
+ else if (mGradVector[i]->grad == RAINBOW)
+ {
+ const gcn::Color &startCol = RAINBOW_COLORS[colIndex];
+ const gcn::Color &destCol =
+ RAINBOW_COLORS[(colIndex + 1) % numOfColors];
+
+ if (delay)
+ startColVal = (cos(M_PI * pos / delay) + 1) / 2;
+ else
+ startColVal = 0;
+
+ destColVal = 1 - startColVal;
+
+ mGradVector[i]->color.r = static_cast<int>(startColVal
+ * startCol.r + destColVal * destCol.r);
+
+ mGradVector[i]->color.g = static_cast<int>(startColVal
+ * startCol.g + destColVal * destCol.g);
+
+ mGradVector[i]->color.b = static_cast<int>(startColVal
+ * startCol.b + destColVal * destCol.b);
+ }
+ }
+
+ if (advance)
+ mRainbowTime = tick_time;
+ }
+}
+
+/*
+gcn::Color Palette::produceHPColor(int hp, int maxHp, int alpha)
+{
+ float r1 = 255;
+ float g1 = 255;
+ float b1 = 255;
+ float r2 = 255;
+ float g2 = 255;
+ float b2 = 255;
+
+ float weight = 1.0f;
+
+ int thresholdLevel = maxHp / 4;
+ int thresholdProgress = hp % thresholdLevel;
+
+ if (thresholdLevel)
+ weight = 1 - ((float)thresholdProgress) / ((float)thresholdLevel);
+ else
+ weight = 0;
+
+ if (hp < (thresholdLevel))
+ {
+ gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_ONE_HALF);
+ gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_ONE_QUARTER);
+ r1 = color1.r; r2 = color2.r;
+ g1 = color1.g; g2 = color2.g;
+ b1 = color1.b; b2 = color2.b;
+ }
+ else if (hp < (thresholdLevel*2))
+ {
+ gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_THREE_QUARTERS);
+ gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_ONE_HALF);
+ r1 = color1.r; r2 = color2.r;
+ g1 = color1.g; g2 = color2.g;
+ b1 = color1.b; b2 = color2.b;
+ }
+ else if (hp < thresholdLevel*3)
+ {
+ gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_FULL);
+ gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_THREE_QUARTERS);
+ r1 = color1.r; r2 = color2.r;
+ g1 = color1.g; g2 = color2.g;
+ b1 = color1.b; b2 = color2.b;
+ }
+ else
+ {
+ gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_FULL);
+ gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_FULL);
+ r1 = color1.r; r2 = color2.r;
+ g1 = color1.g; g2 = color2.g;
+ b1 = color1.b; b2 = color2.b;
+ }
+
+ // Safety checks
+ if (weight > 1.0f) weight = 1.0f;
+ if (weight < 0.0f) weight = 0.0f;
+
+ // Do the color blend
+ r1 = (int) weightedAverage(r1, r2,weight);
+ g1 = (int) weightedAverage(g1, g2, weight);
+ b1 = (int) weightedAverage(b1, b2, weight);
+
+ // More safety checks
+ if (r1 > 255) r1 = 255;
+ if (g1 > 255) g1 = 255;
+ if (b1 > 255) b1 = 255;
+
+ return gcn::Color(r1, g1, b1, alpha);
+}
+*/
+
diff --git a/src/gui/palette.h b/src/gui/palette.h
new file mode 100644
index 000000000..0c8af1d7f
--- /dev/null
+++ b/src/gui/palette.h
@@ -0,0 +1,191 @@
+/*
+ * Configurable text colors
+ * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net>
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PALETTE_H
+#define PALETTE_H
+
+#include "log.h"
+#include "utils/stringutils.h"
+
+#include <guichan/color.hpp>
+
+#include <cstdlib>
+#include <string>
+#include <set>
+#include <vector>
+
+// Default Gradient Delay
+#define GRADIENT_DELAY 40
+
+/**
+ * Class controlling the game's color palette.
+ */
+class Palette
+{
+ public:
+ /** Black Color Constant */
+ static const gcn::Color BLACK;
+
+ /** Colors can be static or can alter over time. */
+ enum GradientType
+ {
+ STATIC = 0,
+ PULSE,
+ SPECTRUM,
+ RAINBOW
+ };
+
+ /**
+ * Returns the color associated with a character, if it exists. Returns
+ * Palette::BLACK if the character is not found.
+ *
+ * @param c character requested
+ * @param valid indicate whether character is known
+ *
+ * @return the requested color or Palette::BLACK
+ */
+ const gcn::Color &getColor(char c, bool &valid);
+
+ /**
+ * Gets the color associated with the type. Sets the alpha channel
+ * before returning.
+ *
+ * @param type the color type requested
+ * @param alpha alpha channel to use
+ *
+ * @return the requested color
+ */
+ inline const gcn::Color &getColor(int type, int alpha = 255)
+ {
+ gcn::Color* col = &mColors[type].color;
+ col->a = alpha;
+ return *col;
+ }
+
+ inline const gcn::Color &getColorWithAlpha(int type)
+ {
+ gcn::Color* col = &mColors[type].color;
+ col->a = mColors[type].delay;
+ return *col;
+ }
+
+ /**
+ * Gets the GradientType associated with the specified type.
+ *
+ * @param type the color type of the color
+ *
+ * @return the gradient type of the color with the given index
+ */
+ inline GradientType getGradientType(int type) const
+ {
+ return mColors[type].grad;
+ }
+
+ /**
+ * Get the character used by the specified color.
+ *
+ * @param type the color type of the color
+ *
+ * @return the color char of the color with the given index
+ */
+ inline char getColorChar(int type) const
+ {
+ return mColors[type].ch;
+ }
+
+ /**
+ * Gets the gradient delay for the specified type.
+ *
+ * @param type the color type of the color
+ *
+ * @return the gradient delay of the color with the given index
+ */
+ inline int getGradientDelay(int type) const
+ { return mColors[type].delay; }
+
+ /**
+ * Updates all colors, that are non-static.
+ */
+ static void advanceGradients();
+
+ gcn::Color static produceHPColor(int hp, int maxHp, int alpha = 255);
+
+ protected:
+ /** Colors used for the rainbow gradient */
+ static const gcn::Color RAINBOW_COLORS[];
+ static const int RAINBOW_COLOR_COUNT;
+
+ /** Time tick, that gradient-type colors were updated the last time. */
+ int mRainbowTime;
+
+ typedef std::set<Palette*> Palettes;
+ static Palettes mInstances;
+
+ /**
+ * Constructor
+ */
+ Palette(int size);
+
+ /**
+ * Destructor
+ */
+ ~Palette();
+
+ void advanceGradient();
+
+ struct ColorElem
+ {
+ int type;
+ gcn::Color color;
+ gcn::Color testColor;
+ gcn::Color committedColor;
+ std::string text;
+ char ch;
+ GradientType grad;
+ GradientType committedGrad;
+ int gradientIndex;
+ int delay;
+ int committedDelay;
+
+ void set(int type, gcn::Color& color, GradientType grad, int delay)
+ {
+ ColorElem::type = type;
+ ColorElem::color = color;
+ ColorElem::testColor = color;
+ ColorElem::grad = grad;
+ ColorElem::delay = delay;
+ ColorElem::gradientIndex = rand();
+ }
+
+ inline int getRGB() const
+ {
+ return (committedColor.r << 16) | (committedColor.g << 8) |
+ committedColor.b;
+ }
+ };
+ typedef std::vector<ColorElem> Colors;
+ /** Vector containing the colors. */
+ Colors mColors;
+ std::vector<ColorElem*> mGradVector;
+};
+
+#endif
diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp
new file mode 100644
index 000000000..662b184b9
--- /dev/null
+++ b/src/gui/popupmenu.cpp
@@ -0,0 +1,1286 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/popupmenu.h"
+
+#include "actorspritemanager.h"
+#include "being.h"
+#include "dropshortcut.h"
+#include "guild.h"
+#include "flooritem.h"
+#include "graphics.h"
+#include "item.h"
+#include "itemshortcut.h"
+#include "localplayer.h"
+#include "log.h"
+#include "map.h"
+#include "party.h"
+#include "playerinfo.h"
+#include "playerrelations.h"
+#include "spellmanager.h"
+
+#include "gui/buy.h"
+#include "gui/chat.h"
+#include "gui/inventorywindow.h"
+#include "gui/itemamount.h"
+#include "gui/outfitwindow.h"
+#include "gui/sell.h"
+#include "gui/socialwindow.h"
+#include "gui/textcommandeditor.h"
+#include "gui/textdialog.h"
+#include "gui/trade.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/whispertab.h"
+
+#include "net/adminhandler.h"
+#include "net/beinghandler.h"
+#include "net/buysellhandler.h"
+#include "net/guildhandler.h"
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+#include "net/partyhandler.h"
+#include "gui/shortcutwindow.h"
+#include "net/tradehandler.h"
+
+#include "resources/itemdb.h"
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <cassert>
+
+std::string tradePartnerName("");
+
+PopupMenu::PopupMenu():
+ Popup("PopupMenu"),
+ mBeingId(0),
+ mFloorItem(0),
+ mItem(0),
+ mItemId(0),
+ mMapItem(0),
+ mTab(0),
+ mSpell(0),
+ mDialog(0),
+ mNick("")
+{
+ mBrowserBox = new BrowserBox;
+ mBrowserBox->setPosition(4, 4);
+ mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
+ mBrowserBox->setOpaque(false);
+ mBrowserBox->setLinkHandler(this);
+ add(mBrowserBox);
+}
+
+void PopupMenu::showPopup(int x, int y, Being *being)
+{
+ if (!being || !player_node)
+ return;
+
+ mBeingId = being->getId();
+ mNick = being->getName();
+ mBrowserBox->clearRows();
+
+ const std::string &name = mNick;
+
+ mBrowserBox->addRow(name);
+
+ switch (being->getType())
+ {
+ case ActorSprite::PLAYER:
+ {
+ // Players can be traded with.
+ mBrowserBox->addRow(_("@@trade|Trade@@"));
+ // TRANSLATORS: Attacking a player.
+ mBrowserBox->addRow(_("@@attack|Attack@@"));
+ // TRANSLATORS: Whispering a player.
+ mBrowserBox->addRow(_("@@whisper|Whisper@@"));
+
+ mBrowserBox->addRow("##3---");
+
+ mBrowserBox->addRow(_("@@heal|Heal@@"));
+ mBrowserBox->addRow("##3---");
+
+ switch (player_relations.getRelation(name))
+ {
+ case PlayerRelation::NEUTRAL:
+ mBrowserBox->addRow(_("@@friend|Befriend@@"));
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::FRIEND:
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::IGNORED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::ERASED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
+ break;
+
+ default:
+ break;
+ }
+
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@follow|Follow@@"));
+ mBrowserBox->addRow(_("@@imitation|Imitation@@"));
+
+ if (player_node->isInParty())
+ {
+ if (player_node->getParty())
+ {
+ if (player_node->getParty()->getName()
+ != being->getPartyName())
+ {
+ mBrowserBox->addRow(
+ _("@@party|Invite to party@@"));
+ }
+ else
+ {
+ mBrowserBox->addRow(
+ _("@@kick party|Kick from party@@"));
+ }
+ mBrowserBox->addRow("##3---");
+ }
+ }
+
+ Guild *guild1 = being->getGuild();
+ Guild *guild2 = player_node->getGuild();
+ if (guild2)
+ {
+ if (guild1)
+ {
+ if (guild1->getId() == guild2->getId())
+ {
+ mBrowserBox->addRow(
+ _("@@guild-kick|Kick from guild@@"));
+ mBrowserBox->addRow(
+ _("@@guild-pos|Change pos in guild >@@"));
+ }
+ }
+ else
+ {
+ mBrowserBox->addRow(_("@@guild|Invite to guild@@"));
+ }
+ }
+
+ if (player_node->isGM())
+ {
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@admin-kick|Kick player@@"));
+ }
+ mBrowserBox->addRow(_("@@nuke|Nuke@@"));
+ mBrowserBox->addRow(_("@@move|Move@@"));
+ mBrowserBox->addRow(_("@@undress|Undress@@"));
+
+ if (player_relations.getDefault() & PlayerRelation::TRADE)
+ {
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@buy|Buy@@"));
+ mBrowserBox->addRow(_("@@sell|Sell@@"));
+ }
+ }
+ break;
+
+ case ActorSprite::NPC:
+ // NPCs can be talked to (single option, candidate for removal
+ // unless more options would be added)
+ mBrowserBox->addRow(_("@@talk|Talk@@"));
+
+ mBrowserBox->addRow(_("@@buy|Buy@@"));
+ mBrowserBox->addRow(_("@@sell|Sell@@"));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@move|Move@@"));
+ break;
+
+ case ActorSprite::MONSTER:
+ {
+ // Monsters can be attacked
+ mBrowserBox->addRow(_("@@attack|Attack@@"));
+
+ if (player_node->isGM())
+ mBrowserBox->addRow(_("@@admin-kick|Kick@@"));
+ }
+ break;
+
+ default:
+ /* Other beings aren't interesting... */
+ return;
+ }
+ mBrowserBox->addRow(_("@@name|Add name to chat@@"));
+
+ //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@cancel|Cancel@@"));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showPopup(int x, int y, std::list<Being*> &beings)
+{
+ mBrowserBox->clearRows();
+ mBrowserBox->addRow("Players");
+ std::list<Being*>::iterator it, it_end;
+ for (it = beings.begin(), it_end = beings.end(); it != it_end; it++)
+ {
+ Being *being = *it;
+ if (!being->getName().empty())
+ {
+ mBrowserBox->addRow(strprintf(_("@@player_%u|%s >@@"),
+ being->getId(), being->getName().c_str()));
+ }
+ }
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@cancel|Cancel@@"));
+ showPopup(x, y);
+}
+
+void PopupMenu::showPlayerPopup(int x, int y, std::string nick)
+{
+ if (nick.empty() || !player_node)
+ return;
+
+ mNick = nick;
+ mBeingId = 0;
+ mBrowserBox->clearRows();
+
+ const std::string &name = mNick;
+
+ mBrowserBox->addRow(name);
+
+ mBrowserBox->addRow(_("@@whisper|Whisper@@"));
+ mBrowserBox->addRow("##3---");
+
+ switch (player_relations.getRelation(name))
+ {
+ case PlayerRelation::NEUTRAL:
+ mBrowserBox->addRow(_("@@friend|Befriend@@"));
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::FRIEND:
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::IGNORED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::ERASED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
+ break;
+
+ default:
+ break;
+ }
+
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@follow|Follow@@"));
+ mBrowserBox->addRow(_("@@imitation|Imitation@@"));
+
+ Guild *guild2 = player_node->getGuild();
+ if (guild2)
+ {
+ if (guild2->getMember(mNick))
+ {
+ mBrowserBox->addRow(_("@@guild-kick|Kick from guild@@"));
+ mBrowserBox->addRow(_("@@guild-pos|Change pos in guild >@@"));
+ }
+ else
+ {
+ mBrowserBox->addRow(_("@@guild|Invite to guild@@"));
+ }
+ }
+
+ mBrowserBox->addRow("##3---");
+ if (player_relations.getDefault() & PlayerRelation::TRADE)
+ {
+ mBrowserBox->addRow(_("@@buy|Buy@@"));
+ mBrowserBox->addRow(_("@@sell|Sell@@"));
+ }
+
+ mBrowserBox->addRow(_("@@name|Add name to chat@@"));
+
+ //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@cancel|Cancel@@"));
+
+ showPopup(x, y);
+
+}
+
+void PopupMenu::showPopup(int x, int y, FloorItem *floorItem)
+{
+ if (!floorItem)
+ return;
+
+ mFloorItem = floorItem;
+ ItemInfo info = floorItem->getInfo();
+ mBrowserBox->clearRows();
+
+ // Floor item can be picked up (single option, candidate for removal)
+ std::string name = info.getName();
+ mBrowserBox->addRow(name);
+ mBrowserBox->addRow(_("@@pickup|Pick up@@"));
+ mBrowserBox->addRow(_("@@chat|Add to chat@@"));
+
+ //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@cancel|Cancel@@"));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showPopup(int x, int y, MapItem *mapItem)
+{
+ if (!mapItem)
+ return;
+
+ mMapItem = mapItem;
+
+ mBrowserBox->clearRows();
+
+ mBrowserBox->addRow(_("Map Item"));
+ mBrowserBox->addRow(_("@@rename map|Rename@@"));
+ mBrowserBox->addRow(_("@@remove map|Remove@@"));
+
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@cancel|Cancel@@"));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showOutfitsPopup(int x, int y)
+{
+ mBrowserBox->clearRows();
+
+ mBrowserBox->addRow(_("Outfits"));
+ mBrowserBox->addRow(_("@@load old outfits|Load old outfits@@"));
+
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@cancel|Cancel@@"));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showSpellPopup(int x, int y, TextCommand *cmd)
+{
+ if (!cmd)
+ return;
+
+ mBrowserBox->clearRows();
+
+ mSpell = cmd;
+ mBrowserBox->addRow(_("Spells"));
+ mBrowserBox->addRow(_("@@load old spells|Load old spells@@"));
+ mBrowserBox->addRow(_("@@edit spell|Edit spell@@"));
+
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@cancel|Cancel@@"));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showChatPopup(int x, int y, ChatTab *tab)
+{
+ if (!tab || !actorSpriteManager || !player_node)
+ return;
+
+ mTab = tab;
+
+ mBrowserBox->clearRows();
+
+ if (tab->getType() == ChatTab::TAB_WHISPER)
+ mBrowserBox->addRow(_("@@chat close|Close@@"));
+
+ mBrowserBox->addRow(strprintf("@@chat clear|%s@@", _("Clear")));
+ mBrowserBox->addRow("##3---");
+
+ if (tab->getAllowHighlight())
+ {
+ mBrowserBox->addRow(strprintf("@@disable highlight|%s@@",
+ _("Disable highlight")));
+ mBrowserBox->addRow("##3---");
+ }
+ else
+ {
+ mBrowserBox->addRow(strprintf("@@enable highlight|%s@@",
+ _("Enable highlight")));
+ mBrowserBox->addRow("##3---");
+ }
+
+ if (tab->getType() == ChatTab::TAB_PARTY)
+ {
+ mBrowserBox->addRow(_("@@leave party|Leave@@"));
+ mBrowserBox->addRow("##3---");
+ }
+
+ if (tab->getType() == ChatTab::TAB_WHISPER)
+ {
+ WhisperTab *wTab = static_cast<WhisperTab*>(tab);
+ std::string name = wTab->getNick();
+
+ Being* being = actorSpriteManager->findBeingByName(
+ name, Being::PLAYER);
+
+ if (being)
+ {
+ mBeingId = being->getId();
+ mNick = being->getName();
+
+ mBrowserBox->addRow(_("@@trade|Trade@@"));
+ mBrowserBox->addRow(_("@@attack|Attack@@"));
+
+ mBrowserBox->addRow("##3---");
+
+ mBrowserBox->addRow(_("@@heal|Heal@@"));
+ mBrowserBox->addRow("##3---");
+
+ switch (player_relations.getRelation(name))
+ {
+ case PlayerRelation::NEUTRAL:
+ mBrowserBox->addRow(_("@@friend|Befriend@@"));
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::FRIEND:
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::IGNORED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@erase|Erase@@"));
+ break;
+
+ case PlayerRelation::ERASED:
+ mBrowserBox->addRow(_("@@unignore|Unignore@@"));
+ mBrowserBox->addRow(_("@@disregard|Disregard@@"));
+ mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
+ break;
+
+ default:
+ break;
+ }
+
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@follow|Follow@@"));
+ mBrowserBox->addRow(_("@@imitation|Imitation@@"));
+ mBrowserBox->addRow(_("@@move|Move@@"));
+ mBrowserBox->addRow(_("@@undress|Undress@@"));
+
+ if (player_relations.getDefault() & PlayerRelation::TRADE)
+ {
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(_("@@buy|Buy@@"));
+ mBrowserBox->addRow(_("@@sell|Sell@@"));
+ }
+
+ mBrowserBox->addRow("##3---");
+
+ if (player_node->isInParty())
+ {
+ if (player_node->getParty())
+ {
+ if (!player_node->getParty()->isMember(wTab->getNick()))
+ {
+ mBrowserBox->addRow(_("@@party|Invite to party@@"));
+ }
+ else
+ {
+ mBrowserBox->addRow(
+ _("@@kick party|Kick from party@@"));
+ }
+ mBrowserBox->addRow("##3---");
+ }
+ }
+ Guild *guild1 = being->getGuild();
+ Guild *guild2 = player_node->getGuild();
+ if (guild2)
+ {
+ if (guild1)
+ {
+ if (guild1->getId() == guild2->getId())
+ {
+ mBrowserBox->addRow(
+ _("@@guild-kick|Kick from guild@@"));
+ mBrowserBox->addRow(
+ _("@@guild-pos|Change pos in guild >@@"));
+ }
+ }
+ else
+ {
+ mBrowserBox->addRow(_("@@guild|Invite to guild@@"));
+ }
+ }
+ }
+ }
+ mBrowserBox->addRow(strprintf(_("@@cancel|Cancel@@")));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showChangePos(int x, int y)
+{
+ mBrowserBox->clearRows();
+ mBrowserBox->addRow(_("Change guild position"));
+
+ if (!player_node)
+ return;
+
+ const Guild *guild = player_node->getGuild();
+ if (guild)
+ {
+ PositionsMap map = guild->getPositions();
+ PositionsMap::iterator itr = map.begin();
+ PositionsMap::iterator itr_end = map.end();
+ for (; itr != itr_end; ++itr)
+ {
+ mBrowserBox->addRow(strprintf(_("@@guild-pos-%d|%s@@"),
+ itr->first, itr->second.c_str()));
+ }
+ mBrowserBox->addRow(strprintf(_("@@cancel|Cancel@@")));
+
+ showPopup(x, y);
+ }
+ else
+ {
+ mBeingId = 0;
+ mFloorItem = 0;
+ mItem = 0;
+ mMapItem = 0;
+ mNick = "";
+ setVisible(false);
+ }
+}
+
+void PopupMenu::handleLink(const std::string &link,
+ gcn::MouseEvent *event _UNUSED_)
+{
+ if (!actorSpriteManager)
+ return;
+
+ Being *being = actorSpriteManager->findBeing(mBeingId);
+
+ // Talk To action
+ if (link == "talk" && being && being->canTalk())
+ {
+ being->talkTo();
+ }
+ // Trade action
+ else if (link == "trade" && being &&
+ being->getType() == ActorSprite::PLAYER)
+ {
+ Net::getTradeHandler()->request(being);
+ tradePartnerName = being->getName();
+ if (tradeWindow)
+ tradeWindow->clear();
+ }
+ else if (link == "buy" && being && mBeingId != 0)
+ {
+ if (being->getType() == Being::NPC)
+ Net::getNpcHandler()->buy(mBeingId);
+ else if (being->getType() == Being::PLAYER)
+ Net::getBuySellHandler()->requestSellList(being->getName());
+ }
+ else if (link == "buy" && !mNick.empty())
+ {
+ Net::getBuySellHandler()->requestSellList(mNick);
+ }
+ else if (link == "sell" && being && mBeingId != 0)
+ {
+ if (being->getType() == Being::NPC)
+ Net::getNpcHandler()->sell(mBeingId);
+ else if (being->getType() == Being::PLAYER)
+ Net::getBuySellHandler()->requestBuyList(being->getName());
+ }
+ else if (link == "sell" && !mNick.empty())
+ {
+ Net::getBuySellHandler()->requestBuyList(mNick);
+ }
+ // Attack action
+ else if (link == "attack" && being)
+ {
+ if (player_node)
+ player_node->attack(being, true);
+ }
+ else if (link == "heal" && being && being->getType() != Being::MONSTER)
+ {
+ actorSpriteManager->heal(player_node, being);
+ }
+ else if (link == "unignore" && being &&
+ being->getType() == ActorSprite::PLAYER)
+ {
+ player_relations.setRelation(being->getName(),
+ PlayerRelation::NEUTRAL);
+ }
+ else if (link == "unignore" && !mNick.empty())
+ {
+ player_relations.setRelation(mNick,
+ PlayerRelation::NEUTRAL);
+ }
+ else if (link == "ignore" && being &&
+ being->getType() == ActorSprite::PLAYER)
+ {
+ player_relations.setRelation(being->getName(),
+ PlayerRelation::IGNORED);
+ }
+ else if (link == "ignore" && !mNick.empty())
+ {
+ player_relations.setRelation(mNick, PlayerRelation::IGNORED);
+ }
+ else if (link == "erase" && being &&
+ being->getType() == ActorSprite::PLAYER)
+ {
+ player_relations.setRelation(being->getName(), PlayerRelation::ERASED);
+ }
+ else if (link == "erase" && !mNick.empty())
+ {
+ player_relations.setRelation(mNick, PlayerRelation::ERASED);
+ }
+ else if (link == "disregard" && being &&
+ being->getType() == ActorSprite::PLAYER)
+ {
+ player_relations.setRelation(being->getName(),
+ PlayerRelation::DISREGARDED);
+ }
+ else if (link == "disregard" && !mNick.empty())
+ {
+ player_relations.setRelation(mNick, PlayerRelation::DISREGARDED);
+ }
+ else if (link == "friend" && being &&
+ being->getType() == ActorSprite::PLAYER)
+ {
+ player_relations.setRelation(being->getName(), PlayerRelation::FRIEND);
+ }
+ else if (link == "friend" && !mNick.empty())
+ {
+ player_relations.setRelation(mNick, PlayerRelation::FRIEND);
+ }
+ // Guild action
+ else if (link == "guild" && !mNick.empty())
+ {
+ if (player_node)
+ {
+ const Guild *guild = player_node->getGuild();
+ if (guild)
+ Net::getGuildHandler()->invite(guild->getId(), mNick);
+ }
+ }
+ else if (link == "nuke" && being)
+ {
+ actorSpriteManager->addBlock(being->getId());
+ actorSpriteManager->destroy(being);
+ }
+ // Follow Player action
+ else if (link == "follow" && !mNick.empty())
+ {
+ if (player_node)
+ player_node->setFollow(mNick);
+ }
+ else if (link == "imitation" && !mNick.empty())
+ {
+ if (player_node)
+ player_node->setImitate(mNick);
+ }
+ // Pick Up Floor Item action
+ else if ((link == "pickup") && mFloorItem)
+ {
+ if (player_node)
+ player_node->pickUp(mFloorItem);
+ }
+ // Look To action
+ else if (link == "look")
+ {
+ }
+ else if (link == "use" && mItem)
+ {
+ if (mItem->isEquipment())
+ {
+ if (mItem->isEquipped())
+ Net::getInventoryHandler()->unequipItem(mItem);
+ else
+ Net::getInventoryHandler()->equipItem(mItem);
+ }
+ else
+ {
+ Net::getInventoryHandler()->useItem(mItem);
+ }
+ }
+ else if (link == "use" && mItemId)
+ {
+ if (mItemId < SPELL_MIN_ID)
+ {
+ Inventory *inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ Item *item = inv->findItem(mItemId);
+ if (item)
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ Net::getInventoryHandler()->unequipItem(item);
+ else
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ else
+ {
+ Net::getInventoryHandler()->useItem(item);
+ }
+ }
+ }
+ }
+ else if (spellManager)
+ {
+ spellManager->useItem(mItemId);
+ }
+ }
+ else if (link == "chat")
+ {
+ if (chatWindow)
+ {
+ if (mItem)
+ chatWindow->addItemText(mItem->getInfo().getName());
+ else if (mFloorItem)
+ chatWindow->addItemText(mFloorItem->getInfo().getName());
+ }
+ }
+ else if (link == "whisper" && !mNick.empty() && chatWindow)
+ {
+ if (chatWindow)
+ {
+ if (config.getBoolValue("whispertab"))
+ chatWindow->localChatInput("/q " + mNick);
+ else
+ chatWindow->addInputText("/w \"" + mNick + "\" ");
+ }
+ }
+ else if (link == "move" && being)
+ {
+ if (player_node)
+ player_node->navigateTo(being->getTileX(), being->getTileY());
+ }
+ else if (link == "split" && mItem)
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit,
+ inventoryWindow, mItem);
+ }
+ else if (link == "drop" && mItem)
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop,
+ inventoryWindow, mItem);
+ }
+ else if (link == "store" && mItem)
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd,
+ inventoryWindow, mItem);
+ }
+ else if (link == "store 10" && mItem)
+ {
+ int cnt = 10;
+ if (cnt > mItem->getQuantity())
+ cnt = mItem->getQuantity();
+ Net::getInventoryHandler()->moveItem(Inventory::INVENTORY,
+ mItem->getInvIndex(), cnt,
+ Inventory::STORAGE);
+ }
+ else if (link == "store half" && mItem)
+ {
+ Net::getInventoryHandler()->moveItem(Inventory::INVENTORY,
+ mItem->getInvIndex(), mItem->getQuantity() / 2,
+ Inventory::STORAGE);
+ }
+ else if (link == "store all" && mItem)
+ {
+ Net::getInventoryHandler()->moveItem(Inventory::INVENTORY,
+ mItem->getInvIndex(), mItem->getQuantity(),
+ Inventory::STORAGE);
+ }
+ else if (link == "retrieve" && mItem)
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, mWindow,
+ mItem);
+ }
+ else if (link == "retrieve 10" && mItem)
+ {
+ int cnt = 10;
+ if (cnt > mItem->getQuantity())
+ cnt = mItem->getQuantity();
+ Net::getInventoryHandler()->moveItem(Inventory::STORAGE,
+ mItem->getInvIndex(), cnt,
+ Inventory::INVENTORY);
+ }
+ else if (link == "retrieve half" && mItem)
+ {
+ Net::getInventoryHandler()->moveItem(Inventory::STORAGE,
+ mItem->getInvIndex(), mItem->getQuantity() / 2,
+ Inventory::INVENTORY);
+ }
+ else if (link == "retrieve all" && mItem)
+ {
+ Net::getInventoryHandler()->moveItem(Inventory::STORAGE,
+ mItem->getInvIndex(), mItem->getQuantity(),
+ Inventory::INVENTORY);
+ }
+ else if (link == "party" && being &&
+ being->getType() == ActorSprite::PLAYER)
+ {
+ Net::getPartyHandler()->invite(being);
+ }
+ else if (link == "kick party" && being
+ && being->getType() == Being::PLAYER)
+ {
+ Net::getPartyHandler()->kick(being);
+ }
+ else if (link == "name" && !mNick.empty())
+ {
+ const std::string &name = mNick;
+ if (chatWindow)
+ chatWindow->addInputText(name);
+ }
+ else if (link == "admin-kick" && being &&
+ (being->getType() == ActorSprite::PLAYER ||
+ being->getType() == ActorSprite::MONSTER))
+ {
+ Net::getAdminHandler()->kick(being->getId());
+ }
+ else if (link == "chat close" && mTab)
+ {
+ mTab->handleCommand("close", "");
+ }
+ else if (link == "leave party" && mTab)
+ {
+ Net::getPartyHandler()->leave();
+ }
+ else if (link == "chat clear" && mTab)
+ {
+ if (chatWindow)
+ chatWindow->clearTab();
+ }
+ else if (link == "remove map" && mMapItem)
+ {
+ if (viewport)
+ {
+ Map *map = viewport->getCurrentMap();
+ if (map)
+ {
+ SpecialLayer *specialLayer = map->getSpecialLayer();
+ if (specialLayer)
+ {
+ const int x = mMapItem->getX();
+ const int y = mMapItem->getY();
+ specialLayer->setTile(x, y, MapItem::EMPTY);
+ if (socialWindow)
+ socialWindow->removePortal(x, y);
+ }
+ }
+ }
+ }
+ else if (link == "rename map" && mMapItem)
+ {
+ mRenameListener.setMapItem(mMapItem);
+ mDialog = new TextDialog(_("Rename map sign "),
+ _("Name: "));
+ mRenameListener.setDialog(mDialog);
+ mDialog->setText(mMapItem->getComment());
+ mDialog->setActionEventId("ok");
+ mDialog->addActionListener(&mRenameListener);
+ }
+ else if (link == "load old outfits")
+ {
+ if (outfitWindow)
+ outfitWindow->load(true);
+ }
+ else if (link == "load old spells")
+ {
+ if (spellManager)
+ {
+ spellManager->load(true);
+ spellManager->save();
+ }
+ }
+ else if (link == "load old item shortcuts")
+ {
+ if (itemShortcutWindow)
+ {
+ int num = itemShortcutWindow->getTabIndex();
+ if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num])
+ {
+ itemShortcut[num]->load(true);
+ itemShortcut[num]->save();
+ }
+ }
+ }
+ else if (link == "load old drop shortcuts")
+ {
+ if (dropShortcut)
+ {
+ dropShortcut->load(true);
+ dropShortcut->save();
+ }
+ }
+ else if (link == "edit spell" && mSpell)
+ {
+ new TextCommandEditor(mSpell);
+ }
+ else if (link == "undress" && being)
+ {
+ Net::getBeingHandler()->undress(being);
+ }
+ else if (link == "guild-kick" && !mNick.empty())
+ {
+ if (player_node)
+ {
+ const Guild *guild = player_node->getGuild();
+ if (guild)
+ Net::getGuildHandler()->kick(guild->getMember(mNick));
+ }
+ }
+ else if (link == "enable highlight" && mTab)
+ {
+ mTab->setAllowHighlight(true);
+ }
+ else if (link == "disable highlight" && mTab)
+ {
+ mTab->setAllowHighlight(false);
+ }
+ else if (link == "guild-pos" && !mNick.empty())
+ {
+ showChangePos(getX(), getY());
+ return;
+ }
+ else if (!link.find("guild-pos-"))
+ {
+ if (player_node)
+ {
+ int num = atoi(link.substr(10).c_str());
+ const Guild *guild = player_node->getGuild();
+ if (guild)
+ {
+ Net::getGuildHandler()->changeMemberPostion(
+ guild->getMember(mNick), num);
+ }
+ }
+ }
+ else if (!link.find("player_"))
+ {
+ mBeingId = atoi(link.substr(7).c_str());
+ Being *being = actorSpriteManager->findBeing(mBeingId);
+ if (being)
+ {
+ showPopup(getX(), getY(), being);
+ return;
+ }
+ }
+ // Unknown actions
+ else if (link != "cancel")
+ {
+ logger->log("PopupMenu: Warning, unknown action '%s'", link.c_str());
+ }
+
+ setVisible(false);
+
+ mBeingId = 0;
+ mFloorItem = 0;
+ mItem = 0;
+ mItemId = 0;
+ mMapItem = 0;
+ mNick = "";
+}
+
+void PopupMenu::showPopup(Window *parent, int x, int y, Item *item,
+ bool isInventory)
+{
+ if (!item)
+ return;
+
+ mItem = item;
+ mWindow = parent;
+ mBrowserBox->clearRows();
+
+ int cnt = item->getQuantity();
+
+ if (isInventory)
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip")));
+ else
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip")));
+ }
+ else
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use")));
+
+ if (cnt > 1)
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
+ else
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));
+
+ if (Net::getInventoryHandler()->canSplit(item))
+ mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split")));
+
+ if (InventoryWindow::isStorageActive())
+ {
+ mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store")));
+ if (cnt > 1)
+ {
+ if (cnt > 10)
+ {
+ mBrowserBox->addRow(strprintf("@@store 10|%s@@",
+ _("Store 10")));
+ }
+ mBrowserBox->addRow(strprintf("@@store half|%s@@",
+ _("Store half")));
+ mBrowserBox->addRow(strprintf("@@store all|%s@@",
+ _("Store all")));
+ }
+ }
+ }
+ // Assume in storage for now
+ // TODO: make this whole system more flexible, if needed
+ else
+ {
+ mBrowserBox->addRow(strprintf("@@retrieve|%s@@", _("Retrieve")));
+ if (cnt > 1)
+ {
+ if (cnt > 10)
+ {
+ mBrowserBox->addRow(strprintf("@@retrieve 10|%s@@",
+ _("Retrieve 10")));
+ }
+ mBrowserBox->addRow(strprintf("@@retrieve half|%s@@",
+ _("Retrieve half")));
+ mBrowserBox->addRow(strprintf("@@retrieve all|%s@@",
+ _("Retrieve all")));
+ }
+ }
+ mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showItemPopup(int x, int y, int itemId)
+{
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ Item *item = inv->findItem(itemId);
+ if (item)
+ {
+ showItemPopup(x, y, item);
+ }
+ else
+ {
+ mItem = 0;
+ mItemId = itemId;
+ mBrowserBox->clearRows();
+
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(strprintf("@@load old item shortcuts|%s@@",
+ _("Load old item shortcuts")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));
+
+ showPopup(x, y);
+ }
+}
+
+void PopupMenu::showItemPopup(int x, int y, Item *item)
+{
+ mItem = item;
+ if (item)
+ mItemId = item->getId();
+ else
+ mItemId = 0;
+ mBrowserBox->clearRows();
+
+ if (item)
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip")));
+ else
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip")));
+ }
+ else
+ {
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use")));
+ }
+
+ if (item->getQuantity() > 1)
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
+ else
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));
+
+ if (Net::getInventoryHandler()->canSplit(item))
+ mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split")));
+
+ if (InventoryWindow::isStorageActive())
+ mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store")));
+ mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat")));
+ mBrowserBox->addRow("##3---");
+ }
+ mBrowserBox->addRow(strprintf("@@load old item shortcuts|%s@@",
+ _("Load old item shortcuts")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showDropPopup(int x, int y, Item *item)
+{
+ mItem = item;
+ mBrowserBox->clearRows();
+
+ if (item)
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip")));
+ else
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip")));
+ }
+ else
+ mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use")));
+
+ if (item->getQuantity() > 1)
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
+ else
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));
+
+ if (Net::getInventoryHandler()->canSplit(item))
+ mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split")));
+
+ if (InventoryWindow::isStorageActive())
+ mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store")));
+ mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat")));
+ mBrowserBox->addRow("##3---");
+ }
+ mBrowserBox->addRow(strprintf("@@load old drop shortcuts|%s@@",
+ _("Load old drop shortcuts")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));
+
+ showPopup(x, y);
+}
+
+void PopupMenu::showPopup(int x _UNUSED_, int y _UNUSED_, Button *button)
+{
+ if (!button)
+ return;
+
+}
+
+void PopupMenu::showPopup(int x, int y)
+{
+ setContentSize(mBrowserBox->getWidth() + 8, mBrowserBox->getHeight() + 8);
+ if (graphics->getWidth() < (x + getWidth() + 5))
+ x = graphics->getWidth() - getWidth();
+ if (graphics->getHeight() < (y + getHeight() + 5))
+ y = graphics->getHeight() - getHeight();
+ setPosition(x, y);
+ setVisible(true);
+ requestMoveToTop();
+}
+
+
+void RenameListener::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "ok" && mMapItem && viewport && mDialog)
+ {
+ Map *map = viewport->getMap();
+ if (!map)
+ return;
+
+ SpecialLayer *sl = map->getSpecialLayer();
+ MapItem *item = 0;
+ if (sl)
+ {
+ item = sl->getTile(mMapItem->getX(), mMapItem->getY());
+ if (!item)
+ {
+ sl->setTile(mMapItem->getX(), mMapItem->getY(),
+ mMapItem->getType());
+ item = sl->getTile(mMapItem->getX(), mMapItem->getY());
+ }
+ item->setComment(mDialog->getText());
+ }
+ item = map->findPortalXY(mMapItem->getX(), mMapItem->getY());
+ if (item)
+ item->setComment(mDialog->getText());
+
+ if (socialWindow)
+ socialWindow->updatePortalNames();
+ }
+ mDialog = 0;
+}
diff --git a/src/gui/popupmenu.h b/src/gui/popupmenu.h
new file mode 100644
index 000000000..d6c66aa81
--- /dev/null
+++ b/src/gui/popupmenu.h
@@ -0,0 +1,149 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef POPUP_MENU_H
+#define POPUP_MENU_H
+
+#include "gui/widgets/linkhandler.h"
+#include "gui/widgets/popup.h"
+
+#include <guichan/actionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Being;
+class BrowserBox;
+class Button;
+class ChatTab;
+class FloorItem;
+class Item;
+class ItemShortcut;
+class MapItem;
+class TextCommand;
+class TextDialog;
+class Window;
+
+class RenameListener : public gcn::ActionListener
+{
+ public:
+ void action(const gcn::ActionEvent &event);
+
+ void setMapItem(MapItem* mapItem)
+ { mMapItem = mapItem; }
+
+ void setDialog(TextDialog *dialog)
+ { mDialog = dialog; }
+
+ private:
+ MapItem *mMapItem;
+ TextDialog *mDialog;
+};
+
+/**
+ * Window showing popup menu.
+ */
+class PopupMenu : public Popup, public LinkHandler
+{
+ public:
+ /**
+ * Constructor.
+ */
+ PopupMenu();
+
+ /**
+ * Shows the being related popup menu at the specified mouse coords.
+ */
+ void showPopup(int x, int y, Being *being);
+
+ /**
+ * Shows the beings related popup menu at the specified mouse coords.
+ */
+ void showPopup(int x, int y, std::list<Being*> &beings);
+
+ void showPlayerPopup(int x, int y, std::string nick);
+
+ /**
+ * Shows the floor item related popup menu at the specified
+ * mouse coords.
+ */
+ void showPopup(int x, int y, FloorItem *floorItem);
+
+ /**
+ * Shows the related popup menu when right click on the inventory
+ * at the specified mouse coordinates.
+ */
+ void showPopup(Window *parent, int x, int y, Item *item,
+ bool isInventory);
+
+ void showPopup(int x, int y, Button *button);
+
+ void showPopup(int x, int y, MapItem *mapItem);
+
+ void showItemPopup(int x, int y, Item *item);
+
+ void showItemPopup(int x, int y, int itemId);
+
+ void showDropPopup(int x, int y, Item *item);
+
+ void showOutfitsPopup(int x, int y);
+
+ void showSpellPopup(int x, int y, TextCommand *cmd);
+
+ /**
+ * Shows the related popup menu when right click on the chat
+ * at the specified mouse coordinates.
+ */
+ void showChatPopup(int x, int y, ChatTab *tab);
+
+ void showChangePos(int x, int y);
+
+ /**
+ * Handles link action.
+ */
+ void handleLink(const std::string &link,
+ gcn::MouseEvent *event _UNUSED_);
+
+ private:
+ BrowserBox* mBrowserBox;
+
+ int mBeingId;
+ FloorItem* mFloorItem;
+ Item *mItem;
+ int mItemId;
+ MapItem *mMapItem;
+ ChatTab *mTab;
+ TextCommand *mSpell;
+ Window *mWindow;
+ RenameListener mRenameListener;
+ TextDialog *mDialog;
+ std::string mNick;
+
+ /**
+ * Shared code for the various showPopup functions.
+ */
+ void showPopup(int x, int y);
+};
+
+#endif
diff --git a/src/gui/quitdialog.cpp b/src/gui/quitdialog.cpp
new file mode 100644
index 000000000..7b587dccc
--- /dev/null
+++ b/src/gui/quitdialog.cpp
@@ -0,0 +1,204 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/quitdialog.h"
+
+#include "client.h"
+
+#include "gui/chat.h"
+#include "gui/sdlinput.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/radiobutton.h"
+
+#include "net/charhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+
+#include <assert.h>
+
+QuitDialog::QuitDialog(QuitDialog** pointerToMe):
+ Window(_("Quit"), true, NULL), mMyPointer(pointerToMe)
+{
+// int width = 200;
+// int height = 120;
+
+ mForceQuit = new RadioButton(_("Quit"), "quitdialog");
+ mLogoutQuit = new RadioButton(_("Quit"), "quitdialog");
+ mSaveState = new CheckBox(_("Save state"), true);
+ mSwitchAccountServer = new RadioButton(_("Switch server"), "quitdialog");
+ mSwitchCharacter = new RadioButton(_("Switch character"), "quitdialog");
+ mOkButton = new Button(_("OK"), "ok", this);
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+// setContentSize(width, height);
+
+ addKeyListener(this);
+
+ ContainerPlacer place = getPlacer(0, 0);
+
+ const State state = Client::getState();
+
+ // All states, when we're not logged in to someone.
+ if (state == STATE_CHOOSE_SERVER ||
+ state == STATE_CONNECT_SERVER ||
+ state == STATE_LOGIN ||
+ state == STATE_LOGIN_ATTEMPT ||
+ state == STATE_UPDATE ||
+ state == STATE_LOAD_DATA)
+ {
+ placeOption(place, mForceQuit);
+ }
+ else
+ {
+ // Only added if we are connected to an accountserver or gameserver
+ placeOption(place, mLogoutQuit);
+ placeOption(place, mSwitchAccountServer);
+
+ // Only added if we are connected to a gameserver
+ if (state == STATE_GAME)
+ placeOption(place, mSwitchCharacter);
+ }
+
+ mOptions[0]->setSelected(true);
+
+ place = getPlacer(0, 1);
+
+ place(0, 0, mSaveState, 3);
+ place(1, 1, mOkButton, 1);
+ place(2, 1, mCancelButton, 1);
+
+ reflowLayout(200, 0);
+ setLocationRelativeTo(getParent());
+ setVisible(true);
+ requestModalFocus();
+ mOkButton->requestFocus();
+}
+
+QuitDialog::~QuitDialog()
+{
+ if (mMyPointer)
+ *mMyPointer = 0;
+ // Optional widgets, so delete them by hand.
+ delete mForceQuit;
+ mForceQuit = 0;
+ delete mLogoutQuit;
+ mLogoutQuit = 0;
+ delete mSwitchAccountServer;
+ mSwitchAccountServer = 0;
+ delete mSwitchCharacter;
+ mSwitchCharacter = 0;
+}
+
+void QuitDialog::placeOption(ContainerPlacer &place, gcn::RadioButton *option)
+{
+ place(0, static_cast<int>(mOptions.size()), option, 3);
+ mOptions.push_back(option);
+}
+
+void QuitDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "ok")
+ {
+ if (viewport)
+ {
+ Map *map = viewport->getCurrentMap();
+ if (map)
+ map->saveExtraLayer();
+ }
+ if (chatWindow && mSaveState->isSelected())
+ chatWindow->saveState();
+
+ if (mForceQuit->isSelected())
+ {
+ Client::setState(STATE_FORCE_QUIT);
+ }
+ else if (mLogoutQuit->isSelected())
+ {
+ Client::setState(STATE_EXIT);
+ }
+ else if (mSwitchAccountServer->isSelected())
+ {
+ Client::setState(STATE_SWITCH_SERVER);
+ }
+ else if (mSwitchCharacter->isSelected())
+ {
+ assert(Client::getState() == STATE_GAME);
+
+ Net::getCharHandler()->switchCharacter();
+ }
+ }
+ scheduleDelete();
+}
+
+void QuitDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ const gcn::Key &key = keyEvent.getKey();
+ int dir = 0;
+
+ switch (key.getValue())
+ {
+ case Key::ENTER:
+ action(gcn::ActionEvent(NULL, mOkButton->getActionEventId()));
+ break;
+ case Key::ESCAPE:
+ action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId()));
+ break;
+ case Key::UP:
+ dir = -1;
+ break;
+ case Key::DOWN:
+ dir = 1;
+ break;
+ default:
+ break;
+ }
+
+ if (dir != 0)
+ {
+ std::vector<gcn::RadioButton*>::iterator it = mOptions.begin();
+
+ for (; it < mOptions.end(); it++)
+ {
+ if ((*it)->isSelected())
+ break;
+ }
+
+ if (it == mOptions.end())
+ {
+ if (mOptions[0])
+ mOptions[0]->setSelected(true);
+ return;
+ }
+ else if (it == mOptions.begin() && dir < 0)
+ it = mOptions.end();
+
+ it += dir;
+
+ if (it == mOptions.end())
+ it = mOptions.begin();
+
+ (*it)->setSelected(true);
+ }
+}
diff --git a/src/gui/quitdialog.h b/src/gui/quitdialog.h
new file mode 100644
index 000000000..a6763079e
--- /dev/null
+++ b/src/gui/quitdialog.h
@@ -0,0 +1,77 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QUITDIALOG_H
+#define QUITDIALOG_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+#include <vector>
+
+/**
+ * The quit dialog.
+ *
+ * \ingroup Interface
+ */
+class QuitDialog : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @pointerToMe will be set to NULL when the QuitDialog is destroyed
+ */
+ QuitDialog(QuitDialog **pointerToMe);
+
+ /**
+ * Destructor
+ */
+ ~QuitDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ private:
+ void placeOption(ContainerPlacer &place, gcn::RadioButton *option);
+ std::vector<gcn::RadioButton*> mOptions;
+
+ gcn::RadioButton *mLogoutQuit;
+ gcn::CheckBox *mSaveState;
+ gcn::RadioButton *mForceQuit;
+ gcn::RadioButton *mSwitchAccountServer;
+ gcn::RadioButton *mSwitchCharacter;
+ gcn::Button *mOkButton;
+ gcn::Button *mCancelButton;
+
+ QuitDialog **mMyPointer;
+};
+
+#endif
diff --git a/src/gui/register.cpp b/src/gui/register.cpp
new file mode 100644
index 000000000..fbd63195f
--- /dev/null
+++ b/src/gui/register.cpp
@@ -0,0 +1,258 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/register.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "log.h"
+
+#include "gui/login.h"
+#include "gui/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/passwordfield.h"
+#include "gui/widgets/radiobutton.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+WrongDataNoticeListener::WrongDataNoticeListener():
+ mTarget(0)
+{
+}
+
+void WrongDataNoticeListener::setTarget(gcn::TextField *textField)
+{
+ mTarget = textField;
+}
+
+void WrongDataNoticeListener::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "ok" && mTarget)
+ mTarget->requestFocus();
+}
+
+RegisterDialog::RegisterDialog(LoginData *loginData):
+ Window(_("Register")),
+ mEmailField(0),
+ mMaleButton(0),
+ mFemaleButton(0),
+ mWrongDataNoticeListener(new WrongDataNoticeListener),
+ mLoginData(loginData)
+{
+ int optionalActions = Net::getLoginHandler()->supportedOptionalActions();
+
+ gcn::Label *userLabel = new Label(_("Name:"));
+ gcn::Label *passwordLabel = new Label(_("Password:"));
+ gcn::Label *confirmLabel = new Label(_("Confirm:"));
+ mUserField = new TextField(loginData->username);
+ mPasswordField = new PasswordField(loginData->password);
+ mConfirmField = new PasswordField;
+ mRegisterButton = new Button(_("Register"), "register", this);
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+ place(0, 0, userLabel);
+ place(0, 1, passwordLabel);
+ place(0, 2, confirmLabel);
+
+ place(1, 0, mUserField, 3).setPadding(2);
+ place(1, 1, mPasswordField, 3).setPadding(2);
+ place(1, 2, mConfirmField, 3).setPadding(2);
+
+ int row = 3;
+
+ if (optionalActions & Net::LoginHandler::SetGenderOnRegister)
+ {
+ mMaleButton = new RadioButton(_("Male"), "sex", true);
+ mFemaleButton = new RadioButton(_("Female"), "sex", false);
+ place(1, row, mMaleButton);
+ place(2, row, mFemaleButton);
+
+ row++;
+ }
+
+ if (optionalActions & Net::LoginHandler::SetEmailOnRegister)
+ {
+ gcn::Label *emailLabel = new Label(_("Email:"));
+ mEmailField = new TextField;
+ place(0, row, emailLabel);
+ place(1, row, mEmailField, 3).setPadding(2);
+
+ row++;
+ }
+
+ place = getPlacer(0, 2);
+ place(1, 0, mRegisterButton);
+ place(2, 0, mCancelButton);
+ reflowLayout(250, 0);
+
+ mUserField->addKeyListener(this);
+ mPasswordField->addKeyListener(this);
+ mConfirmField->addKeyListener(this);
+
+ /* TODO:
+ * This is a quick and dirty way to respond to the ENTER key, regardless of
+ * which text field is selected. There may be a better way now with the new
+ * input system of Guichan 0.6.0. See also the login dialog.
+ */
+ mUserField->setActionEventId("register");
+ mPasswordField->setActionEventId("register");
+ mConfirmField->setActionEventId("register");
+
+ mUserField->addActionListener(this);
+ mPasswordField->addActionListener(this);
+ mConfirmField->addActionListener(this);
+
+ center();
+ setVisible(true);
+ mUserField->requestFocus();
+ mUserField->setCaretPosition(static_cast<unsigned>(
+ mUserField->getText().length()));
+
+ mRegisterButton->setEnabled(canSubmit());
+}
+
+RegisterDialog::~RegisterDialog()
+{
+ delete mWrongDataNoticeListener;
+ mWrongDataNoticeListener = 0;
+}
+
+void RegisterDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "cancel")
+ {
+ Client::setState(STATE_LOGIN);
+ }
+ else if (event.getId() == "register" && canSubmit())
+ {
+ const std::string user = mUserField->getText();
+ logger->log("RegisterDialog::register Username is %s", user.c_str());
+
+ std::string errorMessage;
+ int error = 0;
+
+ unsigned int minUser = Net::getLoginHandler()->getMinUserNameLength();
+ unsigned int maxUser = Net::getLoginHandler()->getMaxUserNameLength();
+ unsigned int minPass = Net::getLoginHandler()->getMinPasswordLength();
+ unsigned int maxPass = Net::getLoginHandler()->getMaxPasswordLength();
+
+ if (user.length() < minUser)
+ {
+ // Name too short
+ errorMessage = strprintf
+ (_("The username needs to be at least %d characters long."),
+ minUser);
+ error = 1;
+ }
+ else if (user.length() > maxUser - 1 )
+ {
+ // Name too long
+ errorMessage = strprintf
+ (_("The username needs to be less than %d characters long."),
+ maxUser);
+ error = 1;
+ }
+ else if (mPasswordField->getText().length() < minPass)
+ {
+ // Pass too short
+ errorMessage = strprintf
+ (_("The password needs to be at least %d characters long."),
+ minPass);
+ error = 2;
+ }
+ else if (mPasswordField->getText().length() > maxPass - 1 )
+ {
+ // Pass too long
+ errorMessage = strprintf
+ (_("The password needs to be less than %d characters long."),
+ maxPass);
+ error = 2;
+ }
+ else if (mPasswordField->getText() != mConfirmField->getText())
+ {
+ // Password does not match with the confirmation one
+ errorMessage = _("Passwords do not match.");
+ error = 2;
+ }
+
+ // TODO: Check if a valid email address was given
+
+ if (error > 0)
+ {
+ if (error == 1)
+ {
+ mWrongDataNoticeListener->setTarget(this->mUserField);
+ }
+ else if (error == 2)
+ {
+ // Reset password confirmation
+ mPasswordField->setText("");
+ mConfirmField->setText("");
+
+ mWrongDataNoticeListener->setTarget(this->mPasswordField);
+ }
+
+ OkDialog *dlg = new OkDialog(_("Error"), errorMessage);
+ dlg->addActionListener(mWrongDataNoticeListener);
+ }
+ else
+ {
+ // No errors detected, register the new user.
+ mRegisterButton->setEnabled(false);
+
+ mLoginData->username = mUserField->getText();
+ mLoginData->password = mPasswordField->getText();
+ if (mFemaleButton)
+ mLoginData->gender = mFemaleButton->isSelected() ?
+ GENDER_FEMALE : GENDER_MALE;
+ if (mEmailField)
+ mLoginData->email = mEmailField->getText();
+ mLoginData->registerLogin = true;
+
+ Client::setState(STATE_REGISTER_ATTEMPT);
+ }
+ }
+}
+
+void RegisterDialog::keyPressed(gcn::KeyEvent &keyEvent _UNUSED_)
+{
+ mRegisterButton->setEnabled(canSubmit());
+}
+
+bool RegisterDialog::canSubmit() const
+{
+ return !mUserField->getText().empty() &&
+ !mPasswordField->getText().empty() &&
+ !mConfirmField->getText().empty() &&
+ Client::getState() == STATE_REGISTER;
+}
diff --git a/src/gui/register.h b/src/gui/register.h
new file mode 100644
index 000000000..f9c866768
--- /dev/null
+++ b/src/gui/register.h
@@ -0,0 +1,110 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef REGISTER_H
+#define REGISTER_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+#include <string>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class LoginData;
+class OkDialog;
+
+/**
+ * Listener used while dealing with wrong data. It is used to direct the focus
+ * to the field which contained wrong data when the Ok button was pressed on
+ * the error notice.
+ */
+class WrongDataNoticeListener : public gcn::ActionListener
+{
+ public:
+ WrongDataNoticeListener();
+ void setTarget(gcn::TextField *textField);
+ void action(const gcn::ActionEvent &event);
+ private:
+ gcn::TextField *mTarget;
+};
+
+/**
+ * The registration dialog.
+ *
+ * \ingroup Interface
+ */
+class RegisterDialog : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor. Name, password and server fields will be initialized to
+ * the information already present in the LoginData instance.
+ *
+ * @see Window::Window
+ */
+ RegisterDialog(LoginData *loginData);
+
+ /**
+ * Destructor
+ */
+ ~RegisterDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Called when a key is pressed in one of the text fields.
+ */
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ private:
+ /**
+ * Returns whether submit can be enabled. This is true in the register
+ * state, when all necessary fields have some text.
+ */
+ bool canSubmit() const;
+
+ gcn::TextField *mUserField;
+ gcn::TextField *mPasswordField;
+ gcn::TextField *mConfirmField;
+ gcn::TextField *mEmailField;
+
+ gcn::Button *mRegisterButton;
+ gcn::Button *mCancelButton;
+ gcn::RadioButton *mMaleButton;
+ gcn::RadioButton *mFemaleButton;
+
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+
+ LoginData *mLoginData;
+};
+
+#endif
diff --git a/src/gui/sdlinput.cpp b/src/gui/sdlinput.cpp
new file mode 100644
index 000000000..a48f35be8
--- /dev/null
+++ b/src/gui/sdlinput.cpp
@@ -0,0 +1,432 @@
+/* _______ __ __ __ ______ __ __ _______ __ __
+ * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\
+ * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
+ * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / /
+ * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / /
+ * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
+ * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
+ *
+ * Copyright (c) 2004, 2005, 2006, 2007 Olof Naessén and Per Larsson
+ * Copyright (C) 2007-2010 The Mana World Development Team
+ *
+ * Js_./
+ * Per Larsson a.k.a finalman _RqZ{a<^_aa
+ * Olof Naessén a.k.a jansem/yakslem _asww7!uY`> )\a//
+ * _Qhm`] _f "'c 1!5m
+ * Visit: http://guichan.darkbits.org )Qk<P ` _: :+' .' "{[
+ * .)j(] .d_/ '-( P . S
+ * License: (BSD) <Td/Z <fP"5(\"??"\a. .L
+ * Redistribution and use in source and _dV>ws?a-?' ._/L #'
+ * binary forms, with or without )4d[#7r, . ' )d`)[
+ * modification, are permitted provided _Q-5'5W..j/?' -?!\)cam'
+ * that the following conditions are met: j<<WP+k/);. _W=j f
+ * 1. Redistributions of source code must .$%w\/]Q . ."' . mj$
+ * retain the above copyright notice, ]E.pYY(Q]>. a J@\
+ * this list of conditions and the j(]1u<sE"L,. . ./^ ]{a
+ * following disclaimer. 4'_uomm\. )L);-4 (3=
+ * 2. Redistributions in binary form must )_]X{Z('a_"a7'<a"a, ]"[
+ * reproduce the above copyright notice, #}<]m7`Za??4,P-"'7. ).m
+ * this list of conditions and the ]d2e)Q(<Q( ?94 b- LQ/
+ * following disclaimer in the <B!</]C)d_, '(<' .f. =C+m
+ * documentation and/or other materials .Z!=J ]e []('-4f _ ) -.)m]'
+ * provided with the distribution. .w[5]' _[ /.)_-"+? _/ <W"
+ * 3. Neither the name of Guichan nor the :$we` _! + _/ . j?
+ * names of its contributors may be used =3)= _f (_yQmWW$#( "
+ * to endorse or promote products derived - W, sQQQQmZQ#Wwa]..
+ * from this software without specific (js, \[QQW$QWW#?!V"".
+ * prior written permission. ]y:.<\.. .
+ * -]n w/ ' [.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT )/ )/ !
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY < (; sac , '
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, ]^ .- %
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF c < r
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR aga< <La
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 5% )P'-3L
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR _bQf` y`..)a
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ,J?4P'.P"_(\?d'.,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES _Pa,)!f/<[]/ ?"
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT _2-..:. .r+_,.. .
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ?a.<%"' " -'.a_ _,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ^
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "sdlinput.h"
+
+#include <guichan/exception.hpp>
+
+SDLInput::SDLInput()
+{
+ mMouseInWindow = true;
+ mMouseDown = false;
+}
+
+bool SDLInput::isKeyQueueEmpty()
+{
+ return mKeyInputQueue.empty();
+}
+
+gcn::KeyInput SDLInput::dequeueKeyInput()
+{
+ gcn::KeyInput keyInput;
+
+ if (mKeyInputQueue.empty())
+ {
+ throw GCN_EXCEPTION("The queue is empty.");
+ }
+
+ keyInput = mKeyInputQueue.front();
+ mKeyInputQueue.pop();
+
+ return keyInput;
+}
+
+bool SDLInput::isMouseQueueEmpty()
+{
+ return mMouseInputQueue.empty();
+}
+
+gcn::MouseInput SDLInput::dequeueMouseInput()
+{
+ gcn::MouseInput mouseInput;
+
+ if (mMouseInputQueue.empty())
+ {
+ throw GCN_EXCEPTION("The queue is empty.");
+ }
+
+ mouseInput = mMouseInputQueue.front();
+ mMouseInputQueue.pop();
+
+ return mouseInput;
+}
+
+void SDLInput::pushInput(SDL_Event event)
+{
+ gcn::KeyInput keyInput;
+ gcn::MouseInput mouseInput;
+
+ switch (event.type)
+ {
+ case SDL_KEYDOWN:
+ keyInput.setKey(gcn::Key(convertKeyCharacter(event)));
+ keyInput.setType(gcn::KeyInput::PRESSED);
+ keyInput.setShiftPressed(event.key.keysym.mod & KMOD_SHIFT);
+ keyInput.setControlPressed(event.key.keysym.mod & KMOD_CTRL);
+ keyInput.setAltPressed(event.key.keysym.mod & KMOD_ALT);
+ keyInput.setMetaPressed(event.key.keysym.mod & KMOD_META);
+ keyInput.setNumericPad(event.key.keysym.sym >= SDLK_KP0
+ && event.key.keysym.sym <= SDLK_KP_EQUALS);
+
+ mKeyInputQueue.push(keyInput);
+ break;
+
+ case SDL_KEYUP:
+ keyInput.setKey(gcn::Key(convertKeyCharacter(event)));
+ keyInput.setType(gcn::KeyInput::RELEASED);
+ keyInput.setShiftPressed(event.key.keysym.mod & KMOD_SHIFT);
+ keyInput.setControlPressed(event.key.keysym.mod & KMOD_CTRL);
+ keyInput.setAltPressed(event.key.keysym.mod & KMOD_ALT);
+ keyInput.setMetaPressed(event.key.keysym.mod & KMOD_META);
+ keyInput.setNumericPad(event.key.keysym.sym >= SDLK_KP0
+ && event.key.keysym.sym <= SDLK_KP_EQUALS);
+
+ mKeyInputQueue.push(keyInput);
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ mMouseDown = true;
+ mouseInput.setX(event.button.x);
+ mouseInput.setY(event.button.y);
+ mouseInput.setButton(convertMouseButton(event.button.button));
+
+ if (event.button.button == SDL_BUTTON_WHEELDOWN)
+ {
+ mouseInput.setType(gcn::MouseInput::WHEEL_MOVED_DOWN);
+ }
+ else if (event.button.button == SDL_BUTTON_WHEELUP)
+ {
+ mouseInput.setType(gcn::MouseInput::WHEEL_MOVED_UP);
+ }
+ else
+ {
+ mouseInput.setType(gcn::MouseInput::PRESSED);
+ }
+ mouseInput.setTimeStamp(SDL_GetTicks());
+ mMouseInputQueue.push(mouseInput);
+ break;
+
+ case SDL_MOUSEBUTTONUP:
+ mMouseDown = false;
+ mouseInput.setX(event.button.x);
+ mouseInput.setY(event.button.y);
+ mouseInput.setButton(convertMouseButton(event.button.button));
+ mouseInput.setType(gcn::MouseInput::RELEASED);
+ mouseInput.setTimeStamp(SDL_GetTicks());
+ mMouseInputQueue.push(mouseInput);
+ break;
+
+ case SDL_MOUSEMOTION:
+ mouseInput.setX(event.button.x);
+ mouseInput.setY(event.button.y);
+ mouseInput.setButton(gcn::MouseInput::EMPTY);
+ mouseInput.setType(gcn::MouseInput::MOVED);
+ mouseInput.setTimeStamp(SDL_GetTicks());
+ mMouseInputQueue.push(mouseInput);
+ break;
+
+ case SDL_ACTIVEEVENT:
+ /*
+ * This occurs when the mouse leaves the window and the Gui-chan
+ * application loses its mousefocus.
+ */
+ if ((event.active.state & SDL_APPMOUSEFOCUS)
+ && !event.active.gain)
+ {
+ mMouseInWindow = false;
+
+ if (!mMouseDown)
+ {
+ mouseInput.setX(-1);
+ mouseInput.setY(-1);
+ mouseInput.setButton(gcn::MouseInput::EMPTY);
+ mouseInput.setType(gcn::MouseInput::MOVED);
+ mMouseInputQueue.push(mouseInput);
+ }
+ }
+
+ if ((event.active.state & SDL_APPMOUSEFOCUS)
+ && event.active.gain)
+ {
+ mMouseInWindow = true;
+ }
+ break;
+
+ default:
+ break;
+
+ } // end switch
+}
+
+int SDLInput::convertMouseButton(int button)
+{
+ switch (button)
+ {
+ case SDL_BUTTON_LEFT:
+ return gcn::MouseInput::LEFT;
+ case SDL_BUTTON_RIGHT:
+ return gcn::MouseInput::RIGHT;
+ case SDL_BUTTON_MIDDLE:
+ return gcn::MouseInput::MIDDLE;
+ default:
+ // We have an unknown mouse type which is ignored.
+ return button;
+ }
+}
+
+int SDLInput::convertKeyCharacter(SDL_Event event)
+{
+ SDL_keysym keysym = event.key.keysym;
+
+ int value = keysym.unicode;
+
+ switch (keysym.sym)
+ {
+ case SDLK_TAB:
+ value = Key::TAB;
+ break;
+ case SDLK_LALT:
+ value = Key::LEFT_ALT;
+ break;
+ case SDLK_RALT:
+ value = Key::RIGHT_ALT;
+ break;
+ case SDLK_LSHIFT:
+ value = Key::LEFT_SHIFT;
+ break;
+ case SDLK_RSHIFT:
+ value = Key::RIGHT_SHIFT;
+ break;
+ case SDLK_LCTRL:
+ value = Key::LEFT_CONTROL;
+ break;
+ case SDLK_RCTRL:
+ value = Key::RIGHT_CONTROL;
+ break;
+ case SDLK_BACKSPACE:
+ value = Key::BACKSPACE;
+ break;
+ case SDLK_PAUSE:
+ value = Key::PAUSE;
+ break;
+ case SDLK_SPACE:
+ // Special characters like ~ (tilde) ends up
+ // with the keysym.sym SDLK_SPACE which
+ // without this check would be lost. The check
+ // is only valid on key down events in SDL.
+ if (event.type == SDL_KEYUP || keysym.unicode == ' ')
+ {
+ value = Key::SPACE;
+ }
+ break;
+ case SDLK_ESCAPE:
+ value = Key::ESCAPE;
+ break;
+ case SDLK_DELETE:
+ value = Key::DELETE;
+ break;
+ case SDLK_INSERT:
+ value = Key::INSERT;
+ break;
+ case SDLK_HOME:
+ value = Key::HOME;
+ break;
+ case SDLK_END:
+ value = Key::END;
+ break;
+ case SDLK_PAGEUP:
+ value = Key::PAGE_UP;
+ break;
+ case SDLK_PRINT:
+ value = Key::PRINT_SCREEN;
+ break;
+ case SDLK_PAGEDOWN:
+ value = Key::PAGE_DOWN;
+ break;
+ case SDLK_F1:
+ value = Key::F1;
+ break;
+ case SDLK_F2:
+ value = Key::F2;
+ break;
+ case SDLK_F3:
+ value = Key::F3;
+ break;
+ case SDLK_F4:
+ value = Key::F4;
+ break;
+ case SDLK_F5:
+ value = Key::F5;
+ break;
+ case SDLK_F6:
+ value = Key::F6;
+ break;
+ case SDLK_F7:
+ value = Key::F7;
+ break;
+ case SDLK_F8:
+ value = Key::F8;
+ break;
+ case SDLK_F9:
+ value = Key::F9;
+ break;
+ case SDLK_F10:
+ value = Key::F10;
+ break;
+ case SDLK_F11:
+ value = Key::F11;
+ break;
+ case SDLK_F12:
+ value = Key::F12;
+ break;
+ case SDLK_F13:
+ value = Key::F13;
+ break;
+ case SDLK_F14:
+ value = Key::F14;
+ break;
+ case SDLK_F15:
+ value = Key::F15;
+ break;
+ case SDLK_NUMLOCK:
+ value = Key::NUM_LOCK;
+ break;
+ case SDLK_CAPSLOCK:
+ value = Key::CAPS_LOCK;
+ break;
+ case SDLK_SCROLLOCK:
+ value = Key::SCROLL_LOCK;
+ break;
+ case SDLK_RMETA:
+ value = Key::RIGHT_META;
+ break;
+ case SDLK_LMETA:
+ value = Key::LEFT_META;
+ break;
+ case SDLK_LSUPER:
+ value = Key::LEFT_SUPER;
+ break;
+ case SDLK_RSUPER:
+ value = Key::RIGHT_SUPER;
+ break;
+ case SDLK_MODE:
+ value = Key::ALT_GR;
+ break;
+ case SDLK_UP:
+ value = Key::UP;
+ break;
+ case SDLK_DOWN:
+ value = Key::DOWN;
+ break;
+ case SDLK_LEFT:
+ value = Key::LEFT;
+ break;
+ case SDLK_RIGHT:
+ value = Key::RIGHT;
+ break;
+ case SDLK_RETURN:
+ value = Key::ENTER;
+ break;
+ case SDLK_KP_ENTER:
+ value = Key::ENTER;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!(keysym.mod & KMOD_NUM))
+ {
+ switch (keysym.sym)
+ {
+ case SDLK_KP0:
+ value = Key::INSERT;
+ break;
+ case SDLK_KP1:
+ value = Key::END;
+ break;
+ case SDLK_KP2:
+ value = Key::DOWN;
+ break;
+ case SDLK_KP3:
+ value = Key::PAGE_DOWN;
+ break;
+ case SDLK_KP4:
+ value = Key::LEFT;
+ break;
+ case SDLK_KP5:
+ value = 0;
+ break;
+ case SDLK_KP6:
+ value = Key::RIGHT;
+ break;
+ case SDLK_KP7:
+ value = Key::HOME;
+ break;
+ case SDLK_KP8:
+ value = Key::UP;
+ break;
+ case SDLK_KP9:
+ value = Key::PAGE_UP;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return value;
+}
diff --git a/src/gui/sdlinput.h b/src/gui/sdlinput.h
new file mode 100644
index 000000000..b441380a4
--- /dev/null
+++ b/src/gui/sdlinput.h
@@ -0,0 +1,188 @@
+/* _______ __ __ __ ______ __ __ _______ __ __
+ * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\
+ * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
+ * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / /
+ * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / /
+ * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
+ * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
+ *
+ * Copyright (c) 2004, 2005, 2006, 2007 Olof Naessén and Per Larsson
+ * Copyright (C) 2007-2010 The Mana World Development Team
+ *
+ * Js_./
+ * Per Larsson a.k.a finalman _RqZ{a<^_aa
+ * Olof Naessén a.k.a jansem/yakslem _asww7!uY`> )\a//
+ * _Qhm`] _f "'c 1!5m
+ * Visit: http://guichan.darkbits.org )Qk<P ` _: :+' .' "{[
+ * .)j(] .d_/ '-( P . S
+ * License: (BSD) <Td/Z <fP"5(\"??"\a. .L
+ * Redistribution and use in source and _dV>ws?a-?' ._/L #'
+ * binary forms, with or without )4d[#7r, . ' )d`)[
+ * modification, are permitted provided _Q-5'5W..j/?' -?!\)cam'
+ * that the following conditions are met: j<<WP+k/);. _W=j f
+ * 1. Redistributions of source code must .$%w\/]Q . ."' . mj$
+ * retain the above copyright notice, ]E.pYY(Q]>. a J@\
+ * this list of conditions and the j(]1u<sE"L,. . ./^ ]{a
+ * following disclaimer. 4'_uomm\. )L);-4 (3=
+ * 2. Redistributions in binary form must )_]X{Z('a_"a7'<a"a, ]"[
+ * reproduce the above copyright notice, #}<]m7`Za??4,P-"'7. ).m
+ * this list of conditions and the ]d2e)Q(<Q( ?94 b- LQ/
+ * following disclaimer in the <B!</]C)d_, '(<' .f. =C+m
+ * documentation and/or other materials .Z!=J ]e []('-4f _ ) -.)m]'
+ * provided with the distribution. .w[5]' _[ /.)_-"+? _/ <W"
+ * 3. Neither the name of Guichan nor the :$we` _! + _/ . j?
+ * names of its contributors may be used =3)= _f (_yQmWW$#( "
+ * to endorse or promote products derived - W, sQQQQmZQ#Wwa]..
+ * from this software without specific (js, \[QQW$QWW#?!V"".
+ * prior written permission. ]y:.<\.. .
+ * -]n w/ ' [.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT )/ )/ !
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY < (; sac , '
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, ]^ .- %
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF c < r
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR aga< <La
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 5% )P'-3L
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR _bQf` y`..)a
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ,J?4P'.P"_(\?d'.,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES _Pa,)!f/<[]/ ?"
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT _2-..:. .r+_,.. .
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ?a.<%"' " -'.a_ _,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ^
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SDLINPUT_H
+#define SDLINPUT_H
+
+#include <queue>
+
+#include <SDL/SDL.h>
+
+#include <guichan/input.hpp>
+#include <guichan/keyinput.hpp>
+#include <guichan/mouseinput.hpp>
+#include <guichan/platform.hpp>
+
+namespace Key
+{
+ enum
+ {
+ SPACE = ' ',
+ TAB = '\t',
+ ENTER = '\n',
+ // Negative values, to avoid conflicts with higher character codes.
+ LEFT_ALT = -1000,
+ RIGHT_ALT,
+ LEFT_SHIFT,
+ RIGHT_SHIFT,
+ LEFT_CONTROL,
+ RIGHT_CONTROL,
+ LEFT_META,
+ RIGHT_META,
+ LEFT_SUPER,
+ RIGHT_SUPER,
+ INSERT,
+ HOME,
+ PAGE_UP,
+ DELETE,
+ END,
+ PAGE_DOWN,
+ ESCAPE,
+ CAPS_LOCK,
+ BACKSPACE,
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+ F13,
+ F14,
+ F15,
+ PRINT_SCREEN,
+ SCROLL_LOCK,
+ PAUSE,
+ NUM_LOCK,
+ ALT_GR,
+ LEFT,
+ RIGHT,
+ UP,
+ DOWN
+ };
+}
+
+/**
+ * SDL implementation of Input.
+ */
+class SDLInput : public gcn::Input
+{
+public:
+
+ /**
+ * Constructor.
+ */
+ SDLInput();
+
+ /**
+ * Pushes an SDL event. It should be called at least once per frame to
+ * update input with user input.
+ *
+ * @param event an event from SDL.
+ */
+ virtual void pushInput(SDL_Event event);
+
+ /**
+ * Polls all input. It exists for input driver compatibility. If you
+ * only use SDL and plan sticking with SDL you can safely ignore this
+ * function as it in the SDL case does nothing.
+ */
+ virtual void _pollInput() { }
+
+
+ // Inherited from Input
+
+ virtual bool isKeyQueueEmpty();
+
+ virtual gcn::KeyInput dequeueKeyInput();
+
+ virtual bool isMouseQueueEmpty();
+
+ virtual gcn::MouseInput dequeueMouseInput();
+
+protected:
+ /**
+ * Converts a mouse button from SDL to a Guichan mouse button
+ * representation.
+ *
+ * @param button an SDL mouse button.
+ * @return a Guichan mouse button.
+ */
+ int convertMouseButton(int button);
+
+ /**
+ * Converts an SDL event key to a key value.
+ *
+ * @param event an SDL event with a key to convert.
+ * @return a key value.
+ * @see Key
+ */
+ int convertKeyCharacter(SDL_Event event);
+
+ std::queue<gcn::KeyInput> mKeyInputQueue;
+ std::queue<gcn::MouseInput> mMouseInputQueue;
+
+ bool mMouseDown;
+ bool mMouseInWindow;
+};
+
+#endif
diff --git a/src/gui/sell.cpp b/src/gui/sell.cpp
new file mode 100644
index 000000000..85577b245
--- /dev/null
+++ b/src/gui/sell.cpp
@@ -0,0 +1,333 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/sell.h"
+
+#include "shopitem.h"
+#include "units.h"
+
+#include "gui/setup.h"
+#include "gui/trade.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/shopitems.h"
+#include "gui/widgets/shoplistbox.h"
+#include "gui/widgets/slider.h"
+
+#include "net/buysellhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+SellDialog::DialogList SellDialog::instances;
+
+SellDialog::SellDialog(int npcId):
+ Window(_("Sell")),
+ mNpcId(npcId), mMaxItems(0), mAmountItems(0), mNick("")
+{
+ init();
+}
+
+SellDialog::SellDialog(std::string nick):
+ Window(_("Sell")),
+ mNpcId(-1), mMaxItems(0), mAmountItems(0), mNick(nick)
+{
+ init();
+}
+
+void SellDialog::init()
+{
+ setWindowName("Sell");
+ //setupWindow->registerWindowForReset(this);
+ setResizable(true);
+ setCloseButton(true);
+ setMinWidth(260);
+ setMinHeight(230);
+ setDefaultSize(260, 230, ImageRect::CENTER);
+
+ // Create a ShopItems instance, that is aware of duplicate entries.
+ mShopItems = new ShopItems(true);
+
+ mShopItemList = new ShopListBox(mShopItems, mShopItems);
+ mScrollArea = new ScrollArea(mShopItemList);
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mSlider = new Slider(1.0);
+
+ mQuantityLabel = new Label(strprintf("%d / %d", mAmountItems, mMaxItems));
+ mQuantityLabel->setAlignment(gcn::Graphics::CENTER);
+ mMoneyLabel = new Label(strprintf(_("Price: %s / Total: %s"),
+ "", ""));
+
+ mIncreaseButton = new Button(_("+"), "inc", this);
+ mDecreaseButton = new Button(_("-"), "dec", this);
+ mSellButton = new Button(_("Sell"), "sell", this);
+ mQuitButton = new Button(_("Quit"), "quit", this);
+ mAddMaxButton = new Button(_("Max"), "max", this);
+
+ mDecreaseButton->adjustSize();
+ mDecreaseButton->setWidth(mIncreaseButton->getWidth());
+
+ mIncreaseButton->setEnabled(false);
+ mDecreaseButton->setEnabled(false);
+ mSellButton->setEnabled(false);
+ mSlider->setEnabled(false);
+
+ mShopItemList->setPriceCheck(false);
+ mShopItemList->addSelectionListener(this);
+ mSlider->setActionEventId("slider");
+ mSlider->addActionListener(this);
+
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+
+ place(0, 0, mScrollArea, 8, 5).setPadding(3);
+ place(0, 5, mDecreaseButton);
+ place(1, 5, mSlider, 3);
+ place(4, 5, mIncreaseButton);
+ place(5, 5, mQuantityLabel, 2);
+ place(7, 5, mAddMaxButton);
+ place(0, 6, mMoneyLabel, 8);
+ place(6, 7, mSellButton);
+ place(7, 7, mQuitButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ center();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(true);
+}
+
+SellDialog::~SellDialog()
+{
+ delete mShopItems;
+ mShopItems = 0;
+
+ instances.remove(this);
+}
+
+void SellDialog::reset()
+{
+ mShopItems->clear();
+ mSlider->setValue(0);
+
+ // Reset previous selected item to prevent failing asserts
+ mShopItemList->setSelected(-1);
+
+ updateButtonsAndLabels();
+}
+
+void SellDialog::addItem(const Item *item, int price)
+{
+ if (!item)
+ return;
+
+ mShopItems->addItem(item->getInvIndex(), item->getId(),
+ item->getQuantity(), price);
+
+ mShopItemList->adjustSize();
+}
+
+void SellDialog::addItem(int id, int amount, int price)
+{
+ mShopItems->addItem(id, amount, price);
+ mShopItemList->adjustSize();
+}
+
+
+void SellDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "quit")
+ {
+ close();
+ return;
+ }
+
+ int selectedItem = mShopItemList->getSelected();
+
+ // The following actions require a valid item selection
+ if (selectedItem == -1 ||
+ selectedItem >= static_cast<int>(mShopItems->getNumberOfElements()))
+ {
+ return;
+ }
+
+ if (event.getId() == "slider")
+ {
+ mAmountItems = static_cast<int>(mSlider->getValue());
+ updateButtonsAndLabels();
+ }
+ else if (event.getId() == "inc" && mAmountItems < mMaxItems)
+ {
+ mAmountItems++;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (event.getId() == "dec" && mAmountItems > 1)
+ {
+ mAmountItems--;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (event.getId() == "max")
+ {
+ mAmountItems = mMaxItems;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (event.getId() == "sell" && mAmountItems > 0
+ && mAmountItems <= mMaxItems)
+ {
+ if (mNpcId != -1)
+ {
+ // Attempt sell
+ ShopItem *item = mShopItems->at(selectedItem);
+ int sellCount, itemIndex;
+ mPlayerMoney +=
+ mAmountItems * mShopItems->at(selectedItem)->getPrice();
+ mMaxItems -= mAmountItems;
+ while (mAmountItems > 0)
+ {
+ // This order is important, item->getCurrentInvIndex() would return
+ // the inventory index of the next Duplicate otherwise.
+ itemIndex = item->getCurrentInvIndex();
+ sellCount = item->sellCurrentDuplicate(mAmountItems);
+
+ // For Manaserv, the Item id is to be given as index.
+ if ((Net::getNetworkType() == ServerInfo::MANASERV))
+ itemIndex = item->getId();
+
+ Net::getNpcHandler()->sellItem(mNpcId, itemIndex, sellCount);
+ mAmountItems -= sellCount;
+ }
+
+ mPlayerMoney +=
+ mAmountItems * mShopItems->at(selectedItem)->getPrice();
+ mAmountItems = 1;
+ mSlider->setValue(0);
+
+ if (!mMaxItems)
+ {
+ // All were sold
+ mShopItemList->setSelected(-1);
+ delete mShopItems->at(selectedItem);
+ mShopItems->erase(selectedItem);
+
+ gcn::Rectangle scroll;
+ scroll.y = mShopItemList->getRowHeight() * (selectedItem + 1);
+ scroll.height = mShopItemList->getRowHeight();
+ mShopItemList->showPart(scroll);
+ }
+ }
+ else
+ {
+ ShopItem *item = mShopItems->at(selectedItem);
+
+ Net::getBuySellHandler()->sendSellRequest(mNick,
+ item, mAmountItems);
+
+ if (tradeWindow)
+ tradeWindow->addAutoItem(mNick, item, mAmountItems);
+ }
+ }
+}
+
+void SellDialog::valueChanged(const gcn::SelectionEvent &event _UNUSED_)
+{
+ // Reset amount of items and update labels
+ mAmountItems = 1;
+ mSlider->setValue(0);
+
+ updateButtonsAndLabels();
+ mSlider->gcn::Slider::setScale(1, mMaxItems);
+}
+
+void SellDialog::setMoney(int amount)
+{
+ mPlayerMoney = amount;
+ mShopItemList->setPlayersMoney(amount);
+}
+
+void SellDialog::updateButtonsAndLabels()
+{
+ int selectedItem = mShopItemList->getSelected();
+ int income = 0;
+
+ if (selectedItem > -1 && mShopItems->at(selectedItem))
+ {
+ mMaxItems = mShopItems->at(selectedItem)->getQuantity();
+ if (mAmountItems > mMaxItems)
+ mAmountItems = mMaxItems;
+
+ income = mAmountItems * mShopItems->at(selectedItem)->getPrice();
+ }
+ else
+ {
+ mMaxItems = 0;
+ mAmountItems = 0;
+ }
+
+ // Update Buttons and slider
+ mSellButton->setEnabled(mAmountItems > 0);
+ mDecreaseButton->setEnabled(mAmountItems > 1);
+ mIncreaseButton->setEnabled(mAmountItems < mMaxItems);
+ mSlider->setEnabled(mMaxItems > 1);
+
+ // Update the quantity and money labels
+ mQuantityLabel->setCaption(strprintf("%d / %d", mAmountItems, mMaxItems));
+ mMoneyLabel->setCaption(strprintf(_("Price: %s / Total: %s"),
+ Units::formatCurrency(income).c_str(),
+ Units::formatCurrency(mPlayerMoney + income).c_str()));
+}
+
+void SellDialog::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (visible)
+ {
+ if (mShopItemList)
+ mShopItemList->requestFocus();
+ }
+ else
+ {
+ scheduleDelete();
+ }
+}
+
+void SellDialog::closeAll()
+{
+ DialogList::iterator it = instances.begin();
+ DialogList::iterator it_end = instances.end();
+
+ for (; it != it_end; it++)
+ (*it)->close();
+}
diff --git a/src/gui/sell.h b/src/gui/sell.h
new file mode 100644
index 000000000..fd6245eb8
--- /dev/null
+++ b/src/gui/sell.h
@@ -0,0 +1,145 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SELL_H
+#define SELL_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#include <SDL_types.h>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Item;
+class ShopItems;
+class ShopListBox;
+
+/**
+ * The sell dialog.
+ *
+ * \ingroup Interface
+ */
+class SellDialog : public Window, gcn::ActionListener, gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ SellDialog(int npcId);
+
+ /**
+ * Constructor.
+ */
+ SellDialog(std::string nick);
+
+ /**
+ * Destructor
+ */
+ virtual ~SellDialog();
+
+ void init();
+
+ /**
+ * Resets the dialog, clearing inventory.
+ */
+ void reset();
+
+ /**
+ * Adds an item to the inventory.
+ */
+ void addItem(const Item *item, int price);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Updates labels according to selected item.
+ *
+ * @see SelectionListener::selectionChanged
+ */
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ /**
+ * Gives Player's Money amount
+ */
+ void setMoney(int amount);
+
+ /**
+ * Sets the visibility of this window.
+ */
+ void setVisible(bool visible);
+
+ void addItem(int id, int amount, int price);
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive()
+ { return !instances.empty(); }
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+ private:
+ typedef std::list<SellDialog*> DialogList;
+ static DialogList instances;
+
+ /**
+ * Updates the state of buttons and labels.
+ */
+ void updateButtonsAndLabels();
+
+ int mNpcId;
+
+ gcn::Button *mSellButton;
+ gcn::Button *mQuitButton;
+ gcn::Button *mAddMaxButton;
+ gcn::Button *mIncreaseButton;
+ gcn::Button *mDecreaseButton;
+ ShopListBox *mShopItemList;
+ gcn::ScrollArea *mScrollArea;
+ gcn::Label *mMoneyLabel;
+ gcn::Label *mQuantityLabel;
+ gcn::Slider *mSlider;
+
+ ShopItems *mShopItems;
+ int mPlayerMoney;
+
+ int mMaxItems;
+ int mAmountItems;
+
+ std::string mNick;
+};
+
+#endif
diff --git a/src/gui/serverdialog.cpp b/src/gui/serverdialog.cpp
new file mode 100644
index 000000000..c7e1d0f94
--- /dev/null
+++ b/src/gui/serverdialog.cpp
@@ -0,0 +1,768 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/serverdialog.h"
+
+#include "chatlog.h"
+#include "client.h"
+#include "configuration.h"
+#include "gui.h"
+#include "log.h"
+#include "main.h"
+
+#include "gui/login.h"
+#include "gui/okdialog.h"
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+#include "utils/xml.h"
+#include "widgets/dropdown.h"
+
+#include <guichan/font.hpp>
+
+#include <cstdlib>
+#include <iostream>
+#include <string>
+
+static const int MAX_SERVERLIST = 15;
+
+static std::string serverTypeToString(ServerInfo::Type type)
+{
+ switch (type)
+ {
+ case ServerInfo::TMWATHENA:
+ return "TmwAthena";
+ case ServerInfo::MANASERV:
+ return "ManaServ";
+ default:
+ return "";
+ }
+}
+
+static unsigned short defaultPortForServerType(ServerInfo::Type type)
+{
+ switch (type)
+ {
+ default:
+ case ServerInfo::TMWATHENA:
+ return 6901;
+ case ServerInfo::MANASERV:
+ return 9601;
+ }
+}
+
+ServersListModel::ServersListModel(ServerInfos *servers, ServerDialog *parent):
+ mServers(servers),
+ mVersionStrings(servers->size(), VersionString(0, "")),
+ mParent(parent)
+{
+}
+
+int ServersListModel::getNumberOfElements()
+{
+ MutexLocker lock = mParent->lock();
+ return static_cast<int>(mServers->size());
+}
+
+std::string ServersListModel::getElementAt(int elementIndex)
+{
+ MutexLocker lock = mParent->lock();
+ const ServerInfo &server = mServers->at(elementIndex);
+ std::string myServer;
+ myServer += server.hostname;
+ myServer += ":";
+ myServer += toString(server.port);
+ return myServer;
+}
+
+void ServersListModel::setVersionString(int index, const std::string &version)
+{
+ if (version.empty())
+ {
+ mVersionStrings[index] = VersionString(0, "");
+ }
+ else
+ {
+ int width = gui->getFont()->getWidth(version);
+ mVersionStrings[index] = VersionString(width, version);
+ }
+}
+
+std::string TypeListModel::getElementAt(int elementIndex)
+{
+ if (elementIndex == 0)
+ return "TmwAthena";
+ else if (elementIndex == 1)
+ return "ManaServ";
+ else
+ return "Unknown";
+}
+
+class ServersListBox : public ListBox
+{
+public:
+ ServersListBox(ServersListModel *model):
+ ListBox(model)
+ {
+ }
+
+ void draw(gcn::Graphics *graphics)
+ {
+ if (!mListModel)
+ return;
+
+ ServersListModel *model = static_cast<ServersListModel*>(mListModel);
+
+ updateAlpha();
+
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT,
+ static_cast<int>(mAlpha * 255.0f)));
+ graphics->setFont(getFont());
+
+ const int height = getRowHeight();
+ const gcn::Color unsupported =
+ Theme::getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED,
+ static_cast<int>(mAlpha * 255.0f));
+
+ // Draw filled rectangle around the selected list element
+ if (mSelected >= 0)
+ {
+ graphics->fillRectangle(gcn::Rectangle(0, height * mSelected,
+ getWidth(), height));
+ }
+
+ // Draw the list elements
+ for (int i = 0, y = 0; i < model->getNumberOfElements();
+ ++i, y += height)
+ {
+ ServerInfo info = model->getServer(i);
+
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
+ int top;
+
+ if (!info.name.empty())
+ {
+ graphics->setFont(boldFont);
+ graphics->drawText(info.name, 2, y);
+ top = y + height / 2;
+ }
+ else
+ {
+ top = y + height / 4;
+ }
+
+ graphics->setFont(getFont());
+
+ graphics->drawText(model->getElementAt(i), 2, top);
+
+ if (info.version.first > 0)
+ {
+ graphics->setColor(unsupported);
+
+ graphics->drawText(info.version.second,
+ getWidth() - info.version.first - 2, top);
+ }
+ }
+ }
+
+ unsigned int getRowHeight() const
+ {
+ return 2 * getFont()->getHeight();
+ }
+};
+
+
+ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir):
+ Window(_("Choose Your Server")),
+ mDir(dir),
+// mDownloadStatus(DOWNLOADING_PREPARING),
+ mDownloadStatus(DOWNLOADING_UNKNOWN),
+ mDownload(0),
+ mDownloadProgress(-1.0f),
+ mServers(ServerInfos()),
+ mServerInfo(serverInfo)
+{
+ if (isSafeMode)
+ setCaption("Choose Your Server *** SAFE MODE ***");
+
+ setWindowName("ServerDialog");
+
+ Label *serverLabel = new Label(_("Server:"));
+ Label *portLabel = new Label(_("Port:"));
+ Label *typeLabel = new Label(_("Server type:"));
+ mServerNameField = new TextField(mServerInfo->hostname);
+ mPortField = new TextField(toString(mServerInfo->port));
+
+ loadCustomServers();
+
+ mServersListModel = new ServersListModel(&mServers, this);
+
+ mServersList = new ServersListBox(mServersListModel);
+ mServersList->addMouseListener(this);
+
+ ScrollArea *usedScroll = new ScrollArea(mServersList);
+ usedScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mTypeListModel = new TypeListModel();
+ mTypeField = new DropDown(mTypeListModel);
+ mTypeField->setSelected((serverInfo->type == ServerInfo::MANASERV) ?
+ 1 : 0);
+
+ mDescription = new Label(std::string());
+
+ mQuitButton = new Button(_("Quit"), "quit", this);
+ mLoadButton = new Button(_("Load"), "load", this);
+ mConnectButton = new Button(_("Connect"), "connect", this);
+ mManualEntryButton = new Button(_("Custom Server"), "addEntry", this);
+ mDeleteButton = new Button(_("Delete"), "remove", this);
+
+ mServerNameField->setActionEventId("connect");
+ mPortField->setActionEventId("connect");
+
+ mServerNameField->addActionListener(this);
+ mPortField->addActionListener(this);
+ mManualEntryButton->addActionListener(this);
+ mServersList->addSelectionListener(this);
+ usedScroll->setVerticalScrollAmount(0);
+
+ place(0, 0, serverLabel);
+ place(1, 0, mServerNameField, 5).setPadding(3);
+ place(0, 1, portLabel);
+ place(1, 1, mPortField, 5).setPadding(3);
+ place(0, 2, typeLabel);
+ place(1, 2, mTypeField, 5).setPadding(3);
+ place(0, 3, usedScroll, 6, 5).setPadding(3);
+ place(0, 8, mDescription, 6);
+ place(0, 9, mManualEntryButton);
+ place(1, 9, mDeleteButton);
+ place(2, 9, mLoadButton);
+ place(4, 9, mQuitButton);
+ place(5, 9, mConnectButton);
+
+ // Make sure the list has enough height
+ getLayout().setRowHeight(3, 80);
+
+/*
+ reflowLayout(400, 300);
+ setDefaultSize(400, 300, ImageRect::CENTER);
+*/
+ // Do this manually instead of calling reflowLayout so we can enforce a
+ // minimum width.
+ int width = 0, height = 0;
+ getLayout().reflow(width, height);
+ if (width < 400)
+ {
+ width = 400;
+ getLayout().reflow(width, height);
+ }
+
+ setContentSize(width, height);
+
+ setMinWidth(getWidth());
+ setMinHeight(getHeight());
+ setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER);
+
+ setResizable(true);
+ addKeyListener(this);
+
+ loadWindowState();
+
+ setFieldsReadOnly(true);
+ mServersList->setSelected(0); // Do this after for the Delete button
+ setVisible(true);
+
+ if (mServerNameField->getText().empty())
+ {
+ mServerNameField->requestFocus();
+ }
+ else
+ {
+ if (mPortField->getText().empty())
+ mPortField->requestFocus();
+ else
+ mConnectButton->requestFocus();
+ }
+
+ loadServers(false);
+
+ if (mServers.size() == 0)
+ downloadServerList();
+}
+
+ServerDialog::~ServerDialog()
+{
+ if (mDownload)
+ {
+ mDownload->cancel();
+ delete mDownload;
+ mDownload = 0;
+ }
+ delete mServersListModel;
+ mServersListModel = 0;
+ delete mTypeListModel;
+ mTypeListModel = 0;
+}
+
+void ServerDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "ok")
+ {
+ // Give focus back to the server dialog.
+ mServerNameField->requestFocus();
+ }
+ else if (event.getId() == "connect")
+ {
+ // Check login
+ if (mServerNameField->getText().empty()
+ || mPortField->getText().empty())
+ {
+ OkDialog *dlg = new OkDialog(_("Error"),
+ _("Please type both the address and the port of a server."));
+ dlg->addActionListener(this);
+ }
+ else
+ {
+ if (mDownload)
+ mDownload->cancel();
+
+ mQuitButton->setEnabled(false);
+ mConnectButton->setEnabled(false);
+ mLoadButton->setEnabled(false);
+
+ mServerInfo->hostname = mServerNameField->getText();
+ mServerInfo->port = static_cast<short>(
+ atoi(mPortField->getText().c_str()));
+
+ switch (mTypeField->getSelected())
+ {
+ case 0:
+ mServerInfo->type = ServerInfo::TMWATHENA;
+ break;
+ case 1:
+ mServerInfo->type = ServerInfo::MANASERV;
+ break;
+ default:
+ mServerInfo->type = ServerInfo::UNKNOWN;
+ }
+
+ // Save the selected server
+ mServerInfo->save = true;
+
+ if (chatLogger)
+ chatLogger->setServerName(mServerInfo->hostname);
+
+ saveCustomServers(*mServerInfo);
+
+ if (!LoginDialog::savedPasswordKey.empty())
+ {
+ if (mServerInfo->hostname != LoginDialog::savedPasswordKey)
+ LoginDialog::savedPassword = "";
+ }
+
+ Client::setState(STATE_CONNECT_SERVER);
+ }
+ }
+ else if (event.getId() == "quit")
+ {
+ if (mDownload)
+ mDownload->cancel();
+ Client::setState(STATE_FORCE_QUIT);
+ }
+ else if (event.getId() == "load")
+ {
+ downloadServerList();
+ }
+ else if (event.getId() == "addEntry")
+ {
+ setFieldsReadOnly(false);
+ }
+ else if (event.getId() == "remove")
+ {
+ int index = mServersList->getSelected();
+ mServersList->setSelected(0);
+ mServers.erase(mServers.begin() + index);
+
+ saveCustomServers();
+ }
+}
+
+void ServerDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ESCAPE)
+ Client::setState(STATE_EXIT);
+ else if (key.getValue() == Key::ENTER)
+ action(gcn::ActionEvent(NULL, mConnectButton->getActionEventId()));
+}
+
+void ServerDialog::valueChanged(const gcn::SelectionEvent &)
+{
+ const int index = mServersList->getSelected();
+ if (index == -1)
+ {
+ mDeleteButton->setEnabled(false);
+ return;
+ }
+
+ // Update the server and post fields according to the new selection
+ const ServerInfo &myServer = mServersListModel->getServer(index);
+ mDescription->setCaption(myServer.description);
+ mServerNameField->setText(myServer.hostname);
+ mPortField->setText(toString(myServer.port));
+ switch (myServer.type)
+ {
+ case ServerInfo::TMWATHENA:
+ case ServerInfo::UNKNOWN:
+ default:
+ mTypeField->setSelected(0);
+ break;
+ case ServerInfo::MANASERV:
+ mTypeField->setSelected(1);
+ break;
+ }
+ setFieldsReadOnly(true);
+
+ mDeleteButton->setEnabled(myServer.save);
+}
+
+void ServerDialog::mouseClicked(gcn::MouseEvent &mouseEvent)
+{
+ if (mouseEvent.getClickCount() == 2 &&
+ mouseEvent.getSource() == mServersList)
+ {
+ action(gcn::ActionEvent(mConnectButton,
+ mConnectButton->getActionEventId()));
+ }
+}
+
+void ServerDialog::logic()
+{
+ {
+ MutexLocker lock(&mMutex);
+ if (mDownloadStatus == DOWNLOADING_COMPLETE)
+ {
+ mDownloadStatus = DOWNLOADING_OVER;
+
+ mDescription->setCaption(std::string());
+ }
+ else if (mDownloadStatus == DOWNLOADING_IN_PROGRESS)
+ {
+ mDescription->setCaption(strprintf(_("Downloading server list..."
+ "%2.2f%%"),
+ mDownloadProgress * 100));
+ }
+ else if (mDownloadStatus == DOWNLOADING_IDLE)
+ {
+ mDescription->setCaption(_("Waiting for server..."));
+ }
+ else if (mDownloadStatus == DOWNLOADING_PREPARING)
+ {
+ mDescription->setCaption(_("Preparing download"));
+ }
+ else if (mDownloadStatus == DOWNLOADING_ERROR)
+ {
+ mDescription->setCaption(_("Error retreiving server list!"));
+ }
+ }
+
+ Window::logic();
+}
+
+void ServerDialog::setFieldsReadOnly(bool readOnly)
+{
+ if (!readOnly)
+ {
+ mDescription->setCaption(std::string());
+ mServersList->setSelected(-1);
+
+ mServerNameField->setText(std::string());
+ mPortField->setText(std::string("6901"));
+
+ mServerNameField->requestFocus();
+ }
+
+ mManualEntryButton->setEnabled(readOnly);
+ mDeleteButton->setEnabled(false);
+ mLoadButton->setEnabled(readOnly);
+ mDescription->setVisible(readOnly);
+
+ mServerNameField->setEnabled(!readOnly);
+ mPortField->setEnabled(!readOnly);
+ mTypeField->setEnabled(!readOnly);
+}
+
+void ServerDialog::downloadServerList()
+{
+ // Try to load the configuration value for the onlineServerList
+ std::string listFile = branding.getStringValue("onlineServerList");
+
+ if (listFile.empty())
+ listFile = config.getStringValue("onlineServerList");
+
+ // Fall back to manasource.org when neither branding nor config set it
+ if (listFile.empty())
+ listFile = "http://manasource.org/serverlist.xml";
+
+ mDownload = new Net::Download(this, listFile, &downloadUpdate);
+ mDownload->setFile(mDir + "/serverlist.xml");
+ mDownload->start();
+}
+
+void ServerDialog::loadServers(bool addNew)
+{
+ XML::Document doc(mDir + "/serverlist.xml", false);
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "serverlist"))
+ {
+ logger->log1("Error loading server list!");
+ return;
+ }
+
+ int version = XML::getProperty(rootNode, "version", 0);
+ if (version != 1)
+ {
+ logger->log("Error: unsupported online server list version: %d",
+ version);
+ return;
+ }
+
+ for_each_xml_child_node(serverNode, rootNode)
+ {
+ if (!xmlStrEqual(serverNode->name, BAD_CAST "server"))
+ continue;
+
+ ServerInfo server;
+
+ std::string type = XML::getProperty(serverNode, "type", "unknown");
+
+ server.type = ServerInfo::parseType(type);
+
+ // Ignore unknown server types
+ if (server.type == ServerInfo::UNKNOWN)
+ {
+ logger->log("Ignoring server entry with unknown type: %s",
+ type.c_str());
+ continue;
+ }
+
+ server.name = XML::getProperty(serverNode, "name", std::string());
+
+ std::string version = XML::getProperty(serverNode, "minimumVersion",
+ std::string());
+
+ bool meetsMinimumVersion = (compareStrI(version, PACKAGE_VERSION)
+ <= 0);
+
+ // For display in the list
+ if (meetsMinimumVersion)
+ version.clear();
+ else if (version.empty())
+ version = _("requires a newer version");
+ else
+ version = strprintf(_("requires v%s"), version.c_str());
+
+ for_each_xml_child_node(subNode, serverNode)
+ {
+ if (xmlStrEqual(subNode->name, BAD_CAST "connection"))
+ {
+ server.hostname = XML::getProperty(subNode, "hostname", "");
+ server.port = static_cast<short unsigned>(
+ XML::getProperty(subNode, "port", 0));
+
+ if (server.port == 0)
+ {
+ // If no port is given, use the default for the given type
+ server.port = defaultPortForServerType(server.type);
+ }
+ }
+ else if (xmlStrEqual(subNode->name, BAD_CAST "description"))
+ {
+ server.description =
+ (const char*)subNode->xmlChildrenNode->content;
+ }
+ }
+
+ server.version.first = gui->getFont()->getWidth(version);
+ server.version.second = version;
+
+ MutexLocker lock(&mMutex);
+ // Add the server to the local list if it's not already present
+ bool found = false;
+ for (unsigned int i = 0; i < mServers.size(); i++)
+ {
+ if (mServers[i] == server)
+ {
+ // Use the name listed in the server list
+ mServers[i].name = server.name;
+ mServers[i].version = server.version;
+ mServersListModel->setVersionString(i, version);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found && addNew)
+ mServers.push_back(server);
+ }
+}
+
+void ServerDialog::loadCustomServers()
+{
+ for (int i = 0; i < MAX_SERVERLIST; ++i)
+ {
+ const std::string index = toString(i);
+ const std::string nameKey = "MostUsedServerName" + index;
+ const std::string typeKey = "MostUsedServerType" + index;
+ const std::string portKey = "MostUsedServerPort" + index;
+
+ ServerInfo server;
+ server.hostname = config.getValue(nameKey, "");
+ server.type = ServerInfo::parseType(config.getValue(typeKey, ""));
+
+ const int defaultPort = defaultPortForServerType(server.type);
+ server.port = static_cast<unsigned short>(
+ config.getValue(portKey, defaultPort));
+
+ // Stop on the first invalid server
+ if (!server.isValid())
+ break;
+
+ server.save = true;
+ mServers.push_back(server);
+ }
+}
+
+void ServerDialog::saveCustomServers(const ServerInfo &currentServer)
+{
+ // Make sure the current server is mentioned first
+ if (currentServer.isValid())
+ {
+ ServerInfos::iterator i, i_end = mServers.end();
+ for (i = mServers.begin(); i != i_end; ++i)
+ {
+ if (*i == currentServer)
+ {
+ mServers.erase(i);
+ break;
+ }
+ }
+ mServers.insert(mServers.begin(), currentServer);
+ }
+
+ int savedServerCount = 0;
+
+ for (unsigned i = 0;
+ i < mServers.size() && savedServerCount < MAX_SERVERLIST; ++i)
+ {
+ const ServerInfo &server = mServers.at(i);
+
+ // Only save servers that were loaded from settings
+ if (!(server.save && server.isValid()))
+ continue;
+
+ const std::string index = toString(savedServerCount);
+ const std::string nameKey = "MostUsedServerName" + index;
+ const std::string typeKey = "MostUsedServerType" + index;
+ const std::string portKey = "MostUsedServerPort" + index;
+
+ config.setValue(nameKey, toString(server.hostname));
+ config.setValue(typeKey, serverTypeToString(server.type));
+ config.setValue(portKey, toString(server.port));
+ ++savedServerCount;
+ }
+
+ // Insert an invalid entry at the end to make the loading stop there
+ if (savedServerCount < MAX_SERVERLIST)
+ config.setValue("MostUsedServerName" + toString(savedServerCount), "");
+}
+
+int ServerDialog::downloadUpdate(void *ptr, DownloadStatus status,
+ size_t total, size_t remaining)
+{
+ if (status == DOWNLOAD_STATUS_CANCELLED)
+ return -1;
+
+ ServerDialog *sd = reinterpret_cast<ServerDialog*>(ptr);
+ bool finished = false;
+
+ if (!sd->mDownload)
+ return -1;
+
+ if (status == DOWNLOAD_STATUS_COMPLETE)
+ {
+ finished = true;
+ }
+ else if (status < 0)
+ {
+ logger->log("Error retreiving server list: %s\n",
+ sd->mDownload->getError());
+ sd->mDownloadStatus = DOWNLOADING_ERROR;
+ }
+ else
+ {
+ float progress = static_cast<float>(remaining);
+ if (total)
+ progress /= static_cast<float>(total);
+
+ if (progress != progress)
+ {
+ progress = 0.0f; // check for NaN
+ }
+ else if (progress < 0.0f)
+ {
+ progress = 0.0f; // no idea how this could ever happen,
+ // but why not check for it anyway.
+ }
+ else if (progress > 1.0f)
+ {
+ progress = 1.0f;
+ }
+
+ MutexLocker lock(&sd->mMutex);
+ sd->mDownloadStatus = DOWNLOADING_IN_PROGRESS;
+ sd->mDownloadProgress = progress;
+ }
+
+ if (finished)
+ {
+ sd->loadServers();
+
+ MutexLocker lock(&sd->mMutex);
+ sd->mDownloadStatus = DOWNLOADING_COMPLETE;
+ }
+
+ return 0;
+}
diff --git a/src/gui/serverdialog.h b/src/gui/serverdialog.h
new file mode 100644
index 000000000..b9a67e246
--- /dev/null
+++ b/src/gui/serverdialog.h
@@ -0,0 +1,204 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SERVERDIALOG_H
+#define SERVERDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include "net/download.h"
+#include "net/serverinfo.h"
+
+#include "utils/mutex.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/listmodel.hpp>
+#include <guichan/mouselistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#include <string>
+#include <vector>
+
+class Button;
+class Label;
+class ListBox;
+class ServerDialog;
+class TextField;
+class DropDown;
+
+/**
+ * Server and Port List Model
+ */
+class ServersListModel : public gcn::ListModel
+{
+ public:
+ typedef std::pair<int, std::string> VersionString;
+
+ ServersListModel(ServerInfos *servers, ServerDialog *parent);
+
+ /**
+ * Used to get number of line in the list
+ */
+ int getNumberOfElements();
+
+ /**
+ * Used to get an element from the list
+ */
+ std::string getElementAt(int elementIndex);
+
+ /**
+ * Used to get the corresponding Server struct
+ */
+ const ServerInfo &getServer(int elementIndex) const
+ { return mServers->at(elementIndex); }
+
+ void setVersionString(int index, const std::string &version);
+
+ private:
+ typedef std::vector<VersionString> VersionStrings;
+
+ ServerInfos *mServers;
+ VersionStrings mVersionStrings;
+ ServerDialog *mParent;
+};
+
+/**
+ * Server Type List Model
+ */
+class TypeListModel : public gcn::ListModel
+{
+ public:
+ TypeListModel() {}
+
+ /**
+ * Used to get number of line in the list
+ */
+ int getNumberOfElements()
+ { return 2; }
+
+ /**
+ * Used to get an element from the list
+ */
+ std::string getElementAt(int elementIndex);
+};
+
+
+/**
+ * The server choice dialog.
+ *
+ * \ingroup Interface
+ */
+class ServerDialog : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener,
+ public gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ ServerDialog(ServerInfo *serverInfo, const std::string &dir);
+
+ /**
+ * Destructor
+ */
+ ~ServerDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ /**
+ * Called when the selected value changed in the servers list box.
+ */
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ void mouseClicked(gcn::MouseEvent &mouseEvent);
+
+ void logic();
+
+ protected:
+ friend class ServersListModel;
+ MutexLocker lock()
+ { return MutexLocker(&mMutex); }
+
+ private:
+ /**
+ * Called to load a list of available server from an online xml file.
+ */
+ void downloadServerList();
+ void loadServers(bool addNew = true);
+
+ void loadCustomServers();
+ void saveCustomServers(const ServerInfo &currentServer = ServerInfo());
+
+ static int downloadUpdate(void *ptr, DownloadStatus status,
+ size_t total, size_t remaining);
+
+ void setFieldsReadOnly(bool readOnly);
+
+ TextField *mServerNameField;
+ TextField *mPortField;
+ Label *mDescription;
+ Button *mQuitButton;
+ Button *mConnectButton;
+ Button *mManualEntryButton;
+ Button *mDeleteButton;
+ Button *mLoadButton;
+
+ ListBox *mServersList;
+ ServersListModel *mServersListModel;
+
+ DropDown *mTypeField;
+ TypeListModel *mTypeListModel;
+
+ const std::string &mDir;
+
+ enum ServerDialogDownloadStatus
+ {
+ DOWNLOADING_UNKNOWN = 0,
+ DOWNLOADING_ERROR,
+ DOWNLOADING_PREPARING,
+ DOWNLOADING_IDLE,
+ DOWNLOADING_IN_PROGRESS,
+ DOWNLOADING_COMPLETE,
+ DOWNLOADING_OVER
+ };
+
+ /** Status of the current download. */
+ ServerDialogDownloadStatus mDownloadStatus;
+
+ Net::Download *mDownload;
+
+ Mutex mMutex;
+ float mDownloadProgress;
+
+ ServerInfos mServers;
+ ServerInfo *mServerInfo;
+};
+
+#endif
diff --git a/src/gui/setup.cpp b/src/gui/setup.cpp
new file mode 100644
index 000000000..8206b0e7e
--- /dev/null
+++ b/src/gui/setup.cpp
@@ -0,0 +1,178 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "setup.h"
+
+#include "configuration.h"
+#include "main.h"
+
+#include "gui/setup_audio.h"
+#include "gui/setup_colors.h"
+#include "gui/setup_joystick.h"
+#include "gui/setup_other.h"
+#include "gui/setup_theme.h"
+#include "gui/setup_keyboard.h"
+#include "gui/setup_players.h"
+#include "gui/setup_video.h"
+#include "gui/setup_chat.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/tabbedarea.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+extern Window *statusWindow;
+
+Setup::Setup():
+ Window(_("Setup"))
+{
+ setCloseButton(true);
+ setResizable(true);
+
+ int width = 620;
+ int height = 450;
+
+ if (config.getIntValue("screenwidth") >= 730)
+ width += 100;
+
+ setContentSize(width, height);
+ //setMaxHeight(height);
+
+ static const char *buttonNames[] =
+ {
+ N_("Apply"),
+ N_("Cancel"),
+ N_("Store"),
+ N_("Reset Windows"),
+ 0
+ };
+ int x = width;
+ for (const char **curBtn = buttonNames; *curBtn; ++curBtn)
+ {
+ Button *btn = new Button(gettext(*curBtn), *curBtn, this);
+ x -= btn->getWidth() + 5;
+ btn->setPosition(x, height - btn->getHeight() - 5);
+ add(btn);
+
+ // Store this button, as it needs to be enabled/disabled
+ if (!strcmp(*curBtn, "Reset Windows"))
+ mResetWindows = btn;
+ }
+
+ mPanel = new TabbedArea;
+ mPanel->setDimension(gcn::Rectangle(5, 5, width - 10, height - 40));
+
+ mTabs.push_back(new Setup_Video);
+ mTabs.push_back(new Setup_Audio);
+ mTabs.push_back(new Setup_Joystick);
+ mTabs.push_back(new Setup_Keyboard);
+ mTabs.push_back(new Setup_Colors);
+ mTabs.push_back(new Setup_Chat);
+ mTabs.push_back(new Setup_Players);
+ mTabs.push_back(new Setup_Theme);
+ mTabs.push_back(new Setup_Other);
+
+ for (std::list<SetupTab*>::iterator i = mTabs.begin(), i_end = mTabs.end();
+ i != i_end; ++i)
+ {
+ SetupTab *tab = *i;
+ mPanel->addTab(tab->getName(), tab);
+ }
+
+ add(mPanel);
+
+ Label *version = new Label(FULL_VERSION);
+// version->setPosition(9, height - version->getHeight() - 9);
+ if (mResetWindows)
+ {
+ version->setPosition(9,
+ height - version->getHeight() - mResetWindows->getHeight() - 9);
+ }
+ else
+ {
+ version->setPosition(9, height - version->getHeight() - 30);
+ }
+ add(version);
+
+ center();
+
+ setInGame(false);
+}
+
+Setup::~Setup()
+{
+ delete_all(mTabs);
+}
+
+void Setup::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "Apply")
+ {
+ setVisible(false);
+ for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::apply));
+ }
+ else if (event.getId() == "Cancel")
+ {
+ setVisible(false);
+ for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::cancel));
+ }
+ else if (event.getId() == "Store")
+ {
+ config.write();
+ serverConfig.write();
+ }
+ else if (event.getId() == "Reset Windows")
+ {
+ // Bail out if this action happens to be activated before the windows
+ // are created (though it should be disabled then)
+ if (!statusWindow)
+ return;
+
+ for (std::list<Window*>::iterator it = mWindowsToReset.begin();
+ it != mWindowsToReset.end(); it++)
+ {
+ (*it)->resetToDefaultSize();
+ }
+ }
+}
+
+void Setup::setInGame(bool inGame)
+{
+ mResetWindows->setEnabled(inGame);
+}
+
+void Setup::externalUpdate()
+{
+ for (std::list<SetupTab*>::iterator it = mTabs.begin();
+ it != mTabs.end(); it++)
+ {
+ (*it)->externalUpdated();
+ }
+}
+
+void Setup::registerWindowForReset(Window *window)
+{
+ mWindowsToReset.push_back(window);
+}
+
+Setup *setupWindow;
diff --git a/src/gui/setup.h b/src/gui/setup.h
new file mode 100644
index 000000000..1f10a2a0d
--- /dev/null
+++ b/src/gui/setup.h
@@ -0,0 +1,82 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SETUP_H
+#define SETUP_H
+
+#include "gui/widgets/tabbedarea.h"
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <list>
+
+class SetupTab;
+
+/**
+ * The setup dialog. Displays several tabs for configuring different aspects
+ * of the game.
+ *
+ * @see Setup_Audio
+ * @see Setup_Colors
+ * @see Setup_Joystick
+ * @see Setup_Keyboard
+ * @see Setup_Players
+ * @see Setup_Video
+ *
+ * \ingroup GUI
+ */
+class Setup : public Window, public gcn::ActionListener
+{
+ public:
+ Setup();
+ ~Setup();
+
+ /**
+ * Event handling method.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Enables the reset button when in game.
+ */
+ void setInGame(bool inGame);
+
+ void externalUpdate();
+
+ void registerWindowForReset(Window *window);
+
+ void clearWindowsForReset()
+ { mWindowsToReset.clear(); }
+
+ private:
+ std::list<SetupTab*> mTabs;
+ std::list<Window*> mWindowsToReset;
+ gcn::Button *mResetWindows;
+ TabbedArea *mPanel;
+};
+
+extern Setup* setupWindow;
+
+#endif
diff --git a/src/gui/setup_audio.cpp b/src/gui/setup_audio.cpp
new file mode 100644
index 000000000..66a7a4e88
--- /dev/null
+++ b/src/gui/setup_audio.cpp
@@ -0,0 +1,179 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/setup_audio.h"
+
+#include "configuration.h"
+#include "log.h"
+#include "sound.h"
+
+#include "gui/okdialog.h"
+
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/slider.h"
+
+#include "utils/gettext.h"
+
+Setup_Audio::Setup_Audio():
+ mMusicVolume(config.getIntValue("musicVolume")),
+ mSfxVolume(config.getIntValue("sfxVolume")),
+ mAudioEnabled(config.getBoolValue("sound")),
+ mGameSoundEnabled(config.getBoolValue("playBattleSound")),
+ mGuiSoundEnabled(config.getBoolValue("playGuiSound")),
+ mMusicEnabled(config.getBoolValue("playMusic")),
+ mMumbleEnabled(config.getBoolValue("enableMumble")),
+ mDownloadEnabled(config.getBoolValue("download-music")),
+ mAudioCheckBox(new CheckBox(_("Enable Audio"), mAudioEnabled)),
+ mGameSoundCheckBox(new CheckBox(_("Enable game sfx"), mGameSoundEnabled)),
+ mGuiSoundCheckBox(new CheckBox(_("Enable gui sfx"), mGuiSoundEnabled)),
+ mMusicCheckBox(new CheckBox(_("Enable music"), mMusicEnabled)),
+ mMumbleCheckBox(new CheckBox(_("Enable mumble voice chat"),
+ mMumbleEnabled)),
+ mDownloadMusicCheckBox(new CheckBox(_("Download music"),
+ mDownloadEnabled)),
+ mSfxSlider(new Slider(0, sound.getMaxVolume())),
+ mMusicSlider(new Slider(0, sound.getMaxVolume()))
+{
+ setName(_("Audio"));
+ setDimension(gcn::Rectangle(0, 0, 250, 200));
+
+ gcn::Label *sfxLabel = new Label(_("Sfx volume"));
+ gcn::Label *musicLabel = new Label(_("Music volume"));
+
+ mSfxSlider->setActionEventId("sfx");
+ mMusicSlider->setActionEventId("music");
+
+ mSfxSlider->addActionListener(this);
+ mMusicSlider->addActionListener(this);
+
+ mAudioCheckBox->setPosition(10, 10);
+
+ mSfxSlider->setValue(mSfxVolume);
+ mMusicSlider->setValue(mMusicVolume);
+
+ mSfxSlider->setWidth(90);
+ mMusicSlider->setWidth(90);
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mAudioCheckBox);
+ place(0, 1, mMusicCheckBox);
+ place(0, 2, mGameSoundCheckBox);
+ place(0, 3, mGuiSoundCheckBox);
+ place(0, 4, mSfxSlider);
+ place(1, 4, sfxLabel);
+ place(0, 5, mMusicSlider);
+ place(1, 5, musicLabel);
+ place(0, 6, mMumbleCheckBox);
+ place(0, 7, mDownloadMusicCheckBox);
+
+ setDimension(gcn::Rectangle(0, 0, 365, 280));
+}
+
+void Setup_Audio::apply()
+{
+ mAudioEnabled = mAudioCheckBox->isSelected();
+ mGameSoundEnabled = mGameSoundCheckBox->isSelected();
+ mGuiSoundEnabled = mGuiSoundCheckBox->isSelected();
+ mMusicEnabled = mMusicCheckBox->isSelected();
+ mMumbleEnabled = mMumbleCheckBox->isSelected();
+ mDownloadEnabled = mDownloadMusicCheckBox->isSelected();
+ mSfxVolume = config.getIntValue("sfxVolume");
+ mMusicVolume = config.getIntValue("musicVolume");
+
+ config.setValue("sound", mAudioEnabled);
+ config.setValue("playBattleSound", mGameSoundEnabled);
+ config.setValue("playGuiSound", mGuiSoundEnabled);
+ config.setValue("playMusic", mMusicEnabled);
+
+ config.setValue("enableMumble", mMumbleEnabled);
+
+ // Display a message if user has selected to download music,
+ // And if downloadmusic is not already enabled
+ if (mDownloadEnabled && !config.getBoolValue("download-music"))
+ {
+ new OkDialog(_("Notice"), _("You may have to restart your client "
+ "if you want to download new music"));
+ }
+ config.setValue("download-music", mDownloadEnabled);
+
+ if (mAudioEnabled)
+ {
+ try
+ {
+ sound.init();
+ }
+ catch (const char *err)
+ {
+ new OkDialog(_("Sound Engine"), err);
+ logger->log("Warning: %s", err);
+ }
+ }
+ else
+ {
+ sound.close();
+ }
+}
+
+void Setup_Audio::cancel()
+{
+ mAudioCheckBox->setSelected(mAudioEnabled);
+ mGameSoundCheckBox->setSelected(mGameSoundEnabled);
+ mGuiSoundCheckBox->setSelected(mGuiSoundEnabled);
+ mMusicCheckBox->setSelected(mMusicEnabled);
+ mMumbleCheckBox->setSelected(mMumbleEnabled);
+ mDownloadMusicCheckBox->setSelected(mDownloadEnabled);
+
+ sound.setSfxVolume(mSfxVolume);
+ mSfxSlider->setValue(mSfxVolume);
+
+ sound.setMusicVolume(mMusicVolume);
+ mMusicSlider->setValue(mMusicVolume);
+
+ config.setValue("sound", mAudioEnabled);
+ config.setValue("playBattleSound", mGameSoundEnabled);
+ config.setValue("playGuiSound", mGuiSoundEnabled);
+ config.setValue("playMusic", mMusicEnabled);
+ config.setValue("enableMumble", mMumbleEnabled);
+ config.setValue("download-music", mDownloadEnabled);
+ config.setValue("sfxVolume", mSfxVolume);
+ config.setValue("musicVolume", mMusicVolume);
+}
+
+void Setup_Audio::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "sfx")
+ {
+ config.setValueInt("sfxVolume",
+ static_cast<int>(mSfxSlider->getValue()));
+ sound.setSfxVolume(static_cast<int>(mSfxSlider->getValue()));
+ }
+ else if (event.getId() == "music")
+ {
+ config.setValueInt("musicVolume",
+ static_cast<int>(mMusicSlider->getValue()));
+ sound.setMusicVolume(static_cast<int>(mMusicSlider->getValue()));
+ }
+}
diff --git a/src/gui/setup_audio.h b/src/gui/setup_audio.h
new file mode 100644
index 000000000..c16c4fb23
--- /dev/null
+++ b/src/gui/setup_audio.h
@@ -0,0 +1,53 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_SETUP_AUDIO_H
+#define GUI_SETUP_AUDIO_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Setup_Audio : public SetupTab, public gcn::ActionListener
+{
+ public:
+ Setup_Audio();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ int mMusicVolume, mSfxVolume;
+ bool mAudioEnabled, mGameSoundEnabled, mGuiSoundEnabled;
+ bool mMusicEnabled, mMumbleEnabled;
+ bool mDownloadEnabled;
+
+ gcn::CheckBox *mAudioCheckBox, *mGameSoundCheckBox, *mGuiSoundCheckBox;
+ gcn::CheckBox *mMusicCheckBox, *mMumbleCheckBox;
+ gcn::CheckBox *mDownloadMusicCheckBox;
+ gcn::Slider *mSfxSlider, *mMusicSlider;
+};
+
+#endif
diff --git a/src/gui/setup_chat.cpp b/src/gui/setup_chat.cpp
new file mode 100644
index 000000000..1dba2ed85
--- /dev/null
+++ b/src/gui/setup_chat.cpp
@@ -0,0 +1,306 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/setup_chat.h"
+#include "gui/editdialog.h"
+#include "gui/chat.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/inttextfield.h"
+#include "gui/widgets/chattab.h"
+
+#include "configuration.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "utils/gettext.h"
+
+#define ACTION_REMOVE_COLORS "remove colors"
+#define ACTION_MAGIC_IN_DEBUG "magic in debug"
+#define ACTION_ALLOW_COMMANDS_IN_CHATTABS "allow commands"
+#define ACTION_SERVER_MSG_IN_DEBUG "server in debug"
+#define ACTION_SHOW_CHAT_COLORS "show chat colors"
+#define ACTION_MAX_CHAR_LIMIT "char limit"
+#define ACTION_EDIT_CHAR_LIMIT "edit char limit"
+#define ACTION_EDIT_CHAR_OK "edit char ok"
+#define ACTION_MAX_LINES_LIMIT "lines limit"
+#define ACTION_EDIT_LINES_LIMIT "edit lines limit"
+#define ACTION_EDIT_LINES_OK "edit lines ok"
+#define ACTION_CHAT_LOGGER "chat logger"
+#define ACTION_TRADE_TAB "trade tab"
+#define ACTION_HIDE_SHOP_MESSAGES "hide shop messages"
+#define ACTION_SHOW_CHAT_HISTORY "show chat history"
+#define ACTION_ENABLE_BATTLE_TAB "show battle tab"
+#define ACTION_SHOW_BATTLE_EVENTS "show battle events"
+
+Setup_Chat::Setup_Chat()
+{
+ setName(_("Chat"));
+
+ mRemoveColors = config.getBoolValue("removeColors");
+ mRemoveColorsCheckBox = new CheckBox(
+ _("Remove colors from received chat messages"),
+ mRemoveColors, this, ACTION_REMOVE_COLORS);
+
+ mMagicInDebug = config.getBoolValue("showMagicInDebug");
+ mMagicInDebugCheckBox = new CheckBox(_("Log magic messages in debug tab"),
+ mMagicInDebug, this, ACTION_MAGIC_IN_DEBUG);
+
+ mAllowCommandsInChatTabs = config.getBoolValue(
+ "allowCommandsInChatTabs");
+
+ mAllowCommandsInChatTabsCheckBox = new CheckBox(
+ _("Allow magic and GM commands in all chat tabs"),
+ mAllowCommandsInChatTabs, this, ACTION_ALLOW_COMMANDS_IN_CHATTABS);
+
+ mServerMsgInDebug = config.getBoolValue("serverMsgInDebug");
+ mServerMsgInDebugCheckBox = new CheckBox(
+ _("Show server messages in debug tab"),
+ mServerMsgInDebug, this, ACTION_SERVER_MSG_IN_DEBUG);
+
+ mEnableChatLogger = config.getBoolValue("enableChatLog");
+ mEnableChatLoggerCheckBox = new CheckBox(_("Enable chat Log"),
+ mEnableChatLogger, this, ACTION_CHAT_LOGGER);
+
+ mEnableTradeTab = config.getBoolValue("enableTradeTab");
+ mEnableTradeTabCheckBox = new CheckBox(_("Enable trade tab"),
+ mEnableTradeTab, this, ACTION_TRADE_TAB);
+
+ mHideShopMessages = config.getBoolValue("hideShopMessages");
+ mHideShopMessagesCheckBox = new CheckBox(_("Hide shop messages"),
+ mHideShopMessages, this, ACTION_HIDE_SHOP_MESSAGES);
+
+ mShowChatHistory = config.getBoolValue("showChatHistory");
+ mShowChatHistoryCheckBox = new CheckBox(_("Show chat history"),
+ mShowChatHistory, this, ACTION_SHOW_CHAT_HISTORY);
+
+ mEnableBattleTab = config.getBoolValue("enableBattleTab");
+ mEnableBattleTabCheckBox = new CheckBox(_("Enable battle tab"),
+ mEnableBattleTab, this, ACTION_ENABLE_BATTLE_TAB);
+
+ mShowBattleEvents = config.getBoolValue("showBattleEvents");
+ mShowBattleEventsCheckBox = new CheckBox(_("Show battle events"),
+ mShowBattleEvents, this, ACTION_SHOW_BATTLE_EVENTS);
+
+ mShowChatColors = config.getBoolValue("showChatColorsList");
+ mShowChatColorsCheckBox = new CheckBox(_("Show chat colors list"),
+ mShowChatColors, this, ACTION_SHOW_CHAT_COLORS);
+
+ mMaxCharButton = new Button(_("Edit"), ACTION_EDIT_CHAR_LIMIT, this);
+ int maxCharLimit = config.getIntValue("chatMaxCharLimit");
+ mMaxChar = (maxCharLimit != 0);
+ mMaxCharCheckBox = new CheckBox(_("Limit max chars in chat line"),
+ mMaxChar, this, ACTION_MAX_CHAR_LIMIT);
+
+ mMaxCharField = new IntTextField(maxCharLimit, 0, 500, mMaxChar, 20);
+
+ mMaxLinesButton = new Button(_("Edit"), ACTION_EDIT_LINES_LIMIT, this);
+ int maxLinesLimit = config.getIntValue("chatMaxLinesLimit");
+ mMaxLines = (maxLinesLimit != 0);
+
+ mMaxLinesCheckBox = new CheckBox(_("Limit max lines in chat"),
+ mMaxLines,
+ this, ACTION_MAX_LINES_LIMIT);
+
+ mMaxLinesField = new IntTextField(maxLinesLimit, 0, 500, mMaxLines, 20);
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mRemoveColorsCheckBox, 10);
+ place(0, 1, mMagicInDebugCheckBox, 10);
+ place(0, 2, mAllowCommandsInChatTabsCheckBox, 10);
+ place(0, 3, mServerMsgInDebugCheckBox, 10);
+ place(0, 4, mShowChatColorsCheckBox, 10);
+ place(0, 5, mMaxCharCheckBox, 6);
+ place(6, 5, mMaxCharField, 2);
+ place(8, 5, mMaxCharButton, 2);
+ place(0, 6, mEnableChatLoggerCheckBox, 10);
+ place(0, 7, mMaxLinesCheckBox, 6);
+ place(6, 7, mMaxLinesField, 2);
+ place(8, 7, mMaxLinesButton, 2);
+ place(0, 8, mEnableTradeTabCheckBox, 10);
+ place(0, 9, mHideShopMessagesCheckBox, 10);
+ place(0, 10, mShowChatHistoryCheckBox, 10);
+ place(0, 11, mEnableBattleTabCheckBox, 10);
+ place(0, 12, mShowBattleEventsCheckBox, 10);
+
+ place.getCell().matchColWidth(0, 0);
+ place = h.getPlacer(0, 1);
+
+ setDimension(gcn::Rectangle(0, 0, 500, 500));
+}
+
+void Setup_Chat::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == ACTION_REMOVE_COLORS)
+ {
+ mRemoveColors = mRemoveColorsCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_MAGIC_IN_DEBUG)
+ {
+ mMagicInDebug = mMagicInDebugCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_ALLOW_COMMANDS_IN_CHATTABS)
+ {
+ mAllowCommandsInChatTabs
+ = mAllowCommandsInChatTabsCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SERVER_MSG_IN_DEBUG)
+ {
+ mServerMsgInDebug = mServerMsgInDebugCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SHOW_CHAT_COLORS)
+ {
+ mShowChatColors = mShowChatColorsCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_MAX_CHAR_LIMIT)
+ {
+ mMaxChar = mMaxCharCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_EDIT_CHAR_LIMIT)
+ {
+ mEditDialog = new EditDialog("Limit max chars in chat line",
+ toString(mMaxCharField->getValue()),
+ ACTION_EDIT_CHAR_OK);
+ mEditDialog->addActionListener(this);
+ }
+ else if (event.getId() == ACTION_EDIT_CHAR_OK)
+ {
+ mMaxCharField->setValue(atoi(mEditDialog->getMsg().c_str()));
+ }
+ else if (event.getId() == ACTION_MAX_LINES_LIMIT)
+ {
+ mMaxLines = mMaxLinesCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_EDIT_LINES_LIMIT)
+ {
+ mEditDialog = new EditDialog("Limit max lines in chat",
+ toString(mMaxLinesField->getValue()),
+ ACTION_EDIT_LINES_OK);
+ mEditDialog->addActionListener(this);
+ }
+ else if (event.getId() == ACTION_EDIT_LINES_OK)
+ {
+ mMaxLinesField->setValue(atoi(mEditDialog->getMsg().c_str()));
+ }
+ else if (event.getId() == ACTION_CHAT_LOGGER)
+ {
+ mEnableChatLogger = mEnableChatLoggerCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_TRADE_TAB)
+ {
+ mEnableTradeTab = mEnableTradeTabCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_HIDE_SHOP_MESSAGES)
+ {
+ mHideShopMessages = mHideShopMessagesCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SHOW_CHAT_HISTORY)
+ {
+ mShowChatHistory = mShowChatHistoryCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_ENABLE_BATTLE_TAB)
+ {
+ mEnableBattleTab = mEnableBattleTabCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SHOW_BATTLE_EVENTS)
+ {
+ mShowBattleEvents = mShowBattleEventsCheckBox->isSelected();
+ }
+}
+
+void Setup_Chat::cancel()
+{
+ mRemoveColors = config.getBoolValue("removeColors");
+ mRemoveColorsCheckBox->setSelected(mRemoveColors);
+
+ mMagicInDebug = config.getBoolValue("showMagicInDebug");
+ mMagicInDebugCheckBox->setSelected(mMagicInDebug);
+
+ mAllowCommandsInChatTabs
+ = config.getBoolValue("allowCommandsInChatTabs");
+ mAllowCommandsInChatTabsCheckBox->setSelected(mAllowCommandsInChatTabs);
+
+ mServerMsgInDebug = config.getBoolValue("serverMsgInDebug");
+ mServerMsgInDebugCheckBox->setSelected(mServerMsgInDebug);
+
+ mShowChatColors = config.getBoolValue("showChatColorsList");
+ mShowChatColorsCheckBox->setSelected(mShowChatColors);
+
+ int maxCharLimit = config.getIntValue("chatMaxCharLimit");
+ mMaxChar = (maxCharLimit != 0);
+ mMaxCharCheckBox->setSelected(mMaxChar);
+ mMaxCharField->setValue(maxCharLimit);
+ mMaxCharField->setEnabled(mMaxChar);
+
+ int maxLinesLimit = config.getIntValue("chatMaxLinesLimit");
+ mMaxLines = (maxLinesLimit != 0);
+ mMaxLinesCheckBox->setSelected(mMaxLines);
+ mMaxLinesField->setValue(maxLinesLimit);
+ mMaxLinesField->setEnabled(mMaxLines);
+
+ mEnableChatLogger = config.getBoolValue("enableChatLog");
+ mEnableChatLoggerCheckBox->setSelected(mEnableChatLogger);
+
+ mEnableTradeTab = config.getBoolValue("enableTradeTab");
+ mEnableTradeTabCheckBox->setSelected(mEnableTradeTab);
+
+ mHideShopMessages = config.getBoolValue("hideShopMessages");
+ mHideShopMessagesCheckBox->setSelected(mHideShopMessages);
+
+ mShowChatHistory = config.getBoolValue("showChatHistory");
+ mShowChatHistoryCheckBox->setSelected(mShowChatHistory);
+
+ mEnableBattleTab = config.getBoolValue("enableBattleTab");
+ mEnableBattleTabCheckBox->setSelected(mEnableBattleTab);
+
+ mShowBattleEvents = config.getBoolValue("showBattleEvents");
+ mShowBattleEventsCheckBox->setSelected(mShowBattleEvents);
+}
+
+void Setup_Chat::apply()
+{
+ config.setValue("removeColors", mRemoveColors);
+ config.setValue("showMagicInDebug", mMagicInDebug);
+ config.setValue("allowCommandsInChatTabs", mAllowCommandsInChatTabs);
+ config.setValue("serverMsgInDebug", mServerMsgInDebug);
+ config.setValue("showChatColorsList", mShowChatColors);
+ if (mMaxChar)
+ config.setValue("chatMaxCharLimit", mMaxCharField->getValue());
+ else
+ config.setValue("chatMaxCharLimit", 0);
+ if (mMaxLines)
+ config.setValue("chatMaxLinesLimit", mMaxLinesField->getValue());
+ else
+ config.setValue("chatMaxLinesLimit", 0);
+ config.setValue("enableChatLog", mEnableChatLogger);
+ config.setValue("enableTradeTab", mEnableTradeTab);
+ config.setValue("hideShopMessages", mHideShopMessages);
+ config.setValue("showChatHistory", mShowChatHistory);
+ config.setValue("enableBattleTab", mEnableBattleTab);
+ config.setValue("showBattleEvents", mShowBattleEvents);
+}
diff --git a/src/gui/setup_chat.h b/src/gui/setup_chat.h
new file mode 100644
index 000000000..30a59895a
--- /dev/null
+++ b/src/gui/setup_chat.h
@@ -0,0 +1,92 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef GUI_SETUP_CHAT_H
+#define GUI_SETUP_CHAT_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+
+class IntTextField;
+class EditDialog;
+
+class Setup_Chat : public SetupTab, public gcn::ActionListener
+{
+ public:
+ Setup_Chat();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ gcn::CheckBox *mRemoveColorsCheckBox;
+ bool mRemoveColors;
+
+ gcn::CheckBox *mMagicInDebugCheckBox;
+ bool mMagicInDebug;
+
+ gcn::CheckBox *mAllowCommandsInChatTabsCheckBox;
+ bool mAllowCommandsInChatTabs;
+
+ gcn::CheckBox *mServerMsgInDebugCheckBox;
+ bool mServerMsgInDebug;
+
+ gcn::CheckBox *mShowChatColorsCheckBox;
+ bool mShowChatColors;
+
+ gcn::CheckBox *mMaxCharCheckBox;
+ IntTextField *mMaxCharField;
+ gcn::Button *mMaxCharButton;
+ bool mMaxChar;
+
+ gcn::CheckBox *mMaxLinesCheckBox;
+ IntTextField *mMaxLinesField;
+ gcn::Button *mMaxLinesButton;
+ bool mMaxLines;
+
+ gcn::CheckBox *mEnableChatLoggerCheckBox;
+ bool mEnableChatLogger;
+
+ gcn::CheckBox *mEnableTradeTabCheckBox;
+ bool mEnableTradeTab;
+
+ gcn::CheckBox *mHideShopMessagesCheckBox;
+ bool mHideShopMessages;
+
+ gcn::CheckBox *mShowChatHistoryCheckBox;
+ bool mShowChatHistory;
+
+ gcn::CheckBox *mEnableBattleTabCheckBox;
+ bool mEnableBattleTab;
+
+ gcn::CheckBox *mShowBattleEventsCheckBox;
+ bool mShowBattleEvents;
+
+ EditDialog *mEditDialog;
+};
+
+#endif
diff --git a/src/gui/setup_colors.cpp b/src/gui/setup_colors.cpp
new file mode 100644
index 000000000..76510a283
--- /dev/null
+++ b/src/gui/setup_colors.cpp
@@ -0,0 +1,443 @@
+/*
+ * Configurable text colors
+ * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net>
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/setup_colors.h"
+
+#include "configuration.h"
+
+#include "gui/gui.h"
+#include "gui/theme.h"
+#include "gui/userpalette.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/itemlinkhandler.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/slider.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/textpreview.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <string>
+#include <cmath>
+
+const std::string Setup_Colors::rawmsg =
+ _("This is what the color looks like");
+
+Setup_Colors::Setup_Colors() :
+ mSelected(-1)
+{
+ setName(_("Colors"));
+
+ mColorBox = new ListBox(userPalette);
+ mColorBox->addSelectionListener(this);
+
+ mScroll = new ScrollArea(mColorBox);
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mTextPreview = new TextPreview(rawmsg);
+
+ mPreview = new BrowserBox(BrowserBox::AUTO_WRAP);
+ mPreview->setOpaque(false);
+
+ // don't do anything with links
+ mPreview->setLinkHandler(NULL);
+
+ mPreviewBox = new ScrollArea(mPreview);
+ mPreviewBox->setHeight(20);
+ mPreviewBox->setScrollPolicy(gcn::ScrollArea::SHOW_NEVER,
+ gcn::ScrollArea::SHOW_NEVER);
+
+ mGradTypeLabel = new Label(_("Type:"));
+
+ mGradTypeSlider = new Slider(0, 3);
+ mGradTypeSlider->setWidth(180);
+ mGradTypeSlider->setActionEventId("slider_grad");
+ mGradTypeSlider->setValue(0);
+ mGradTypeSlider->addActionListener(this);
+ mGradTypeSlider->setEnabled(false);
+
+ mGradTypeText = new Label;
+
+ std::string longText = _("Static");
+
+ if (getFont()->getWidth(_("Pulse")) > getFont()->getWidth(longText))
+ longText = _("Pulse");
+ if (getFont()->getWidth(_("Rainbow")) > getFont()->getWidth(longText))
+ longText = _("Rainbow");
+ if (getFont()->getWidth(_("Spectrum")) > getFont()->getWidth(longText))
+ longText = _("Spectrum");
+
+ mGradTypeText->setCaption(longText);
+
+ mGradDelayLabel = new Label(_("Delay:"));
+
+ mGradDelayText = new TextField();
+ mGradDelayText->setWidth(40);
+ mGradDelayText->setRange(20, 100);
+ mGradDelayText->setNumeric(true);
+ mGradDelayText->setEnabled(false);
+
+ mGradDelaySlider = new Slider(20, 100);
+ mGradDelaySlider->setWidth(180);
+ mGradDelaySlider->setValue(mGradDelayText->getValue());
+ mGradDelaySlider->setActionEventId("slider_graddelay");
+ mGradDelaySlider->addActionListener(this);
+ mGradDelaySlider->setEnabled(false);
+
+ mRedLabel = new Label(_("Red:"));
+
+ mRedText = new TextField;
+ mRedText->setWidth(40);
+ mRedText->setRange(0, 255);
+ mRedText->setNumeric(true);
+ mRedText->setEnabled(false);
+
+ mRedSlider = new Slider(0, 255);
+ mRedSlider->setWidth(180);
+ mRedSlider->setValue(mRedText->getValue());
+ mRedSlider->setActionEventId("slider_red");
+ mRedSlider->addActionListener(this);
+ mRedSlider->setEnabled(false);
+
+ mGreenLabel = new Label(_("Green:"));
+
+ mGreenText = new TextField;
+ mGreenText->setWidth(40);
+ mGreenText->setRange(0, 255);
+ mGreenText->setNumeric(true);
+ mGreenText->setEnabled(false);
+
+ mGreenSlider = new Slider(0, 255);
+ mGreenSlider->setWidth(180);
+ mGreenSlider->setValue(mGreenText->getValue());
+ mGreenSlider->setActionEventId("slider_green");
+ mGreenSlider->addActionListener(this);
+ mGreenSlider->setEnabled(false);
+
+ mBlueLabel = new Label(_("Blue:"));
+
+ mBlueText = new TextField;
+ mBlueText->setWidth(40);
+ mBlueText->setRange(0, 255);
+ mBlueText->setNumeric(true);
+ mBlueText->setEnabled(false);
+
+ mBlueSlider = new Slider(0, 255);
+ mBlueSlider->setWidth(180);
+ mBlueSlider->setValue(mBlueText->getValue());
+ mBlueSlider->setActionEventId("slider_blue");
+ mBlueSlider->addActionListener(this);
+ mBlueSlider->setEnabled(false);
+
+ setOpaque(false);
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mScroll, 6, 6).setPadding(2);
+ place(0, 6, mPreviewBox, 6).setPadding(2);
+ place(0, 7, mGradTypeLabel, 3);
+ place(3, 7, mGradTypeSlider);
+ place(4, 7, mGradTypeText, 2).setPadding(1);
+ place(0, 8, mRedLabel, 3);
+ place(3, 8, mRedSlider);
+ place(5, 8, mRedText).setPadding(1);
+ place(0, 9, mGreenLabel, 3);
+ place(3, 9, mGreenSlider);
+ place(5, 9, mGreenText).setPadding(1);
+ place(0, 10, mBlueLabel, 3);
+ place(3, 10, mBlueSlider);
+ place(5, 10, mBlueText).setPadding(1);
+ place(0, 11, mGradDelayLabel, 3);
+ place(3, 11, mGradDelaySlider);
+ place(5, 11, mGradDelayText).setPadding(1);
+
+ mGradTypeText->setCaption("");
+
+ setDimension(gcn::Rectangle(0, 0, 365, 350));
+}
+
+Setup_Colors::~Setup_Colors()
+{
+ if (mPreviewBox && mPreviewBox->getContent() == mPreview)
+ {
+ delete mTextPreview;
+ mTextPreview = 0;
+ }
+ else
+ {
+ delete mPreview;
+ mPreview = 0;
+ }
+}
+
+void Setup_Colors::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "slider_grad")
+ {
+ updateGradType();
+ updateColor();
+ return;
+ }
+
+ if (event.getId() == "slider_graddelay")
+ {
+ mGradDelayText->setText(toString(
+ std::floor(mGradDelaySlider->getValue())));
+ updateColor();
+ return;
+ }
+
+ if (event.getId() == "slider_red")
+ {
+ mRedText->setText(toString(std::floor(mRedSlider->getValue())));
+ updateColor();
+ return;
+ }
+
+ if (event.getId() == "slider_green")
+ {
+ mGreenText->setText(toString(std::floor(mGreenSlider->getValue())));
+ updateColor();
+ return;
+ }
+
+ if (event.getId() == "slider_blue")
+ {
+ mBlueText->setText(toString(std::floor(mBlueSlider->getValue())));
+ updateColor();
+ return;
+ }
+}
+
+void Setup_Colors::valueChanged(const gcn::SelectionEvent &event _UNUSED_)
+{
+ if (!userPalette)
+ return;
+
+ mSelected = mColorBox->getSelected();
+ int type = userPalette->getColorTypeAt(mSelected);
+ const gcn::Color *col = &userPalette->getColor(type);
+ Palette::GradientType grad = userPalette->getGradientType(type);
+ const int delay = userPalette->getGradientDelay(type);
+
+ mPreview->clearRows();
+ mPreviewBox->setContent(mTextPreview);
+ mTextPreview->setFont(boldFont);
+ mTextPreview->setTextColor(col);
+ mTextPreview->setTextBGColor(NULL);
+ mTextPreview->setOpaque(false);
+ mTextPreview->setShadow(true);
+ mTextPreview->setOutline(true);
+ mTextPreview->useTextAlpha(false);
+
+ switch (type)
+ {
+ case UserPalette::COLLISION_HIGHLIGHT:
+ case UserPalette::PORTAL_HIGHLIGHT:
+ case UserPalette::HOME_PLACE:
+ case UserPalette::ROAD_POINT:
+ mTextPreview->setBGColor(col);
+ mTextPreview->setOpaque(true);
+ mTextPreview->setOutline(false);
+ mTextPreview->setShadow(false);
+ break;
+ case UserPalette::ATTACK_RANGE_BORDER:
+ case UserPalette::HOME_PLACE_BORDER:
+ mTextPreview->setFont(gui->getFont());
+ mTextPreview->setTextColor(col);
+ mTextPreview->setOutline(false);
+ mTextPreview->setShadow(false);
+ break;
+ case UserPalette::PARTICLE:
+ case UserPalette::EXP_INFO:
+ case UserPalette::PICKUP_INFO:
+ case UserPalette::HIT_PLAYER_MONSTER:
+ case UserPalette::HIT_MONSTER_PLAYER:
+ case UserPalette::HIT_CRITICAL:
+ case UserPalette::MISS:
+ case UserPalette::HIT_LOCAL_PLAYER_MONSTER:
+ case UserPalette::HIT_LOCAL_PLAYER_CRITICAL:
+ case UserPalette::HIT_LOCAL_PLAYER_MISS:
+ case UserPalette::ATTACK_RANGE:
+ case UserPalette::MONSTER_ATTACK_RANGE:
+ mTextPreview->setShadow(false);
+ default:
+ break;
+ }
+
+ switch (type)
+ {
+ case UserPalette::PORTAL_HIGHLIGHT:
+ case UserPalette::ATTACK_RANGE:
+ case UserPalette::ATTACK_RANGE_BORDER:
+ case UserPalette::MONSTER_ATTACK_RANGE:
+ case UserPalette::HOME_PLACE:
+ case UserPalette::HOME_PLACE_BORDER:
+ case UserPalette::COLLISION_HIGHLIGHT:
+ case UserPalette::WALKABLE_HIGHLIGHT:
+ case UserPalette::ROAD_POINT:
+ case UserPalette::MONSTER_HP:
+ case UserPalette::MONSTER_HP2:
+ mGradDelayLabel->setCaption(_("Alpha:"));
+ mGradDelayText->setRange(0, 255);
+ mGradDelaySlider->setScale(0, 255);
+ break;
+ default:
+ mGradDelayLabel->setCaption(_("Delay:"));
+ mGradDelayText->setRange(20, 100);
+ mGradDelaySlider->setScale(20, 100);
+ break;
+ }
+ if (grad != Palette::STATIC && grad != Palette::PULSE)
+ { // If nonstatic color, don't display the current, but the committed
+ // color at the sliders
+ col = &userPalette->getCommittedColor(type);
+ }
+ else if (grad == Palette::PULSE)
+ {
+ col = &userPalette->getTestColor(type);
+ }
+
+ setEntry(mGradDelaySlider, mGradDelayText, delay);
+ setEntry(mRedSlider, mRedText, col->r);
+ setEntry(mGreenSlider, mGreenText, col->g);
+ setEntry(mBlueSlider, mBlueText, col->b);
+
+ mGradTypeSlider->setValue(grad);
+ updateGradType();
+ mGradTypeSlider->setEnabled(true);
+}
+
+void Setup_Colors::setEntry(gcn::Slider *s, TextField *t, int value)
+{
+ if (s)
+ s->setValue(value);
+ if (t)
+ {
+ char buffer[100];
+ sprintf(buffer, "%d", value);
+ t->setText(buffer);
+ }
+}
+
+void Setup_Colors::apply()
+{
+ if (userPalette)
+ userPalette->commit();
+}
+
+void Setup_Colors::cancel()
+{
+ if (!userPalette)
+ return;
+
+ userPalette->rollback();
+ int type = userPalette->getColorTypeAt(mSelected);
+ const gcn::Color *col = &userPalette->getColor(type);
+ mGradTypeSlider->setValue(userPalette->getGradientType(type));
+ const int delay = userPalette->getGradientDelay(type);
+ setEntry(mGradDelaySlider, mGradDelayText, delay);
+ setEntry(mRedSlider, mRedText, col->r);
+ setEntry(mGreenSlider, mGreenText, col->g);
+ setEntry(mBlueSlider, mBlueText, col->b);
+}
+
+#if 0
+void Setup_Colors::listen(const TextField *tf)
+{
+ if (!tf)
+ return;
+
+ if (tf == mGradDelayText)
+ mGradDelaySlider->setValue(tf->getValue());
+ else if (tf == mRedText)
+ mRedSlider->setValue(tf->getValue());
+ else if (tf == mGreenText)
+ mGreenSlider->setValue(tf->getValue());
+ else if (tf == mBlueText)
+ mBlueSlider->setValue(tf->getValue());
+
+ updateColor();
+}
+#endif
+
+void Setup_Colors::updateGradType()
+{
+ if (mSelected == -1 || !userPalette)
+ return;
+
+ mSelected = mColorBox->getSelected();
+ int type = userPalette->getColorTypeAt(mSelected);
+ Palette::GradientType grad = userPalette->getGradientType(type);
+
+ mGradTypeText->setCaption(
+ (grad == Palette::STATIC) ? _("Static") :
+ (grad == Palette::PULSE) ? _("Pulse") :
+ (grad == Palette::RAINBOW) ? _("Rainbow") : _("Spectrum"));
+
+ const bool enable = (grad == Palette::STATIC || grad == Palette::PULSE);
+// const bool delayEnable = (grad != Palette::STATIC);
+ const bool delayEnable = true;
+
+ mGradDelayText->setEnabled(delayEnable);
+ mGradDelaySlider->setEnabled(delayEnable);
+
+ mRedText->setEnabled(enable);
+ mRedSlider->setEnabled(enable);
+ mGreenText->setEnabled(enable);
+ mGreenSlider->setEnabled(enable);
+ mBlueText->setEnabled(enable);
+ mBlueSlider->setEnabled(enable);
+}
+
+void Setup_Colors::updateColor()
+{
+ if (mSelected == -1 || !userPalette)
+ return;
+
+ int type = userPalette->getColorTypeAt(mSelected);
+ Palette::GradientType grad = static_cast<Palette::GradientType>(
+ static_cast<int>(mGradTypeSlider->getValue()));
+ int delay = static_cast<int>(mGradDelaySlider->getValue());
+ userPalette->setGradient(type, grad);
+ userPalette->setGradientDelay(type, delay);
+
+ if (grad == Palette::STATIC)
+ {
+ userPalette->setColor(type,
+ static_cast<int>(mRedSlider->getValue()),
+ static_cast<int>(mGreenSlider->getValue()),
+ static_cast<int>(mBlueSlider->getValue()));
+ }
+ else if (grad == Palette::PULSE)
+ {
+ userPalette->setTestColor(type, gcn::Color(
+ static_cast<int>(mRedSlider->getValue()),
+ static_cast<int>(mGreenSlider->getValue()),
+ static_cast<int>(mBlueSlider->getValue())));
+ }
+}
diff --git a/src/gui/setup_colors.h b/src/gui/setup_colors.h
new file mode 100644
index 000000000..1b3985850
--- /dev/null
+++ b/src/gui/setup_colors.h
@@ -0,0 +1,96 @@
+/*
+ * Configurable text colors
+ * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net>
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SETUP_COLORS_H
+#define SETUP_COLORS_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#include <string>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class BrowserBox;
+class TextField;
+class TextPreview;
+
+class Setup_Colors : public SetupTab,
+ public gcn::ActionListener,
+ public gcn::SelectionListener
+{
+ public:
+ Setup_Colors();
+ ~Setup_Colors();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ private:
+ static const std::string rawmsg;
+
+ gcn::ListBox *mColorBox;
+ gcn::ScrollArea *mScroll;
+ BrowserBox *mPreview;
+ TextPreview *mTextPreview;
+ gcn::ScrollArea *mPreviewBox;
+ int mSelected;
+
+ gcn::Label *mGradTypeLabel;
+ gcn::Slider *mGradTypeSlider;
+ gcn::Label *mGradTypeText;
+
+ gcn::Label *mGradDelayLabel;
+ gcn::Slider *mGradDelaySlider;
+ TextField *mGradDelayText;
+
+ gcn::Label *mRedLabel;
+ gcn::Slider *mRedSlider;
+ TextField *mRedText;
+ int mRedValue;
+
+ gcn::Label *mGreenLabel;
+ gcn::Slider *mGreenSlider;
+ TextField *mGreenText;
+ int mGreenValue;
+
+ gcn::Label *mBlueLabel;
+ gcn::Slider *mBlueSlider;
+ TextField *mBlueText;
+ int mBlueValue;
+
+ void setEntry(gcn::Slider *s, TextField *t, int value);
+ void updateColor();
+ void updateGradType();
+};
+
+#endif // SETUP_COLORS_H
diff --git a/src/gui/setup_joystick.cpp b/src/gui/setup_joystick.cpp
new file mode 100644
index 000000000..5f8f11993
--- /dev/null
+++ b/src/gui/setup_joystick.cpp
@@ -0,0 +1,101 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/setup_joystick.h"
+
+#include "configuration.h"
+#include "joystick.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+
+#include "utils/gettext.h"
+
+extern Joystick *joystick;
+
+Setup_Joystick::Setup_Joystick():
+ mCalibrateLabel(new Label(_("Press the button to start calibration"))),
+ mCalibrateButton(new Button(_("Calibrate"), "calibrate", this)),
+ mJoystickEnabled(new CheckBox(_("Enable joystick")))
+{
+ setName(_("Joystick"));
+
+ mOriginalJoystickEnabled = !config.getBoolValue("joystickEnabled");
+ mJoystickEnabled->setSelected(mOriginalJoystickEnabled);
+
+ mJoystickEnabled->addActionListener(this);
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mJoystickEnabled);
+ place(0, 1, mCalibrateLabel);
+ place.getCell().matchColWidth(0, 0);
+ place = h.getPlacer(0, 1);
+ place(0, 0, mCalibrateButton);
+
+ setDimension(gcn::Rectangle(0, 0, 365, 75));
+}
+
+void Setup_Joystick::action(const gcn::ActionEvent &event)
+{
+ if (!joystick)
+ return;
+
+ if (event.getSource() == mJoystickEnabled)
+ {
+ joystick->setEnabled(mJoystickEnabled->isSelected());
+ }
+ else
+ {
+ if (joystick->isCalibrating())
+ {
+ mCalibrateButton->setCaption(_("Calibrate"));
+ mCalibrateLabel->setCaption
+ (_("Press the button to start calibration"));
+ joystick->finishCalibration();
+ }
+ else
+ {
+ mCalibrateButton->setCaption(_("Stop"));
+ mCalibrateLabel->setCaption(_("Rotate the stick"));
+ joystick->startCalibration();
+ }
+ }
+}
+
+void Setup_Joystick::cancel()
+{
+ if (joystick)
+ joystick->setEnabled(mOriginalJoystickEnabled);
+
+ mJoystickEnabled->setSelected(mOriginalJoystickEnabled);
+}
+
+void Setup_Joystick::apply()
+{
+ config.setValue("joystickEnabled",
+ joystick ? joystick->isEnabled() : false);
+}
+
diff --git a/src/gui/setup_joystick.h b/src/gui/setup_joystick.h
new file mode 100644
index 000000000..f848f45c1
--- /dev/null
+++ b/src/gui/setup_joystick.h
@@ -0,0 +1,48 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_SETUP_JOYSTICK_H
+#define GUI_SETUP_JOYSTICK_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Setup_Joystick : public SetupTab, public gcn::ActionListener
+{
+ public:
+ Setup_Joystick();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ gcn::Label *mCalibrateLabel;
+ gcn::Button *mCalibrateButton;
+ bool mOriginalJoystickEnabled;
+ gcn::CheckBox *mJoystickEnabled;
+};
+
+#endif
diff --git a/src/gui/setup_keyboard.cpp b/src/gui/setup_keyboard.cpp
new file mode 100644
index 000000000..4e55ecb35
--- /dev/null
+++ b/src/gui/setup_keyboard.cpp
@@ -0,0 +1,210 @@
+/*
+ * Custom keyboard shortcuts configuration
+ * Copyright (C) 2007 Joshua Langley <joshlangley@optusnet.com.au>
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/setup_keyboard.h"
+
+#include "keyboardconfig.h"
+
+#include "gui/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/listmodel.hpp>
+
+#include <SDL_keyboard.h>
+
+/**
+ * The list model for key function list.
+ *
+ * \ingroup Interface
+ */
+class KeyListModel : public gcn::ListModel
+{
+ public:
+ /**
+ * Returns the number of elements in container.
+ */
+ int getNumberOfElements()
+ { return keyboard.KEY_TOTAL; }
+
+ /**
+ * Returns element from container.
+ */
+ std::string getElementAt(int i)
+ { return mKeyFunctions[i]; }
+
+ /**
+ * Sets element from container.
+ */
+ void setElementAt(int i, const std::string &caption)
+ { mKeyFunctions[i] = caption; }
+
+ private:
+ std::string mKeyFunctions[KeyboardConfig::KEY_TOTAL];
+};
+
+Setup_Keyboard::Setup_Keyboard():
+ mKeyListModel(new KeyListModel),
+ mKeyList(new ListBox(mKeyListModel)),
+ mKeySetting(false)
+{
+ keyboard.setSetupKeyboard(this);
+ setName(_("Keyboard"));
+
+ refreshKeys();
+
+ mKeyList->addActionListener(this);
+
+ ScrollArea *scrollArea = new ScrollArea(mKeyList);
+ scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mAssignKeyButton = new Button(_("Assign"), "assign", this);
+ mAssignKeyButton->addActionListener(this);
+ mAssignKeyButton->setEnabled(false);
+
+ mUnassignKeyButton = new Button(_("Unassign"), "unassign", this);
+ mUnassignKeyButton->addActionListener(this);
+ mUnassignKeyButton->setEnabled(false);
+
+ mMakeDefaultButton = new Button(_("Default"), "makeDefault", this);
+ mMakeDefaultButton->addActionListener(this);
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, scrollArea, 4, 6).setPadding(2);
+ place(0, 6, mMakeDefaultButton);
+ place(2, 6, mAssignKeyButton);
+ place(3, 6, mUnassignKeyButton);
+
+ setDimension(gcn::Rectangle(0, 0, 500, 350));
+}
+
+Setup_Keyboard::~Setup_Keyboard()
+{
+ delete mKeyList;
+ mKeyList = 0;
+ delete mKeyListModel;
+ mKeyListModel = 0;
+
+ delete mAssignKeyButton;
+ mAssignKeyButton = 0;
+ delete mUnassignKeyButton;
+ mUnassignKeyButton = 0;
+ delete mMakeDefaultButton;
+ mMakeDefaultButton = 0;
+}
+
+void Setup_Keyboard::apply()
+{
+ keyUnresolved();
+
+ if (keyboard.hasConflicts())
+ {
+ new OkDialog(_("Key Conflict(s) Detected."),
+ keyboard.getBindError());
+ }
+ keyboard.setEnabled(true);
+ keyboard.store();
+}
+
+void Setup_Keyboard::cancel()
+{
+ keyUnresolved();
+
+ keyboard.retrieve();
+ keyboard.setEnabled(true);
+
+ refreshKeys();
+}
+
+void Setup_Keyboard::action(const gcn::ActionEvent &event)
+{
+ if (event.getSource() == mKeyList)
+ {
+ if (!mKeySetting)
+ {
+ mAssignKeyButton->setEnabled(true);
+ mUnassignKeyButton->setEnabled(true);
+ }
+ }
+ else if (event.getId() == "assign")
+ {
+ mKeySetting = true;
+ mAssignKeyButton->setEnabled(false);
+ keyboard.setEnabled(false);
+ int i(mKeyList->getSelected());
+ keyboard.setNewKeyIndex(i);
+ mKeyListModel->setElementAt(i, keyboard.getKeyCaption(i) + ": ?");
+ }
+ else if (event.getId() == "unassign")
+ {
+ int i(mKeyList->getSelected());
+ keyboard.setNewKeyIndex(i);
+ refreshAssignedKey(mKeyList->getSelected());
+ keyboard.setNewKey(keyboard.KEY_NO_VALUE);
+ mAssignKeyButton->setEnabled(true);
+ }
+ else if (event.getId() == "makeDefault")
+ {
+ keyboard.makeDefault();
+ refreshKeys();
+ }
+}
+
+void Setup_Keyboard::refreshAssignedKey(int index)
+{
+ std::string caption;
+ char *temp = SDL_GetKeyName(
+ static_cast<SDLKey>(keyboard.getKeyValue(index)));
+ caption = keyboard.getKeyCaption(index) + ": " + toString(temp);
+ mKeyListModel->setElementAt(index, caption);
+}
+
+void Setup_Keyboard::newKeyCallback(int index)
+{
+ mKeySetting = false;
+ refreshAssignedKey(index);
+ mAssignKeyButton->setEnabled(true);
+}
+
+void Setup_Keyboard::refreshKeys()
+{
+ for (int i = 0; i < keyboard.KEY_TOTAL; i++)
+ refreshAssignedKey(i);
+}
+
+void Setup_Keyboard::keyUnresolved()
+{
+ if (mKeySetting)
+ {
+ newKeyCallback(keyboard.getNewKeyIndex());
+ keyboard.setNewKeyIndex(keyboard.KEY_NO_VALUE);
+ }
+}
diff --git a/src/gui/setup_keyboard.h b/src/gui/setup_keyboard.h
new file mode 100644
index 000000000..4c916705c
--- /dev/null
+++ b/src/gui/setup_keyboard.h
@@ -0,0 +1,81 @@
+/*
+ * Custom keyboard shortcuts configuration
+ * Copyright (C) 2007 Joshua Langley <joshlangley@optusnet.com.au>
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_SETUP_KEYBOARD_H
+#define GUI_SETUP_KEYBOARD_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <string>
+
+class Setup_Keyboard : public SetupTab, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor
+ */
+ Setup_Keyboard();
+
+ /**
+ * Destructor
+ */
+ ~Setup_Keyboard();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Get an update on the assigned key.
+ */
+ void refreshAssignedKey(int index);
+
+ /**
+ * The callback function when a new key has been pressed.
+ */
+ void newKeyCallback(int index);
+
+ /**
+ * Shorthand method to update all the keys.
+ */
+ void refreshKeys();
+
+ /**
+ * If a key function is unresolved, then this reverts it.
+ */
+ void keyUnresolved();
+
+ private:
+ class KeyListModel *mKeyListModel;
+ gcn::ListBox *mKeyList;
+
+ gcn::Button *mAssignKeyButton;
+ gcn::Button *mUnassignKeyButton;
+ gcn::Button *mMakeDefaultButton;
+
+ bool mKeySetting; /**< flag to check if key being set. */
+};
+
+#endif
diff --git a/src/gui/setup_other.cpp b/src/gui/setup_other.cpp
new file mode 100644
index 000000000..54ec1c65d
--- /dev/null
+++ b/src/gui/setup_other.cpp
@@ -0,0 +1,426 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/setup_other.h"
+#include "gui/editdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/textfield.h"
+
+#include "configuration.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "utils/gettext.h"
+
+#define ACTION_SHOW_TAKEDDAMAGE "taked damage"
+#define ACTION_NO_RAIN "no rain"
+#define ACTION_ONLY_REACHABLE "only reachable"
+#define ACTION_ERRORS_IN_DEBUG "errors in debug"
+#define ACTION_HIGHLIGHT_PORTALS "highlight portals"
+#define ACTION_HIGHLIGHT_ATTACK_RANGE "highlight attack"
+#define ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE "highlight monster attack"
+#define ACTION_CYCLE_PLAYERS "cycle players"
+#define ACTION_CYCLE_MONSTERS "cycle monsters"
+#define ACTION_ENABLE_BOTCHECKER "bot checker"
+#define ACTION_FLOORITEMS_HIGHLIGHT "floor items"
+#define ACTION_MOVE_PROGRAM "move program"
+#define ACTION_EDIT_PROGRAM "edit program"
+#define ACTION_EDIT_PROGRAM_OK "edit program ok"
+#define ACTION_AFK "move afk"
+#define ACTION_EDIT_AFK "edit afk"
+#define ACTION_EDIT_AFK_OK "edit afk ok"
+#define ACTION_ENABLE_AFK "enable afk"
+#define ACTION_TRADEBOT "trade bot"
+#define ACTION_BUGGY_SERVERS "buggy servers"
+#define ACTION_DEBUG_LOG "debug log"
+#define ACTION_SERVER_ATTACK "server attack"
+#define ACTION_FIX_POS "fix pos"
+#define ACTION_ATTACK_MOVING "attack moving"
+#define ACTION_QUICK_STATS "quick stats"
+#define ACTION_WARP_PARTICLE "warp particle"
+#define ACTION_AUTO_SHOP "auto shop"
+#define ACTION_SHOW_MOB_HP "show mob hp"
+
+Setup_Other::Setup_Other():
+ mShowMonstersTakedDamage(config.getBoolValue("showMonstersTakedDamage")),
+ mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")),
+ mErrorsInDebug(config.getBoolValue("errorsInDebug")),
+ mHighlightPortals(config.getBoolValue("highlightMapPortals")),
+ mHighlightAttackRange(config.getBoolValue("highlightAttackRange")),
+ mHighlightMonsterAttackRange(
+ config.getBoolValue("highlightMonsterAttackRange")),
+ mCyclePlayers(config.getBoolValue("cyclePlayers")),
+ mCycleMonsters(config.getBoolValue("cycleMonsters")),
+ mEnableBotChecker(config.getBoolValue("enableBotCheker")),
+ mFloorItemsHighlight(config.getBoolValue("floorItemsHighlight")),
+ mMoveProgram(config.getStringValue("crazyMoveProgram")),
+ mAfk(config.getStringValue("afkMessage")),
+ mTradeBot(config.getBoolValue("tradebot")),
+ mBuggyServers(serverConfig.getValueBool("enableBuggyServers", true)),
+ mDebugLog(config.getBoolValue("debugLog")),
+ mServerAttack(config.getBoolValue("serverAttack")),
+ mAutofixPos(config.getBoolValue("autofixPos")),
+ mAttackMoving(config.getBoolValue("attackMoving")),
+ mQuickStats(config.getBoolValue("quickStats")),
+ mWarpParticle(config.getBoolValue("warpParticle")),
+ mAutoShop(config.getBoolValue("autoShop")),
+ mShowMobHP(config.getBoolValue("showMobHP"))
+{
+ setName(_("Misc"));
+
+ mShowMonstersTakedDamageCheckBox = new CheckBox(
+ _("Show damage inflicted to monsters"),
+ mShowMonstersTakedDamage,
+ this, ACTION_SHOW_TAKEDDAMAGE);
+
+ mTargetOnlyReachableCheckBox = new CheckBox(
+ _("Auto target only reachable monsters"),
+ mTargetOnlyReachable,
+ this, ACTION_ONLY_REACHABLE);
+
+ mHighlightPortalsCheckBox = new CheckBox(_("Highlight map portals"),
+ mHighlightPortals,
+ this, ACTION_HIGHLIGHT_PORTALS);
+
+ mHighlightAttackRangeCheckBox = new CheckBox(
+ _("Highlight player attack range"),
+ mHighlightAttackRange,
+ this, ACTION_HIGHLIGHT_ATTACK_RANGE);
+
+ mHighlightMonsterAttackRangeCheckBox = new CheckBox(
+ _("Highlight monster attack range"),
+ mHighlightMonsterAttackRange,
+ this, ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE);
+
+ mCyclePlayersCheckBox = new CheckBox(_("Cycle player targets"),
+ mCyclePlayers, this, ACTION_CYCLE_PLAYERS);
+
+ mCycleMonstersCheckBox = new CheckBox(_("Cycle monster targets"),
+ mCycleMonsters, this, ACTION_CYCLE_MONSTERS);
+
+ mEnableBotCheckerCheckBox = new CheckBox(_("Enable bot checker"),
+ mEnableBotChecker, this, ACTION_ENABLE_BOTCHECKER);
+
+ mFloorItemsHighlightCheckBox = new CheckBox(_("Highlight floor items"),
+ mFloorItemsHighlight, this, ACTION_FLOORITEMS_HIGHLIGHT);
+
+ mMoveProgramLabel = new Label(_("Crazy move A program"));
+
+ mMoveProgramField = new TextField(mMoveProgram, true,
+ this, ACTION_MOVE_PROGRAM);
+
+ mMoveProgramButton = new Button(_("Edit"), ACTION_EDIT_PROGRAM, this);
+
+ mAfkField = new TextField(mAfk, true, this, ACTION_AFK);
+
+ mAfkButton = new Button(_("Edit"), ACTION_EDIT_AFK, this);
+
+ mTradeBotCheckBox = new CheckBox(_("Enable shop mode"),
+ mTradeBot,
+ this, ACTION_TRADEBOT);
+
+ mBuggyServersCheckBox = new CheckBox(_("Enable buggy servers protection"),
+ mBuggyServers,
+ this, ACTION_BUGGY_SERVERS);
+
+ mDebugLogCheckBox = new CheckBox(_("Enable debug log"),
+ mDebugLog,
+ this, ACTION_DEBUG_LOG);
+
+ mServerAttackCheckBox = new CheckBox(_("Enable server side attack"),
+ mServerAttack,
+ this, ACTION_SERVER_ATTACK);
+
+ mAutofixPosCheckBox = new CheckBox(_("Auto fix position"),
+ mAutofixPos,
+ this, ACTION_FIX_POS);
+
+ mAttackMovingCheckBox = new CheckBox(_("Attack while moving"),
+ mAttackMoving,
+ this, ACTION_ATTACK_MOVING);
+
+ mQuickStatsCheckBox = new CheckBox(_("Enable quick stats"),
+ mQuickStats,
+ this, ACTION_QUICK_STATS);
+
+ mWarpParticleCheckBox = new CheckBox(_("Show warps particles"),
+ mWarpParticle,
+ this, ACTION_WARP_PARTICLE);
+
+ mAutoShopCheckBox = new CheckBox(_("Accept sell/buy requests"),
+ mAutoShop,
+ this, ACTION_AUTO_SHOP);
+
+ mShowMobHPCheckBox = new CheckBox(_("Show monster hp bar"),
+ mShowMobHP,
+ this, ACTION_SHOW_MOB_HP);
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mShowMonstersTakedDamageCheckBox, 12);
+ place(12, 0, mServerAttackCheckBox, 10);
+ place(0, 1, mTargetOnlyReachableCheckBox, 12);
+ place(12, 1, mAutofixPosCheckBox, 10);
+ place(0, 2, mHighlightPortalsCheckBox, 12);
+ place(12, 2, mAttackMovingCheckBox, 10);
+ place(12, 3, mQuickStatsCheckBox, 10);
+ place(12, 4, mWarpParticleCheckBox, 10);
+ place(12, 5, mAutoShopCheckBox, 10);
+ place(12, 6, mShowMobHPCheckBox, 10);
+ place(0, 3, mFloorItemsHighlightCheckBox, 12);
+ place(0, 4, mHighlightAttackRangeCheckBox, 12);
+ place(0, 5, mHighlightMonsterAttackRangeCheckBox, 12);
+ place(0, 6, mCyclePlayersCheckBox, 12);
+ place(0, 7, mCycleMonstersCheckBox, 12);
+ place(0, 8, mEnableBotCheckerCheckBox, 12);
+ place(0, 9, mMoveProgramLabel, 12);
+ place(0, 10, mMoveProgramField, 9);
+ place(9, 10, mMoveProgramButton, 2);
+ place(0, 11, mAfkField, 9);
+ place(9, 11, mAfkButton, 2);
+ place(0, 12, mTradeBotCheckBox, 12);
+ place(0, 13, mBuggyServersCheckBox, 12);
+ place(0, 14, mDebugLogCheckBox, 12);
+
+ place.getCell().matchColWidth(0, 0);
+ place = h.getPlacer(0, 1);
+
+ setDimension(gcn::Rectangle(0, 0, 550, 500));
+}
+
+void Setup_Other::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == ACTION_SHOW_TAKEDDAMAGE)
+ {
+ mShowMonstersTakedDamage = mShowMonstersTakedDamageCheckBox
+ ->isSelected();
+ }
+ else if (event.getId() == ACTION_ONLY_REACHABLE)
+ {
+ mTargetOnlyReachable = mTargetOnlyReachableCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_HIGHLIGHT_PORTALS)
+ {
+ mHighlightPortals = mHighlightPortalsCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_HIGHLIGHT_ATTACK_RANGE)
+ {
+ mHighlightAttackRange = mHighlightAttackRangeCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE)
+ {
+ mHighlightMonsterAttackRange = mHighlightMonsterAttackRangeCheckBox
+ ->isSelected();
+ }
+ else if (event.getId() == ACTION_CYCLE_PLAYERS)
+ {
+ mCyclePlayers = mCyclePlayersCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_CYCLE_MONSTERS)
+ {
+ mCycleMonsters = mCycleMonstersCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_ENABLE_BOTCHECKER)
+ {
+ mEnableBotChecker = mEnableBotCheckerCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_FLOORITEMS_HIGHLIGHT)
+ {
+ mFloorItemsHighlight = mFloorItemsHighlightCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_MOVE_PROGRAM)
+ {
+ mMoveProgram = mMoveProgramField->getText();
+ }
+ else if (event.getId() == ACTION_EDIT_PROGRAM)
+ {
+ mEditDialog = new EditDialog("Crazy Move A",
+ mMoveProgramField->getText(), ACTION_EDIT_PROGRAM_OK);
+ mEditDialog->addActionListener(this);
+ }
+ else if (event.getId() == ACTION_EDIT_PROGRAM_OK)
+ {
+ mMoveProgramField->setText(mEditDialog->getMsg());
+ }
+
+ else if (event.getId() == ACTION_AFK)
+ mAfk = mAfkField->getText();
+ else if (event.getId() == ACTION_EDIT_AFK)
+ {
+ mEditDialog = new EditDialog("Afk message", mAfkField->getText(),
+ ACTION_EDIT_AFK_OK);
+ mEditDialog->addActionListener(this);
+ }
+ else if (event.getId() == ACTION_EDIT_AFK_OK)
+ {
+ mAfkField->setText(mEditDialog->getMsg());
+ }
+ else if (event.getId() == ACTION_TRADEBOT)
+ {
+ mTradeBot = mTradeBotCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_BUGGY_SERVERS)
+ {
+ mBuggyServers = mBuggyServersCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_DEBUG_LOG)
+ {
+ mDebugLog = mDebugLogCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SERVER_ATTACK)
+ {
+ mServerAttack = mServerAttackCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_FIX_POS)
+ {
+ mAutofixPos = mAutofixPosCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_ATTACK_MOVING)
+ {
+ mAttackMoving = mAttackMovingCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_QUICK_STATS)
+ {
+ mQuickStats = mQuickStatsCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_WARP_PARTICLE)
+ {
+ mWarpParticle = mWarpParticleCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_AUTO_SHOP)
+ {
+ mAutoShop = mAutoShopCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SHOW_MOB_HP)
+ {
+ mShowMobHP = mShowMobHPCheckBox->isSelected();
+ }
+}
+
+void Setup_Other::cancel()
+{
+ mShowMonstersTakedDamage = config.getBoolValue(
+ "showMonstersTakedDamage");
+ mShowMonstersTakedDamageCheckBox->setSelected(mShowMonstersTakedDamage);
+
+ mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable");
+ mTargetOnlyReachableCheckBox->setSelected(mTargetOnlyReachable);
+
+ mHighlightPortals = config.getBoolValue("highlightMapPortals");
+ mHighlightPortalsCheckBox->setSelected(mHighlightPortals);
+
+ mHighlightAttackRange = config.getBoolValue("highlightAttackRange");
+ mHighlightAttackRangeCheckBox->setSelected(mHighlightAttackRange);
+
+ mHighlightMonsterAttackRange = config.getBoolValue(
+ "highlightMonsterAttackRange");
+ mHighlightMonsterAttackRangeCheckBox->setSelected(
+ mHighlightMonsterAttackRange);
+
+ mCyclePlayers = config.getBoolValue("cyclePlayers");
+ mCyclePlayersCheckBox->setSelected(mCyclePlayers);
+
+ mCycleMonsters = config.getBoolValue("cycleMonsters");
+ mCycleMonstersCheckBox->setSelected(mCycleMonsters);
+
+ mEnableBotChecker = config.getBoolValue("enableBotCheker");
+ mEnableBotCheckerCheckBox->setSelected(mEnableBotChecker);
+
+ mFloorItemsHighlight = config.getBoolValue("floorItemsHighlight");
+ mFloorItemsHighlightCheckBox->setSelected(mFloorItemsHighlight);
+
+ mMoveProgram = config.getStringValue("crazyMoveProgram");
+ mMoveProgramField->setText(mMoveProgram);
+
+ mAfk = config.getStringValue("afkMessage");
+ mAfkField->setText(mAfk);
+
+ mTradeBot = config.getBoolValue("tradebot");
+ mTradeBotCheckBox->setSelected(mTradeBot);
+
+ mBuggyServers = serverConfig.getValueBool("enableBuggyServers", true);
+ mBuggyServersCheckBox->setSelected(mBuggyServers);
+
+ mDebugLog = config.getBoolValue("debugLog");
+ mDebugLogCheckBox->setSelected(mDebugLog);
+
+ mServerAttack = config.getBoolValue("serverAttack");
+ mServerAttackCheckBox->setSelected(mServerAttack);
+
+ mAutofixPos = config.getBoolValue("autofixPos");
+ mAutofixPosCheckBox->setSelected(mAutofixPos);
+
+ mAttackMoving = config.getBoolValue("attackMoving");
+ mAttackMovingCheckBox->setSelected(mAttackMoving);
+
+ mQuickStats = config.getBoolValue("quickStats");
+ mQuickStatsCheckBox->setSelected(mQuickStats);
+
+ mWarpParticle = config.getBoolValue("warpParticle");
+ mWarpParticleCheckBox->setSelected(mWarpParticle);
+
+ mAutoShop = config.getBoolValue("autoShop");
+ mAutoShopCheckBox->setSelected(mAutoShop);
+
+ mShowMobHP = config.getBoolValue("showMobHP");
+ mShowMobHPCheckBox->setSelected(mShowMobHP);
+}
+
+void Setup_Other::apply()
+{
+ config.setValue("showMonstersTakedDamage", mShowMonstersTakedDamage);
+ config.setValue("targetOnlyReachable", mTargetOnlyReachable);
+ config.setValue("errorsInDebug", mErrorsInDebug);
+ config.setValue("highlightMapPortals", mHighlightPortals);
+ config.setValue("highlightAttackRange", mHighlightAttackRange);
+ config.setValue("highlightMonsterAttackRange",
+ mHighlightMonsterAttackRange);
+ config.setValue("cyclePlayers", mCyclePlayers);
+ config.setValue("cycleMonsters", mCycleMonsters);
+ config.setValue("enableBotCheker", mEnableBotChecker);
+ config.setValue("floorItemsHighlight", mFloorItemsHighlight);
+ config.setValue("crazyMoveProgram", mMoveProgramField->getText());
+ config.setValue("afkMessage", mAfkField->getText());
+ config.setValue("tradebot", mTradeBot);
+ serverConfig.setValue("enableBuggyServers", mBuggyServers);
+ config.setValue("debugLog", mDebugLog);
+ config.setValue("serverAttack", mServerAttack);
+ config.setValue("autofixPos", mAutofixPos);
+ config.setValue("attackMoving", mAttackMoving);
+ config.setValue("quickStats", mQuickStats);
+ config.setValue("warpParticle", mWarpParticle);
+ config.setValue("autoShop", mAutoShop);
+ config.setValue("showMobHP", mShowMobHP);
+ logger->setDebugLog(mDebugLog);
+}
+
+void Setup_Other::externalUpdated()
+{
+ mBuggyServers = serverConfig.getValueBool("enableBuggyServers", true);
+ mBuggyServersCheckBox->setSelected(mBuggyServers);
+} \ No newline at end of file
diff --git a/src/gui/setup_other.h b/src/gui/setup_other.h
new file mode 100644
index 000000000..b20401be5
--- /dev/null
+++ b/src/gui/setup_other.h
@@ -0,0 +1,125 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef GUI_Setup_Other_H
+#define GUI_Setup_Other_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+
+class EditDialog;
+
+class Setup_Other : public SetupTab, public gcn::ActionListener
+{
+ public:
+ Setup_Other();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ virtual void externalUpdated();
+
+ private:
+ gcn::CheckBox *mShowMonstersTakedDamageCheckBox;
+ bool mShowMonstersTakedDamage;
+
+ gcn::CheckBox *mNoRainCheckBox;
+ bool mNoRain;
+
+ gcn::CheckBox *mTargetOnlyReachableCheckBox;
+ bool mTargetOnlyReachable;
+
+ int mOverlayDetail;
+ gcn::DropDown *mFontSizeDropDown;
+
+ gcn::CheckBox *mErrorsInDebugCheckBox;
+ bool mErrorsInDebug;
+
+ gcn::CheckBox *mHighlightPortalsCheckBox;
+ bool mHighlightPortals;
+
+ gcn::CheckBox *mHighlightAttackRangeCheckBox;
+ bool mHighlightAttackRange;
+
+ gcn::CheckBox *mHighlightMonsterAttackRangeCheckBox;
+ bool mHighlightMonsterAttackRange;
+
+ gcn::CheckBox *mCyclePlayersCheckBox;
+ bool mCyclePlayers;
+
+ gcn::CheckBox *mCycleMonstersCheckBox;
+ bool mCycleMonsters;
+
+ gcn::CheckBox *mEnableBotCheckerCheckBox;
+ bool mEnableBotChecker;
+
+ gcn::CheckBox *mFloorItemsHighlightCheckBox;
+ bool mFloorItemsHighlight;
+
+ gcn::Label *mMoveProgramLabel;
+ gcn::TextField *mMoveProgramField;
+ gcn::Button *mMoveProgramButton;
+ std::string mMoveProgram;
+
+ gcn::TextField *mAfkField;
+ gcn::Button *mAfkButton;
+ std::string mAfk;
+
+ gcn::CheckBox *mTradeBotCheckBox;
+ bool mTradeBot;
+
+ gcn::CheckBox *mBuggyServersCheckBox;
+ bool mBuggyServers;
+
+ gcn::CheckBox *mDebugLogCheckBox;
+ bool mDebugLog;
+
+ gcn::CheckBox *mServerAttackCheckBox;
+ bool mServerAttack;
+
+ gcn::CheckBox *mAutofixPosCheckBox;
+ bool mAutofixPos;
+
+ gcn::CheckBox *mAttackMovingCheckBox;
+ bool mAttackMoving;
+
+ gcn::CheckBox *mQuickStatsCheckBox;
+ bool mQuickStats;
+
+ gcn::CheckBox *mWarpParticleCheckBox;
+ bool mWarpParticle;
+
+ gcn::CheckBox *mAutoShopCheckBox;
+ bool mAutoShop;
+
+ gcn::CheckBox *mShowMobHPCheckBox;
+ bool mShowMobHP;
+
+ EditDialog *mEditDialog;
+};
+
+#endif
diff --git a/src/gui/setup_players.cpp b/src/gui/setup_players.cpp
new file mode 100644
index 000000000..6f4d50888
--- /dev/null
+++ b/src/gui/setup_players.cpp
@@ -0,0 +1,505 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/setup_players.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "gui/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/table.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include <string>
+#include <vector>
+
+#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 230
+#define RELATION_CHOICE_COLUMN_WIDTH 80
+
+#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column)
+
+static const char *table_titles[COLUMNS_NR] =
+{
+ N_("Name"),
+ N_("Relation")
+};
+
+static const char *RELATION_NAMES[PlayerRelation::RELATIONS_NR] =
+{
+ N_("Neutral"),
+ N_("Friend"),
+ N_("Disregarded"),
+ N_("Ignored"),
+ N_("Erased")
+};
+
+class PlayerRelationListModel : public gcn::ListModel
+{
+public:
+ virtual ~PlayerRelationListModel() { }
+
+ virtual int getNumberOfElements()
+ {
+ return PlayerRelation::RELATIONS_NR;
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "";
+ return gettext(RELATION_NAMES[i]);
+ }
+};
+
+class PlayerTableModel : public TableModel
+{
+public:
+ PlayerTableModel() :
+ mPlayers(NULL),
+ mListModel(new PlayerRelationListModel)
+ {
+ playerRelationsUpdated();
+ }
+
+ virtual ~PlayerTableModel()
+ {
+ freeWidgets();
+ delete mListModel;
+ mListModel = 0;
+ delete mPlayers;
+ mPlayers = 0;
+ }
+
+ virtual int getRows() const
+ {
+ if (mPlayers)
+ return static_cast<int>(mPlayers->size());
+ else
+ return 0;
+ }
+
+ virtual int getColumns() const
+ {
+ return COLUMNS_NR;
+ }
+
+ virtual int getRowHeight() const
+ {
+ return ROW_HEIGHT;
+ }
+
+ virtual int getColumnWidth(int index) const
+ {
+ if (index == NAME_COLUMN)
+ return NAME_COLUMN_WIDTH;
+ else
+ return RELATION_CHOICE_COLUMN_WIDTH;
+ }
+
+ virtual void playerRelationsUpdated()
+ {
+ signalBeforeUpdate();
+
+ freeWidgets();
+ std::vector<std::string> *player_names = player_relations.getPlayers();
+
+ if (!player_names)
+ return;
+
+ 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 Label(name);
+ mWidgets.push_back(widget);
+
+ gcn::DropDown *choicebox = new DropDown(mListModel);
+ choicebox->setSelected(player_relations.getRelation(name));
+ mWidgets.push_back(choicebox);
+ }
+
+ signalAfterUpdate();
+ }
+
+ virtual void updateModelInRow(int row)
+ {
+ gcn::DropDown *choicebox = static_cast<gcn::DropDown *>(
+ getElementAt(row, RELATION_CHOICE_COLUMN));
+ player_relations.setRelation(getPlayerAt(row),
+ static_cast<PlayerRelation::Relation>(
+ choicebox->getSelected()));
+ }
+
+
+ virtual gcn::Widget *getElementAt(int row, int column) const
+ {
+ return mWidgets[WIDGET_AT(row, column)];
+ }
+
+ virtual void freeWidgets()
+ {
+ delete mPlayers;
+ mPlayers = 0;
+
+ delete_all(mWidgets);
+ mWidgets.clear();
+ }
+
+ std::string getPlayerAt(int index) const
+ {
+ return (*mPlayers)[index];
+ }
+
+protected:
+ std::vector<std::string> *mPlayers;
+ std::vector<gcn::Widget *> mWidgets;
+ PlayerRelationListModel *mListModel;
+};
+
+/**
+ * Class for choosing one of the various `what to do when ignoring a player' options
+ */
+class IgnoreChoicesListModel : public gcn::ListModel
+{
+public:
+ virtual ~IgnoreChoicesListModel() { }
+
+ virtual int getNumberOfElements()
+ {
+ return static_cast<int>(player_relations.getPlayerIgnoreStrategies()
+ ->size());
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return (*player_relations.getPlayerIgnoreStrategies())
+ [i]->mDescription;
+ }
+};
+
+#define ACTION_DELETE "delete"
+#define ACTION_OLD "old"
+#define ACTION_TABLE "table"
+#define ACTION_STRATEGY "strategy"
+#define ACTION_WHISPER_TAB "whisper tab"
+#define ACTION_SHOW_GENDER "show gender"
+#define ACTION_SHOW_LEVEL "show level"
+#define ACTION_TARGET_DEAD "target dead"
+#define ACTION_SHOW_OWN_NAME "show own name"
+
+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)),
+ 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)),
+ mOldButton(new Button(_("Old"), ACTION_OLD, this)),
+ mWhisperTab(config.getBoolValue("whispertab")),
+ mWhisperTabCheckBox(new CheckBox(_("Put all whispers in tabs"),
+ mWhisperTab)),
+ mShowGender(config.getBoolValue("showgender")),
+ mShowGenderCheckBox(new CheckBox(_("Show gender"), mShowGender)),
+ mShowLevel(config.getBoolValue("showlevel")),
+ mShowOwnName(config.getBoolValue("showownname")),
+ mTargetDead(config.getBoolValue("targetDeadPlayers"))
+{
+ setName(_("Players"));
+
+ mPlayerTable->setOpaque(false);
+
+ mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH);
+ mPlayerTableTitleModel->fixColumnWidth(RELATION_CHOICE_COLUMN,
+ RELATION_CHOICE_COLUMN_WIDTH);
+ mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf));
+
+ mIgnoreActionChoicesModel = new IgnoreChoicesListModel;
+ mIgnoreActionChoicesBox = new DropDown(mIgnoreActionChoicesModel);
+
+ for (int i = 0; i < COLUMNS_NR; i++)
+ {
+ mPlayerTableTitleModel->set(0, i,
+ new Label(gettext(table_titles[i])));
+ }
+
+ mPlayerTitleTable->setLinewiseSelection(true);
+
+ mPlayerScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mPlayerTable->setActionEventId(ACTION_TABLE);
+ mPlayerTable->setLinewiseSelection(true);
+ mPlayerTable->addActionListener(this);
+
+ gcn::Label *ignore_action_label = new Label(_("When ignoring:"));
+
+ 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();
+
+ mWhisperTabCheckBox->setActionEventId(ACTION_WHISPER_TAB);
+ mWhisperTabCheckBox->addActionListener(this);
+
+ mShowGenderCheckBox->setActionEventId(ACTION_SHOW_GENDER);
+ mShowGenderCheckBox->addActionListener(this);
+
+ mShowLevelCheckBox = new CheckBox(_("Show level"), mShowLevel);
+ mShowLevelCheckBox->setActionEventId(ACTION_SHOW_LEVEL);
+ mShowLevelCheckBox->addActionListener(this);
+
+ mShowOwnNameCheckBox = new CheckBox(_("Show own name"), mShowOwnName);
+ mShowOwnNameCheckBox->setActionEventId(ACTION_SHOW_OWN_NAME);
+ mShowOwnNameCheckBox->addActionListener(this);
+
+ mTargetDeadCheckBox = new CheckBox(_("Target dead players"), mTargetDead);
+ mTargetDeadCheckBox->setActionEventId(ACTION_TARGET_DEAD);
+ mTargetDeadCheckBox->addActionListener(this);
+
+ reset();
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mPlayerTitleTable, 5);
+ place(0, 1, mPlayerScrollArea, 5, 4).setPadding(2);
+ place(0, 5, mDeleteButton);
+ place(0, 6, mShowGenderCheckBox, 3).setPadding(2);
+ place(0, 7, mShowLevelCheckBox, 3).setPadding(2);
+ place(0, 8, mShowOwnNameCheckBox, 3).setPadding(2);
+ place(1, 5, mOldButton, 1);
+ place(3, 5, ignore_action_label);
+ place(3, 6, mIgnoreActionChoicesBox, 2).setPadding(2);
+ place(3, 7, mDefaultTrading);
+ place(3, 8, mDefaultWhisper);
+ place(0, 9, mWhisperTabCheckBox, 4).setPadding(4);
+ place(0, 10, mTargetDeadCheckBox, 4).setPadding(4);
+
+ player_relations.addListener(this);
+
+ setDimension(gcn::Rectangle(0, 0, 500, 350));
+}
+
+Setup_Players::~Setup_Players()
+{
+ player_relations.removeListener(this);
+ delete mIgnoreActionChoicesModel;
+ mIgnoreActionChoicesModel = 0;
+}
+
+
+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.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));
+ config.setValue("whispertab", mWhisperTab);
+ config.setValue("showlevel", mShowLevel);
+ config.setValue("showownname", mShowOwnName);
+ config.setValue("targetDeadPlayers", mTargetDead);
+ config.setValue("showgender", mShowGender);
+
+ if (actorSpriteManager)
+ actorSpriteManager->updatePlayerNames();
+
+ if (player_node)
+ player_node->setCheckNameSetting(true);
+}
+
+void Setup_Players::cancel()
+{
+ mWhisperTab = config.getBoolValue("whispertab");
+ mWhisperTabCheckBox->setSelected(mWhisperTab);
+ mShowGender = config.getBoolValue("showgender");
+ mShowGenderCheckBox->setSelected(mShowGender);
+ mShowLevel = config.getBoolValue("showlevel");
+ mShowLevelCheckBox->setSelected(mShowLevel);
+ mShowOwnName = config.getBoolValue("showownname");
+ mShowOwnNameCheckBox->setSelected(mShowOwnName);
+ mTargetDead = config.getBoolValue("targetDeadPlayers");
+ mTargetDeadCheckBox->setSelected(mTargetDead);
+}
+
+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)
+ {
+ int player_index = mPlayerTable->getSelectedRow();
+
+ if (player_index < 0)
+ return;
+
+ std::string name = mPlayerTableModel->getPlayerAt(player_index);
+
+ player_relations.removePlayer(name);
+ }
+ else if (event.getId() == ACTION_OLD)
+ {
+ player_relations.load(true);
+ updateAll();
+ }
+ else if (event.getId() == ACTION_STRATEGY)
+ {
+ PlayerIgnoreStrategy *s =
+ (*player_relations.getPlayerIgnoreStrategies())[
+ mIgnoreActionChoicesBox->getSelected()];
+
+ player_relations.setPlayerIgnoreStrategy(s);
+ }
+ else if (event.getId() == ACTION_WHISPER_TAB)
+ {
+ mWhisperTab = mWhisperTabCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SHOW_GENDER)
+ {
+ mShowGender = mShowGenderCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SHOW_LEVEL)
+ {
+ mShowLevel = mShowLevelCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_SHOW_OWN_NAME)
+ {
+ mShowOwnName = mShowOwnNameCheckBox->isSelected();
+ }
+ else if (event.getId() == ACTION_TARGET_DEAD)
+ {
+ mTargetDead = mTargetDeadCheckBox->isSelected();
+ }
+}
+
+
+void Setup_Players::updatedPlayer(const std::string &name _UNUSED_)
+{
+ mPlayerTableModel->playerRelationsUpdated();
+ mDefaultTrading->setSelected(
+ player_relations.getDefault() & PlayerRelation::TRADE);
+ mDefaultWhisper->setSelected(
+ player_relations.getDefault() & PlayerRelation::WHISPER);
+ if (player_node)
+ player_node->updateName();
+}
+
+void Setup_Players::updateAll()
+{
+ PlayerTableModel *model = new PlayerTableModel();
+ mPlayerTable->setModel(model);
+ delete mPlayerTableModel;
+ mPlayerTableModel = model;
+ 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();
+ reset();
+}
+void Setup_Players::externalUpdated()
+{
+ mDefaultTrading->setSelected(
+ player_relations.getDefault() & PlayerRelation::TRADE);
+ mDefaultWhisper->setSelected(
+ player_relations.getDefault() & PlayerRelation::WHISPER);
+} \ No newline at end of file
diff --git a/src/gui/setup_players.h b/src/gui/setup_players.h
new file mode 100644
index 000000000..21d3bb3e8
--- /dev/null
+++ b/src/gui/setup_players.h
@@ -0,0 +1,95 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_SETUP_PLAYERS_H
+#define GUI_SETUP_PLAYERS_H
+
+#include "guichanfwd.h"
+#include "playerrelations.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class GuiTable;
+class PlayerTableModel;
+class StaticTableModel;
+
+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);
+
+ virtual void updateAll();
+
+ virtual void externalUpdated();
+
+private:
+ StaticTableModel *mPlayerTableTitleModel;
+ PlayerTableModel *mPlayerTableModel;
+ GuiTable *mPlayerTable;
+ GuiTable *mPlayerTitleTable;
+ gcn::ScrollArea *mPlayerScrollArea;
+
+ gcn::CheckBox *mDefaultTrading;
+ gcn::CheckBox *mDefaultWhisper;
+
+ gcn::Button *mDeleteButton;
+ gcn::Button *mOldButton;
+
+ gcn::ListModel *mIgnoreActionChoicesModel;
+ gcn::DropDown *mIgnoreActionChoicesBox;
+
+ bool mWhisperTab;
+ gcn::CheckBox *mWhisperTabCheckBox;
+
+ bool mShowGender;
+ gcn::CheckBox *mShowGenderCheckBox;
+
+ bool mShowLevel;
+ gcn::CheckBox *mShowLevelCheckBox;
+
+ bool mShowOwnName;
+ gcn::CheckBox *mShowOwnNameCheckBox;
+
+ bool mTargetDead;
+ gcn::CheckBox *mTargetDeadCheckBox;
+};
+
+#endif
diff --git a/src/gui/setup_theme.cpp b/src/gui/setup_theme.cpp
new file mode 100644
index 000000000..faaeeb00f
--- /dev/null
+++ b/src/gui/setup_theme.cpp
@@ -0,0 +1,239 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/setup_theme.h"
+
+#include "gui/gui.h"
+#include "gui/editdialog.h"
+#include "gui/okdialog.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/dropdown.h"
+
+#include "configuration.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "utils/gettext.h"
+
+#include "resources/resourcemanager.h"
+
+const char* ACTION_THEME = "theme";
+const char* ACTION_FONT = "font";
+const char* ACTION_BOLD_FONT = "bold font";
+const char* ACTION_PARTICLE_FONT = "particle font";
+const char* ACTION_HELP_FONT = "help font";
+
+class NamesModel : public gcn::ListModel
+{
+public:
+ NamesModel()
+ {
+ }
+
+ virtual ~NamesModel() { }
+
+ virtual int getNumberOfElements()
+ {
+ return static_cast<int>(mNames.size());
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return mNames[i];
+ }
+
+protected:
+ std::vector<std::string> mNames;
+};
+
+class ThemesModel : public NamesModel
+{
+public:
+ ThemesModel()
+ {
+ mNames.push_back("(default)");
+ Theme::fillSkinsList(mNames);
+ }
+
+ virtual ~ThemesModel()
+ { }
+};
+
+class FontsModel : public NamesModel
+{
+public:
+ FontsModel()
+ { Theme::fillFontsList(mNames); }
+
+ virtual ~FontsModel()
+ { }
+};
+
+Setup_Theme::Setup_Theme():
+ mTheme(config.getValue("theme", config.getValue("selectedSkin", ""))),
+ mFont(config.getStringValue("font")),
+ mBoldFont(config.getStringValue("boldFont")),
+ mParticleFont(config.getStringValue("particleFont")),
+ mHelpFont(config.getStringValue("helpFont"))
+{
+ setName(_("Theme"));
+
+ mThemeLabel = new Label(_("Gui theme"));
+ mFontLabel = new Label(_("Main Font"));
+ mBoldFontLabel = new Label(_("Bold font"));
+ mParticleFontLabel = new Label(_("Particle font"));
+ mHelpFontLabel = new Label(_("Help font"));
+ mThemesModel = new ThemesModel();
+ mFontsModel = new FontsModel();
+
+ mThemeDropDown = new DropDown(mThemesModel);
+ mThemeDropDown->setActionEventId(ACTION_THEME);
+ mThemeDropDown->addActionListener(this);
+
+ mFontDropDown = new DropDown(mFontsModel);
+ mFontDropDown->setActionEventId(ACTION_FONT);
+ mFontDropDown->addActionListener(this);
+
+ mBoldFontDropDown = new DropDown(mFontsModel);
+ mBoldFontDropDown->setActionEventId(ACTION_BOLD_FONT);
+ mBoldFontDropDown->addActionListener(this);
+
+ mParticleFontDropDown = new DropDown(mFontsModel);
+ mParticleFontDropDown->setActionEventId(ACTION_PARTICLE_FONT);
+ mParticleFontDropDown->addActionListener(this);
+
+ mHelpFontDropDown = new DropDown(mFontsModel);
+ mHelpFontDropDown->setActionEventId(ACTION_HELP_FONT);
+ mHelpFontDropDown->addActionListener(this);
+
+ std::string skin = Theme::getThemeName();
+ if (!skin.empty())
+ mThemeDropDown->setSelectedString(skin);
+ else
+ mThemeDropDown->setSelected(0);
+
+ mFontDropDown->setSelectedString(getFileName(
+ config.getStringValue("font")));
+ mBoldFontDropDown->setSelectedString(getFileName(
+ config.getStringValue("boldFont")));
+ mParticleFontDropDown->setSelectedString(getFileName(
+ config.getStringValue("particleFont")));
+ mHelpFontDropDown->setSelectedString(getFileName(
+ config.getStringValue("helpFont")));
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mThemeLabel, 10);
+ place(0, 1, mThemeDropDown, 6);
+ place(0, 2, mFontLabel, 10);
+ place(0, 3, mFontDropDown, 6);
+ place(0, 4, mBoldFontLabel, 10);
+ place(0, 5, mBoldFontDropDown, 6);
+ place(0, 6, mParticleFontLabel, 10);
+ place(0, 7, mParticleFontDropDown, 6);
+ place(0, 8, mHelpFontLabel, 10);
+ place(0, 9, mHelpFontDropDown, 6);
+
+ place.getCell().matchColWidth(0, 0);
+ place = h.getPlacer(0, 1);
+
+ setDimension(gcn::Rectangle(0, 0, 365, 500));
+}
+
+Setup_Theme::~Setup_Theme()
+{
+ delete mThemesModel;
+ mThemesModel = 0;
+
+ delete mFontsModel;
+ mFontsModel = 0;
+}
+
+void Setup_Theme::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == ACTION_THEME)
+ {
+ if (mThemeDropDown->getSelected() == 0)
+ mTheme = "";
+ else
+ mTheme = mThemeDropDown->getSelectedString();
+ }
+ else if (event.getId() == ACTION_FONT)
+ {
+ mFont = mFontDropDown->getSelectedString();
+ }
+ else if (event.getId() == ACTION_BOLD_FONT)
+ {
+ mBoldFont = mBoldFontDropDown->getSelectedString();
+ }
+ else if (event.getId() == ACTION_PARTICLE_FONT)
+ {
+ mParticleFont = mParticleFontDropDown->getSelectedString();
+ }
+ else if (event.getId() == ACTION_HELP_FONT)
+ {
+ mHelpFont = mHelpFontDropDown->getSelectedString();
+ }
+}
+
+void Setup_Theme::cancel()
+{
+ mTheme = config.getValue("theme", config.getValue("selectedSkin", ""));
+ mFont = getFileName(config.getStringValue("font"));
+ mBoldFont = getFileName(config.getStringValue("boldFont"));
+ mParticleFont = getFileName(config.getStringValue("particleFont"));
+ mHelpFont = getFileName(config.getStringValue("helpFont"));
+}
+
+void Setup_Theme::apply()
+{
+ if (config.getValue("theme",
+ config.getValue("selectedSkin", "")) != mTheme)
+ {
+ new OkDialog(_("Theme Changed"),
+ _("Restart your client for the change to take effect."));
+ }
+ config.setValue("selectedSkin", "");
+ config.setValue("theme", mTheme);
+ if (config.getValue("font", "dejavusans.ttf") != mFont
+ || config.getValue("boldFont", "dejavusans-bold.ttf") != mBoldFont
+ || config.getValue("particleFont", "dejavusans.ttf") != mParticleFont
+ || config.getValue("helpFont", "dejavusansmono.ttf") != mHelpFont)
+ {
+ config.setValue("font", "fonts/" + getFileName(mFont));
+ config.setValue("boldFont", "fonts/" + getFileName(mBoldFont));
+ config.setValue("particleFont", "fonts/" + getFileName(mParticleFont));
+ config.setValue("helpFont", "fonts/" + getFileName(mHelpFont));
+ gui->updateFonts();
+ }
+}
diff --git a/src/gui/setup_theme.h b/src/gui/setup_theme.h
new file mode 100644
index 000000000..6b2c99999
--- /dev/null
+++ b/src/gui/setup_theme.h
@@ -0,0 +1,74 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef GUI_Setup_Theme_H
+#define GUI_Setup_Theme_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+
+class FontsModel;
+class EditDialog;
+class DropDown;
+class ThemesModel;
+
+class Setup_Theme : public SetupTab, public gcn::ActionListener
+{
+ public:
+ Setup_Theme();
+ ~Setup_Theme();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ gcn::Label *mThemeLabel;
+ DropDown *mThemeDropDown;
+ std::string mTheme;
+ ThemesModel *mThemesModel;
+ FontsModel *mFontsModel;
+
+ gcn::Label *mFontLabel;
+ DropDown *mFontDropDown;
+ std::string mFont;
+
+ gcn::Label *mBoldFontLabel;
+ DropDown *mBoldFontDropDown;
+ std::string mBoldFont;
+
+ gcn::Label *mParticleFontLabel;
+ DropDown *mParticleFontDropDown;
+ std::string mParticleFont;
+
+ gcn::Label *mHelpFontLabel;
+ DropDown *mHelpFontDropDown;
+ std::string mHelpFont;
+
+ EditDialog *mEditDialog;
+};
+
+#endif
diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp
new file mode 100644
index 000000000..9399756aa
--- /dev/null
+++ b/src/gui/setup_video.cpp
@@ -0,0 +1,819 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/setup_video.h"
+
+#include "configuration.h"
+#include "game.h"
+#include "graphics.h"
+#include "localplayer.h"
+#include "log.h"
+#include "main.h"
+#include "particle.h"
+
+#include "gui/gui.h"
+#include "gui/okdialog.h"
+#include "gui/textdialog.h"
+
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/slider.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/dropdown.h"
+
+#include "resources/image.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/key.hpp>
+#include <guichan/listmodel.hpp>
+
+#include <SDL.h>
+
+#include <string>
+#include <vector>
+
+extern Graphics *graphics;
+
+/**
+ * The list model for mode list.
+ *
+ * \ingroup Interface
+ */
+class ModeListModel : public gcn::ListModel
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ModeListModel();
+
+ /**
+ * Destructor.
+ */
+ virtual ~ModeListModel()
+ { }
+
+ /**
+ * Returns the number of elements in container.
+ */
+ int getNumberOfElements()
+ { return static_cast<int>(mVideoModes.size()); }
+
+ /**
+ * Returns element from container.
+ */
+ std::string getElementAt(int i)
+ { return mVideoModes[i]; }
+
+ /**
+ * Returns the index corresponding to the given video mode.
+ * E.g.: "800x600".
+ * or -1 if not found.
+ */
+ int getIndexOf(const std::string &widthXHeightMode);
+
+ private:
+ std::vector<std::string> mVideoModes;
+};
+
+ModeListModel::ModeListModel()
+{
+ /* Get available fullscreen/hardware modes */
+ SDL_Rect **modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE);
+
+ /* Check which modes are available */
+ if (modes == static_cast<SDL_Rect **>(0))
+ {
+ logger->log1("No modes available");
+ }
+ else if (modes == (SDL_Rect **)-1)
+ {
+ logger->log1("All resolutions available");
+ }
+ else
+ {
+ for (int i = 0; modes[i]; ++i)
+ {
+ const std::string modeString =
+ toString(static_cast<int>(modes[i]->w)) + "x"
+ + toString(static_cast<int>(modes[i]->h));
+ mVideoModes.push_back(modeString);
+ }
+ }
+ mVideoModes.push_back("custom");
+}
+
+int ModeListModel::getIndexOf(const std::string &widthXHeightMode)
+{
+ std::string currentMode = "";
+ for (int i = 0; i < getNumberOfElements(); i++)
+ {
+ currentMode = getElementAt(i);
+ if (currentMode == widthXHeightMode)
+ return i;
+ }
+ return -1;
+}
+
+const char *SIZE_NAME[6] =
+{
+ N_("Tiny (10)"),
+ N_("Small (11)"),
+ N_("Medium (12)"),
+ N_("Large (13)"),
+ N_("Big (14)"),
+ N_("Huge (15)"),
+};
+
+class FontSizeChoiceListModel : public gcn::ListModel
+{
+public:
+ virtual ~FontSizeChoiceListModel()
+ { }
+
+ virtual int getNumberOfElements()
+ { return 6; }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return SIZE_NAME[i];
+ }
+};
+
+const char *OPENGL_NAME[3] =
+{
+ N_("Software"),
+ N_("Fast OpenGL"),
+ N_("Safe OpenGL"),
+};
+
+class OpenGLListModel : public gcn::ListModel
+{
+public:
+ virtual ~OpenGLListModel()
+ { }
+
+ virtual int getNumberOfElements()
+ { return 3; }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return OPENGL_NAME[i];
+ }
+};
+
+static const char *speechModeToString(Being::Speech mode)
+{
+ switch (mode)
+ {
+ case Being::NO_SPEECH:
+ default:
+ return _("No text");
+ case Being::TEXT_OVERHEAD:
+ return _("Text");
+ case Being::NO_NAME_IN_BUBBLE:
+ return _("Bubbles, no names");
+ case Being::NAME_IN_BUBBLE:
+ return _("Bubbles with names");
+ }
+ return "";
+}
+
+const char *Setup_Video::overlayDetailToString(int detail)
+{
+ if (detail == -1)
+ detail = config.getIntValue("OverlayDetail");
+
+ switch (detail)
+ {
+ case 0:
+ return _("off");
+ case 1:
+ return _("low");
+ case 2:
+ return _("high");
+ default:
+ return "";
+ }
+ return "";
+}
+
+const char *Setup_Video::particleDetailToString(int detail)
+{
+ if (detail == -1)
+ detail = 3 - config.getIntValue("particleEmitterSkip");
+
+ switch (detail)
+ {
+ case 0:
+ return _("low");
+ case 1:
+ return _("medium");
+ case 2:
+ return _("high");
+ case 3:
+ return _("max");
+ default:
+ return "";
+ }
+ return "";
+}
+
+Setup_Video::Setup_Video():
+ mFullScreenEnabled(config.getBoolValue("screen")),
+ mOpenGLEnabled(config.getIntValue("opengl")),
+ mHwAccelEnabled(config.getBoolValue("hwaccel")),
+ mCustomCursorEnabled(config.getBoolValue("customcursor")),
+ mVisibleNamesEnabled(config.getBoolValue("visiblenames")),
+ mParticleEffectsEnabled(config.getBoolValue("particleeffects")),
+ mNPCLogEnabled(config.getBoolValue("logNpcInGui")),
+ mPickupChatEnabled(config.getBoolValue("showpickupchat")),
+ mPickupParticleEnabled(config.getBoolValue("showpickupparticle")),
+ mOpacity(config.getFloatValue("guialpha")),
+ mFps(config.getIntValue("fpslimit")),
+ mAltFps(config.getIntValue("altfpslimit")),
+ mHideShieldSprite(config.getBoolValue("hideShield")),
+ mLowTraffic(config.getBoolValue("lowTraffic")),
+ mSyncPlayerMove(config.getBoolValue("syncPlayerMove")),
+ mDrawHotKeys(config.getBoolValue("drawHotKeys")),
+ mDrawPath(config.getBoolValue("drawPath")),
+ mShowJob(serverConfig.getBoolValue("showJob")),
+ mAlphaCache(config.getBoolValue("alphaCache")),
+ mShowBackground(config.getBoolValue("showBackground")),
+ mSpeechMode(static_cast<Being::Speech>(
+ config.getIntValue("speech"))),
+ mModeListModel(new ModeListModel),
+ mModeList(new ListBox(mModeListModel)),
+ mFsCheckBox(new CheckBox(_("Full screen"), mFullScreenEnabled)),
+ mHwAccelCheckBox(new CheckBox(_("Hw acceleration"), mHwAccelEnabled)),
+ mCustomCursorCheckBox(new CheckBox(_("Custom cursor"),
+ mCustomCursorEnabled)),
+ mVisibleNamesCheckBox(new CheckBox(_("Visible names"),
+ mVisibleNamesEnabled)),
+ mParticleEffectsCheckBox(new CheckBox(_("Particle effects"),
+ mParticleEffectsEnabled)),
+ mNPCLogCheckBox(new CheckBox(_("Log NPC dialogue"), mNPCLogEnabled)),
+ mPickupNotifyLabel(new Label(_("Show pickup notification"))),
+ // TRANSLATORS: Refers to "Show own name"
+ mPickupChatCheckBox(new CheckBox(_("in chat"), mPickupChatEnabled)),
+ // TRANSLATORS: Refers to "Show own name"
+ mPickupParticleCheckBox(new CheckBox(_("as particle"),
+ mPickupParticleEnabled)),
+ mHideShieldSpriteCheckBox(new CheckBox(_("Hide shield sprite"),
+ mHideShieldSprite)),
+ mLowTrafficCheckBox(new CheckBox(_("Low traffic mode"),
+ mLowTraffic)),
+ mSyncPlayerMoveCheckBox(new CheckBox(_("Sync player move"),
+ mSyncPlayerMove)),
+ mDrawHotKeysCheckBox(new CheckBox(_("Draw hotkeys on map"),
+ mDrawHotKeys)),
+ mDrawPathCheckBox(new CheckBox(_("Draw path"), mDrawPath)),
+ mShowJobCheckBox(new CheckBox(_("Show job"), mShowJob)),
+ mAlphaCacheCheckBox(new CheckBox(_("Enable opacity cache"), mAlphaCache)),
+ mShowBackgroundCheckBox(new CheckBox(_("Show background"),
+ mShowBackground)),
+ mSpeechSlider(new Slider(0, 3)),
+ mSpeechLabel(new Label("")),
+ mAlphaSlider(new Slider(0.1, 1.0)),
+ mFpsCheckBox(new CheckBox(_("FPS limit:"))),
+ mFpsSlider(new Slider(2, 160)),
+ mFpsLabel(new Label),
+ mAltFpsSlider(new Slider(2, 160)),
+ mAltFpsLabel(new Label(_("Alt FPS limit: "))),
+ mOverlayDetail(config.getIntValue("OverlayDetail")),
+ mOverlayDetailSlider(new Slider(0, 2)),
+ mOverlayDetailField(new Label),
+ mParticleDetail(3 - config.getIntValue("particleEmitterSkip")),
+ mParticleDetailSlider(new Slider(0, 3)),
+ mParticleDetailField(new Label),
+ mFontSize(config.getIntValue("fontSize")),
+ mDialog(0)
+{
+ setName(_("Video"));
+
+ ScrollArea *scrollArea = new ScrollArea(mModeList);
+ scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ speechLabel = new Label(_("Overhead text"));
+ alphaLabel = new Label(_("Gui opacity"));
+ overlayDetailLabel = new Label(_("Ambient FX"));
+ particleDetailLabel = new Label(_("Particle detail"));
+ fontSizeLabel = new Label(_("Font size"));
+
+ mOpenGLListModel = new OpenGLListModel;
+ mOpenGLDropDown = new DropDown(mOpenGLListModel),
+ mOpenGLDropDown->setSelected(mOpenGLEnabled);
+
+ mFontSizeListModel = new FontSizeChoiceListModel;
+ mFontSizeDropDown = new DropDown(mFontSizeListModel);
+
+ mModeList->setEnabled(true);
+
+#ifndef USE_OPENGL
+ mOpenGLDropDown->setSelected(0);
+#endif
+
+ mAlphaSlider->setValue(mOpacity);
+ mAlphaSlider->setWidth(90);
+
+ mFpsLabel->setCaption(mFps > 0 ? toString(mFps) : _("None"));
+ mFpsLabel->setWidth(60);
+ mAltFpsLabel->setCaption(_("Alt FPS limit: ") + (mAltFps > 0
+ ? toString(mAltFps) : _("None")));
+ mAltFpsLabel->setWidth(150);
+ mFpsSlider->setValue(mFps);
+ mFpsSlider->setEnabled(mFps > 0);
+ mAltFpsSlider->setValue(mAltFps);
+ mAltFpsSlider->setEnabled(mAltFps > 0);
+ mFpsCheckBox->setSelected(mFps > 0);
+
+ // Pre-select the current video mode.
+ std::string videoMode = toString(graphics->getWidth()) + "x"
+ + toString(graphics->getHeight());
+ mModeList->setSelected(mModeListModel->getIndexOf(videoMode));
+
+ mModeList->setActionEventId("videomode");
+ mCustomCursorCheckBox->setActionEventId("customcursor");
+ mVisibleNamesCheckBox->setActionEventId("visiblenames");
+ mParticleEffectsCheckBox->setActionEventId("particleeffects");
+ mPickupChatCheckBox->setActionEventId("pickupchat");
+ mPickupParticleCheckBox->setActionEventId("pickupparticle");
+ mNPCLogCheckBox->setActionEventId("lognpc");
+ mAlphaSlider->setActionEventId("guialpha");
+ mFpsCheckBox->setActionEventId("fpslimitcheckbox");
+ mSpeechSlider->setActionEventId("speech");
+ mFpsSlider->setActionEventId("fpslimitslider");
+ mAltFpsSlider->setActionEventId("altfpslimitslider");
+ mOverlayDetailSlider->setActionEventId("overlaydetailslider");
+ mOverlayDetailField->setActionEventId("overlaydetailfield");
+ mParticleDetailSlider->setActionEventId("particledetailslider");
+ mParticleDetailField->setActionEventId("particledetailfield");
+ mHideShieldSpriteCheckBox->setActionEventId("hideshieldsprite1");
+ mLowTrafficCheckBox->setActionEventId("lowTraffic1");
+ mSyncPlayerMoveCheckBox->setActionEventId("syncPlayerMove1");
+ mDrawHotKeysCheckBox->setActionEventId("drawHotKeys");
+ mDrawPathCheckBox->setActionEventId("drawPath1");
+ mShowJobCheckBox->setActionEventId("showJob");
+ mAlphaCacheCheckBox->setActionEventId("alphaCache");
+
+ mModeList->addActionListener(this);
+ mCustomCursorCheckBox->addActionListener(this);
+ mVisibleNamesCheckBox->addActionListener(this);
+ mParticleEffectsCheckBox->addActionListener(this);
+ mPickupChatCheckBox->addActionListener(this);
+ mPickupParticleCheckBox->addActionListener(this);
+ mNPCLogCheckBox->addActionListener(this);
+ mAlphaSlider->addActionListener(this);
+ mFpsCheckBox->addActionListener(this);
+ mSpeechSlider->addActionListener(this);
+ mFpsSlider->addActionListener(this);
+ mAltFpsSlider->addActionListener(this);
+ mOverlayDetailSlider->addActionListener(this);
+ mOverlayDetailField->addKeyListener(this);
+ mParticleDetailSlider->addActionListener(this);
+ mParticleDetailField->addKeyListener(this);
+ mHideShieldSpriteCheckBox->addKeyListener(this);
+ mLowTrafficCheckBox->addKeyListener(this);
+ mSyncPlayerMoveCheckBox->addKeyListener(this);
+ mDrawHotKeysCheckBox->addKeyListener(this);
+ mDrawPathCheckBox->addKeyListener(this);
+ mShowJobCheckBox->addKeyListener(this);
+ mAlphaCacheCheckBox->addKeyListener(this);
+
+ mSpeechLabel->setCaption(speechModeToString(mSpeechMode));
+ mSpeechSlider->setValue(mSpeechMode);
+
+ mOverlayDetailField->setCaption(overlayDetailToString(mOverlayDetail));
+ mOverlayDetailSlider->setValue(mOverlayDetail);
+
+ mParticleDetailField->setCaption(particleDetailToString(mParticleDetail));
+ mParticleDetailSlider->setValue(mParticleDetail);
+
+ mFontSizeDropDown->setSelected(mFontSize - 10);
+ mFontSizeDropDown->adjustHeight();
+
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, scrollArea, 1, 5).setPadding(2);
+ place(1, 0, mFsCheckBox, 2);
+ place(3, 0, mOpenGLDropDown, 1);
+
+ place(1, 1, mCustomCursorCheckBox, 3);
+ place(3, 1, mHwAccelCheckBox, 1);
+
+ place(1, 2, mVisibleNamesCheckBox, 3);
+
+ place(3, 2, mAlphaCacheCheckBox, 4);
+
+ place(1, 3, mParticleEffectsCheckBox, 2);
+
+ place(1, 4, mPickupNotifyLabel, 4);
+
+ place(1, 5, mPickupChatCheckBox, 1);
+ place(2, 5, mPickupParticleCheckBox, 2);
+
+ place(0, 6, fontSizeLabel, 3);
+ place(1, 6, mFontSizeDropDown, 1);
+
+ place(0, 7, mAlphaSlider);
+ place(1, 7, alphaLabel, 3);
+
+ place(0, 8, mFpsSlider);
+ place(1, 8, mFpsCheckBox).setPadding(3);
+ place(2, 8, mFpsLabel).setPadding(1);
+
+ place(0, 9, mAltFpsSlider);
+ place(1, 9, mAltFpsLabel).setPadding(3);
+
+ place(0, 10, mSpeechSlider);
+ place(1, 10, speechLabel);
+ place(2, 10, mSpeechLabel, 3).setPadding(2);
+
+ place(0, 11, mOverlayDetailSlider);
+ place(1, 11, overlayDetailLabel);
+ place(2, 11, mOverlayDetailField, 3).setPadding(2);
+
+ place(0, 12, mParticleDetailSlider);
+ place(1, 12, particleDetailLabel);
+ place(2, 12, mParticleDetailField, 3).setPadding(2);
+
+ place(3, 7, mHideShieldSpriteCheckBox);
+ place(3, 8, mLowTrafficCheckBox);
+ place(0, 13, mShowBackgroundCheckBox);
+ place(0, 14, mSyncPlayerMoveCheckBox);
+ place(0, 15, mDrawHotKeysCheckBox);
+ place(2, 13, mDrawPathCheckBox, 2);
+ place(2, 14, mShowJobCheckBox, 2);
+ place(2, 15, mNPCLogCheckBox, 2);
+
+ int width = 600;
+
+ if (config.getIntValue("screenwidth") >= 730)
+ width += 100;
+
+ setDimension(gcn::Rectangle(0, 0, width, 300));
+}
+
+Setup_Video::~Setup_Video()
+{
+ delete mModeListModel;
+ mModeListModel = 0;
+ delete mModeList;
+ mModeList = 0;
+ delete mFontSizeListModel;
+ mFontSizeListModel = 0;
+ delete mOpenGLListModel;
+ mOpenGLListModel = 0;
+ delete mDialog;
+ mDialog = 0;
+}
+
+void Setup_Video::apply()
+{
+ // Full screen changes
+ bool fullscreen = mFsCheckBox->isSelected();
+ if (fullscreen != config.getBoolValue("screen"))
+ {
+ /* The OpenGL test is only necessary on Windows, since switching
+ * to/from full screen works fine on Linux. On Windows we'd have to
+ * reinitialize the OpenGL state and reload all textures.
+ *
+ * See http://libsdl.org/cgi/docwiki.cgi/SDL_SetVideoMode
+ */
+
+#if defined(WIN32) || defined(__APPLE__)
+ // checks for opengl usage
+ if (!config.getIntValue("opengl"))
+ {
+#endif
+ if (!graphics->setFullscreen(fullscreen))
+ {
+ fullscreen = !fullscreen;
+ if (!graphics->setFullscreen(fullscreen))
+ {
+ std::stringstream errorMessage;
+ if (fullscreen)
+ {
+ errorMessage << _("Failed to switch to windowed mode "
+ "and restoration of old mode also "
+ "failed!") << std::endl;
+ }
+ else
+ {
+ errorMessage << _("Failed to switch to fullscreen mode"
+ " and restoration of old mode also "
+ "failed!") << std::endl;
+ }
+ logger->error(errorMessage.str());
+ }
+ }
+#if defined(WIN32) || defined(__APPLE__)
+ }
+ else
+ {
+ new OkDialog(_("Switching to Full Screen"),
+ _("Restart needed for changes to take effect."));
+ }
+#endif
+ config.setValue("screen", fullscreen);
+ }
+
+ // OpenGL change
+ if (mOpenGLDropDown->getSelected() != mOpenGLEnabled)
+ {
+ config.setValue("opengl", mOpenGLDropDown->getSelected());
+
+ // OpenGL can currently only be changed by restarting, notify user.
+ new OkDialog(_("Changing to OpenGL"),
+ _("Applying change to OpenGL requires restart."));
+ }
+
+ mFps = mFpsCheckBox->isSelected() ?
+ static_cast<int>(mFpsSlider->getValue()) : 0;
+
+ mAltFps = static_cast<int>(mAltFpsSlider->getValue());
+
+ mFpsSlider->setEnabled(mFps > 0);
+
+ mAltFpsSlider->setEnabled(mAltFps > 0);
+
+ config.setValue("hwaccel", mHwAccelCheckBox->isSelected());
+
+ // FPS change
+ config.setValue("fpslimit", mFps);
+ config.setValue("altfpslimit", mAltFps);
+
+ if (config.getIntValue("fontSize")
+ != static_cast<int>(mFontSizeDropDown->getSelected()) + 10)
+ {
+ config.setValue("fontSize", mFontSizeDropDown->getSelected() + 10);
+ gui->updateFonts();
+ }
+
+ config.setValue("hideShield", mHideShieldSpriteCheckBox->isSelected());
+ config.setValue("lowTraffic", mLowTrafficCheckBox->isSelected());
+ config.setValue("syncPlayerMove", mSyncPlayerMoveCheckBox->isSelected());
+ config.setValue("drawHotKeys", mDrawHotKeysCheckBox->isSelected());
+ config.setValue("drawPath", mDrawPathCheckBox->isSelected());
+ serverConfig.setValue("showJob", mShowJobCheckBox->isSelected());
+ config.setValue("alphaCache", mAlphaCacheCheckBox->isSelected());
+ config.setValue("showBackground", mShowBackgroundCheckBox->isSelected());
+
+ // We sync old and new values at apply time
+ mFullScreenEnabled = config.getBoolValue("screen");
+ mCustomCursorEnabled = config.getBoolValue("customcursor");
+ mVisibleNamesEnabled = config.getBoolValue("visiblenames");
+ mParticleEffectsEnabled = config.getBoolValue("particleeffects");
+ mNPCLogEnabled = config.getBoolValue("logNpcInGui");
+ mHideShieldSprite = config.getBoolValue("hideShield");
+ mLowTraffic = config.getBoolValue("lowTraffic");
+ mSyncPlayerMove = config.getBoolValue("syncPlayerMove");
+ mDrawHotKeys = config.getBoolValue("drawHotKeys");
+ mDrawPath = config.getBoolValue("drawPath");
+ mShowJob = serverConfig.getBoolValue("showJob");
+ mAlphaCache = config.getBoolValue("alphaCache");
+ mShowBackground = config.getBoolValue("showBackground");
+
+ mSpeechMode = static_cast<Being::Speech>(
+ config.getIntValue("speech"));
+ mOpacity = config.getFloatValue("guialpha");
+ mOverlayDetail = config.getIntValue("OverlayDetail");
+ mOpenGLEnabled = config.getIntValue("opengl");
+ mHwAccelEnabled = config.getBoolValue("hwaccel");
+ mPickupChatEnabled = config.getBoolValue("showpickupchat");
+ mPickupParticleEnabled = config.getBoolValue("showpickupparticle");
+}
+
+void Setup_Video::cancel()
+{
+ mFpsCheckBox->setSelected(mFps > 0);
+ mFsCheckBox->setSelected(mFullScreenEnabled);
+ mOpenGLDropDown->setSelected(mOpenGLEnabled);
+ mHwAccelCheckBox->setSelected(mHwAccelEnabled);
+ mCustomCursorCheckBox->setSelected(mCustomCursorEnabled);
+ mVisibleNamesCheckBox->setSelected(mVisibleNamesEnabled);
+ mParticleEffectsCheckBox->setSelected(mParticleEffectsEnabled);
+ mFpsSlider->setValue(mFps);
+ mFpsSlider->setEnabled(mFps > 0);
+ mAltFpsSlider->setValue(mAltFps);
+ mAltFpsSlider->setEnabled(mAltFps > 0);
+ mSpeechSlider->setValue(mSpeechMode);
+ mNPCLogCheckBox->setSelected(mNPCLogEnabled);
+ mHideShieldSpriteCheckBox->setSelected(mHideShieldSprite);
+ mLowTrafficCheckBox->setSelected(mLowTraffic);
+ mSyncPlayerMoveCheckBox->setSelected(mSyncPlayerMove);
+ mDrawHotKeysCheckBox->setSelected(mDrawHotKeys);
+ mDrawPathCheckBox->setSelected(mDrawPath);
+ mShowJobCheckBox->setSelected(mShowJob);
+ mAlphaCacheCheckBox->setSelected(mAlphaCache);
+ mShowBackgroundCheckBox->setSelected(mShowBackground);
+ mAlphaSlider->setValue(mOpacity);
+ mOverlayDetailSlider->setValue(mOverlayDetail);
+ mParticleDetailSlider->setValue(mParticleDetail);
+ mFpsLabel->setCaption(mFpsCheckBox->isSelected()
+ ? toString(mFps) : _("None"));
+ mAltFpsLabel->setCaption(_("Alt FPS limit: ") + toString(mAltFps));
+
+ config.setValue("screen", mFullScreenEnabled);
+
+ // Set back to the current video mode.
+ std::string videoMode = toString(graphics->getWidth()) + "x"
+ + toString(graphics->getHeight());
+ mModeList->setSelected(mModeListModel->getIndexOf(videoMode));
+ config.setValue("screenwidth", graphics->getWidth());
+ config.setValue("screenheight", graphics->getHeight());
+
+ config.setValue("customcursor", mCustomCursorEnabled);
+ config.setValue("visiblenames", mVisibleNamesEnabled);
+ config.setValue("particleeffects", mParticleEffectsEnabled);
+ config.setValue("speech", static_cast<int>(mSpeechMode));
+ config.setValue("logNpcInGui", mNPCLogEnabled);
+ config.setValue("hideShield", mHideShieldSprite);
+ config.setValue("lowTraffic", mLowTraffic);
+ config.setValue("syncPlayerMove", mSyncPlayerMove);
+ config.setValue("drawHotKeys", mDrawHotKeys);
+ config.setValue("drawPath", mDrawPath);
+ serverConfig.setValue("showJob", mShowJob);
+ config.setValue("alphaCache", mAlphaCache);
+ config.setValue("showBackground", mShowBackground);
+ config.setValue("guialpha", mOpacity);
+ Image::setEnableAlpha(mOpacity != 1.0f);
+ config.setValue("opengl", mOpenGLEnabled);
+ config.setValue("hwaccel", mHwAccelEnabled);
+ config.setValue("showpickupchat", mPickupChatEnabled);
+ config.setValue("showpickupparticle", mPickupParticleEnabled);
+}
+
+void Setup_Video::action(const gcn::ActionEvent &event)
+{
+ const std::string &id = event.getId();
+
+ if (id == "videomode")
+ {
+ std::string mode = mModeListModel->getElementAt(
+ mModeList->getSelected());
+
+ if (mode == "custom")
+ {
+ if (mDialog)
+ {
+ mode = mDialog->getText();
+ mDialog = 0;
+ }
+ else
+ {
+ mDialog = new TextDialog(
+ _("Custom resolution (example: 1024x768)"),
+ _("Enter new resolution: "));
+ mDialog->setActionEventId("videomode");
+ mDialog->addActionListener(this);
+ return;
+ }
+ }
+ const int width = atoi(mode.substr(0, mode.find("x")).c_str());
+ const int height = atoi(mode.substr(mode.find("x") + 1).c_str());
+ if (!width || !height)
+ return;
+
+ // TODO: Find out why the drawing area doesn't resize without a restart.
+ if (width != graphics->getWidth() || height != graphics->getHeight())
+ {
+ if (width < graphics->getWidth() || height < graphics->getHeight())
+ new OkDialog(_("Screen Resolution Changed"),
+ _("Restart your client for the change to take effect.")
+ + std::string("\n") +
+ _("Some windows may be moved to fit the lowered resolution."));
+ else
+ new OkDialog(_("Screen Resolution Changed"),
+ _("Restart your client for the change to take effect."));
+ }
+
+ config.setValue("screenwidth", width);
+ config.setValue("screenheight", height);
+ }
+ if (id == "~videomode")
+ {
+ mDialog = 0;
+ }
+ else if (id == "guialpha")
+ {
+ config.setValue("guialpha", mAlphaSlider->getValue());
+ Image::setEnableAlpha(config.getFloatValue("guialpha") != 1.0f);
+ }
+ else if (id == "customcursor")
+ {
+ config.setValue("customcursor", mCustomCursorCheckBox->isSelected());
+ }
+ else if (id == "visiblenames")
+ {
+ config.setValue("visiblenames", mVisibleNamesCheckBox->isSelected());
+ }
+ else if (id == "particleeffects")
+ {
+ config.setValue("particleeffects",
+ mParticleEffectsCheckBox->isSelected());
+ Particle::enabled = mParticleEffectsCheckBox->isSelected();
+
+ if (Game::instance())
+ {
+ new OkDialog(_("Particle Effect Settings Changed."),
+ _("Changes will take effect on map change."));
+ }
+ }
+ else if (id == "pickupchat")
+ {
+ config.setValue("showpickupchat", mPickupChatCheckBox->isSelected());
+ }
+ else if (id == "pickupparticle")
+ {
+ config.setValue("showpickupparticle",
+ mPickupParticleCheckBox->isSelected());
+ }
+ else if (id == "speech")
+ {
+ Being::Speech val = static_cast<Being::Speech>(
+ mSpeechSlider->getValue());
+ mSpeechLabel->setCaption(speechModeToString(val));
+ mSpeechSlider->setValue(val);
+ config.setValue("speech", (int)val);
+ }
+ else if (id == "lognpc")
+ {
+ config.setValue("logNpcInGui", mNPCLogCheckBox->isSelected());
+ }
+ else if (id == "overlaydetailslider")
+ {
+ int val = static_cast<int>(mOverlayDetailSlider->getValue());
+ mOverlayDetailField->setCaption(overlayDetailToString(val));
+ config.setValue("OverlayDetail", val);
+ }
+ else if (id == "particledetailslider")
+ {
+ int val = static_cast<int>(mParticleDetailSlider->getValue());
+ mParticleDetailField->setCaption(particleDetailToString(val));
+ config.setValue("particleEmitterSkip", 3 - val);
+ Particle::emitterSkip = 4 - val;
+ }
+ else if (id == "fpslimitcheckbox" || id == "fpslimitslider")
+ {
+ int fps = static_cast<int>(mFpsSlider->getValue());
+ if (id == "fpslimitcheckbox" && !mFpsSlider->isEnabled())
+ fps = 60;
+ else
+ fps = fps > 0 ? fps : 60;
+ mFps = mFpsCheckBox->isSelected() ? fps : 0;
+ const std::string text = mFps > 0 ? toString(mFps) : _("None");
+
+ mFpsLabel->setCaption(text);
+ mFpsSlider->setValue(mFps);
+ mFpsSlider->setEnabled(mFps > 0);
+ }
+ else if (id == "altfpslimitslider")
+ {
+ int fps = static_cast<int>(mAltFpsSlider->getValue());
+ fps = fps > 0 ? fps : static_cast<int>(mAltFpsSlider->getScaleStart());
+ mAltFps = fps;
+ const std::string text = mAltFps > 0 ? toString(mAltFps) : _("None");
+
+ mAltFpsLabel->setCaption(_("Alt FPS limit: ") + text);
+ mAltFpsSlider->setValue(mAltFps);
+ mAltFpsSlider->setEnabled(mAltFps > 0);
+ }
+}
+
+void Setup_Video::externalUpdated()
+{
+ mShowJob = serverConfig.getBoolValue("showJob");
+ mShowJobCheckBox->setSelected(mShowJob);
+} \ No newline at end of file
diff --git a/src/gui/setup_video.h b/src/gui/setup_video.h
new file mode 100644
index 000000000..c9a72666a
--- /dev/null
+++ b/src/gui/setup_video.h
@@ -0,0 +1,136 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_SETUP_VIDEO_H
+#define GUI_SETUP_VIDEO_H
+
+#include "being.h"
+#include "guichanfwd.h"
+
+#include "gui/widgets/setuptab.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+class FontSizeChoiceListModel;
+class ModeListModel;
+class OpenGLListModel;
+class TextDialog;
+
+class Setup_Video : public SetupTab, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ Setup_Video();
+ ~Setup_Video();
+
+ void apply();
+ void cancel();
+
+ void action(const gcn::ActionEvent &event);
+
+ static const char *overlayDetailToString(int detail = -1);
+
+ static const char *particleDetailToString(int detail = -1);
+
+ virtual void externalUpdated();
+
+ private:
+ bool mFullScreenEnabled;
+ int mOpenGLEnabled;
+ bool mHwAccelEnabled;
+ bool mCustomCursorEnabled;
+ bool mVisibleNamesEnabled;
+ bool mParticleEffectsEnabled;
+ bool mNPCLogEnabled;
+ bool mPickupChatEnabled;
+ bool mPickupParticleEnabled;
+ double mOpacity;
+ int mFps;
+ int mAltFps;
+ bool mHideShieldSprite;
+ bool mLowTraffic;
+ bool mSyncPlayerMove;
+ bool mDrawHotKeys;
+ bool mDrawPath;
+ bool mShowJob;
+ bool mAlphaCache;
+ bool mShowBackground;
+ Being::Speech mSpeechMode;
+
+ ModeListModel *mModeListModel;
+ FontSizeChoiceListModel *mFontSizeListModel;
+
+ OpenGLListModel *mOpenGLListModel;
+
+ gcn::Label *speechLabel;
+ gcn::Label *alphaLabel;
+ gcn::Label *scrollRadiusLabel;
+ gcn::Label *scrollLazinessLabel;
+ gcn::Label *overlayDetailLabel;
+ gcn::Label *particleDetailLabel;
+ gcn::Label *fontSizeLabel;
+
+ gcn::ListBox *mModeList;
+ gcn::CheckBox *mFsCheckBox;
+ gcn::DropDown *mOpenGLDropDown;
+ gcn::CheckBox *mHwAccelCheckBox;
+ gcn::CheckBox *mCustomCursorCheckBox;
+ gcn::CheckBox *mVisibleNamesCheckBox;
+ gcn::CheckBox *mParticleEffectsCheckBox;
+ gcn::CheckBox *mNPCLogCheckBox;
+
+ gcn::Label *mPickupNotifyLabel;
+ gcn::CheckBox *mPickupChatCheckBox;
+ gcn::CheckBox *mPickupParticleCheckBox;
+
+ gcn::CheckBox *mHideShieldSpriteCheckBox;
+ gcn::CheckBox *mLowTrafficCheckBox;
+ gcn::CheckBox *mSyncPlayerMoveCheckBox;
+ gcn::CheckBox *mDrawHotKeysCheckBox;
+ gcn::CheckBox *mDrawPathCheckBox;
+ gcn::CheckBox *mShowJobCheckBox;
+ gcn::CheckBox *mAlphaCacheCheckBox;
+ gcn::CheckBox *mShowBackgroundCheckBox;
+ gcn::Slider *mSpeechSlider;
+ gcn::Label *mSpeechLabel;
+ gcn::Slider *mAlphaSlider;
+ gcn::CheckBox *mFpsCheckBox;
+ gcn::Slider *mFpsSlider;
+ gcn::Label *mFpsLabel;
+// gcn::CheckBox *mAltFpsCheckBox;
+ gcn::Slider *mAltFpsSlider;
+ gcn::Label *mAltFpsLabel;
+
+ int mOverlayDetail;
+ gcn::Slider *mOverlayDetailSlider;
+ gcn::Label *mOverlayDetailField;
+
+ int mParticleDetail;
+ gcn::Slider *mParticleDetailSlider;
+ gcn::Label *mParticleDetailField;
+
+ int mFontSize;
+ gcn::DropDown *mFontSizeDropDown;
+ TextDialog *mDialog;
+};
+
+#endif
diff --git a/src/gui/shopwindow.cpp b/src/gui/shopwindow.cpp
new file mode 100644
index 000000000..410e79dbb
--- /dev/null
+++ b/src/gui/shopwindow.cpp
@@ -0,0 +1,788 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/shopwindow.h"
+
+#include "gui/buy.h"
+#include "gui/itemamount.h"
+#include "gui/sell.h"
+#include "gui/setup.h"
+#include "gui/trade.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/shopitems.h"
+#include "gui/widgets/shoplistbox.h"
+#include "gui/widgets/slider.h"
+#include "gui/widgets/tradetab.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+#include "confirmdialog.h"
+#include "inventory.h"
+#include "item.h"
+#include "localplayer.h"
+#include "playerinfo.h"
+#include "playerrelations.h"
+#include "shopitem.h"
+#include "sound.h"
+#include "units.h"
+
+#include "net/net.h"
+#include "net/chathandler.h"
+#include "net/npchandler.h"
+#include "net/tradehandler.h"
+
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <sstream>
+
+#include <sys/stat.h>
+
+extern std::string tradePartnerName;
+ShopWindow::DialogList ShopWindow::instances;
+
+ShopWindow::ShopWindow():
+ Window(_("Personal Shop")),
+ mSelectedItem(-1),
+ mAnnonceTime(0),
+ mLastRequestTimeList(0),
+ mLastRequestTimeItem(0),
+ mRandCounter(0),
+ mAcceptPlayer(""),
+ mTradeItem(0),
+ mTradeNick(""),
+ mTradeMoney(0)
+{
+ setWindowName("Personal Shop");
+ setResizable(true);
+ setCloseButton(true);
+ setMinWidth(260);
+ setMinHeight(230);
+ setDefaultSize(380, 300, ImageRect::CENTER);
+
+ mBuyShopItems = new ShopItems;
+ mSellShopItems = new ShopItems;
+
+ mAnnounceCounter[BUY] = 0;
+ mAnnounceCounter[SELL] = 0;
+
+ loadList();
+
+ mBuyShopItemList = new ShopListBox(mBuyShopItems, mBuyShopItems);
+ mSellShopItemList = new ShopListBox(mSellShopItems, mSellShopItems);
+
+ mBuyShopItemList->setPriceCheck(false);
+ mSellShopItemList->setPriceCheck(false);
+
+ mBuyScrollArea = new ScrollArea(mBuyShopItemList);
+ mBuyScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mSellScrollArea = new ScrollArea(mSellShopItemList);
+ mSellScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mCloseButton = new Button(_("Close"), "close", this);
+
+ mBuyShopItemList->addSelectionListener(this);
+ mSellShopItemList->addSelectionListener(this);
+
+ mBuyLabel = new Label(_("Buy items"));
+ mSellLabel = new Label(_("Sell items"));
+
+ mBuyAddButton = new Button(_("Add"), "add buy", this);
+ mBuyDeleteButton = new Button(_("Delete"), "delete buy", this);
+ mBuyAnnounceButton = new Button(_("Announce"), "announce buy", this);
+ mSellAddButton = new Button(_("Add"), "add sell", this);
+ mSellDeleteButton = new Button(_("Delete"), "delete sell", this);
+ mSellAnnounceButton = new Button(_("Announce"), "announce sell", this);
+ mAnnounceLinks = new CheckBox(_("Show links in announce"), false,
+ this, "link announce");
+
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+
+ place(0, 0, mBuyLabel, 8).setPadding(3);
+ place(8, 0, mSellLabel, 8).setPadding(3);
+ place(0, 1, mBuyScrollArea, 8, 5).setPadding(3);
+ place(8, 1, mSellScrollArea, 8, 5).setPadding(3);
+ place(0, 6, mBuyAddButton);
+ place(1, 6, mBuyDeleteButton);
+ place(3, 6, mBuyAnnounceButton);
+ place(8, 6, mSellAddButton);
+ place(9, 6, mSellDeleteButton);
+ place(11, 6, mSellAnnounceButton);
+ place(0, 7, mAnnounceLinks, 8);
+ place(15, 7, mCloseButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ center();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(false);
+
+ updateButtonsAndLabels();
+}
+
+ShopWindow::~ShopWindow()
+{
+ saveList();
+
+ delete mBuyShopItems;
+ mBuyShopItems = 0;
+
+ delete mSellShopItems;
+ mSellShopItems = 0;
+
+ instances.remove(this);
+}
+
+void ShopWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "close")
+ {
+ close();
+ return;
+ }
+
+ if (event.getId() == "yes")
+ {
+ startTrade();
+ }
+ else if (event.getId() == "no")
+ {
+ mTradeNick = "";
+ }
+ else if (event.getId() == "ignore")
+ {
+ player_relations.ignoreTrade(mTradeNick);
+ mTradeNick = "";
+ }
+ else if (event.getId() == "delete buy" && mBuyShopItemList
+ && mBuyShopItemList->getSelected() >= 0)
+ {
+ mBuyShopItems->erase(mBuyShopItemList->getSelected());
+ }
+ else if (event.getId() == "delete sell" && mSellShopItemList
+ && mSellShopItemList->getSelected() >= 0)
+ {
+ mSellShopItems->erase(mSellShopItemList->getSelected());
+ }
+ else if (event.getId() == "announce buy" && mBuyShopItems
+ && mBuyShopItems->getNumberOfElements() > 0)
+ {
+ announce(mBuyShopItems, BUY);
+ }
+ else if (event.getId() == "announce sell" && mSellShopItems
+ && mSellShopItems->getNumberOfElements() > 0)
+ {
+ announce(mSellShopItems, SELL);
+ }
+
+ if (mSelectedItem < 1)
+ return;
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ Item *item = inv->findItem(mSelectedItem);
+ if (item)
+ {
+ if (event.getId() == "add buy")
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ShopBuyAdd,
+ this, item, sumAmount(item));
+ }
+ else if (event.getId() == "add sell")
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ShopSellAdd,
+ this, item, sumAmount(item));
+ }
+ }
+
+}
+
+void ShopWindow::startTrade()
+{
+ if (!actorSpriteManager || !tradeWindow)
+ return;
+
+ Being *being = actorSpriteManager->findBeingByName(
+ mTradeNick, Being::PLAYER);
+ tradeWindow->clear();
+ if (mTradeMoney)
+ {
+ tradeWindow->addAutoMoney(mTradeNick, mTradeMoney);
+ }
+ else
+ {
+ tradeWindow->addAutoItem(mTradeNick, mTradeItem,
+ mTradeItem->getQuantity());
+ }
+ Net::getTradeHandler()->request(being);
+ tradePartnerName = mTradeNick;
+ mTradeNick = "";
+}
+
+void ShopWindow::valueChanged(const gcn::SelectionEvent &event _UNUSED_)
+{
+ updateButtonsAndLabels();
+}
+
+void ShopWindow::updateButtonsAndLabels()
+{
+ mBuyAddButton->setEnabled(mSelectedItem != -1);
+ mSellAddButton->setEnabled(mSelectedItem != -1);
+ mBuyDeleteButton->setEnabled(
+ mBuyShopItemList->getSelected() != -1
+ && mBuyShopItems->getNumberOfElements() > 0);
+ mSellDeleteButton->setEnabled(
+ mSellShopItemList->getSelected() != -1
+ && mSellShopItems->getNumberOfElements() > 0);
+}
+
+void ShopWindow::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+}
+
+void ShopWindow::addBuyItem(Item *item, int amount, int price)
+{
+ if (!mBuyShopItems || !item)
+ return;
+ mBuyShopItems->addItemNoDup(item->getId(), amount, price);
+ updateButtonsAndLabels();
+}
+
+void ShopWindow::addSellItem(Item *item, int amount, int price)
+{
+ if (!mBuyShopItems || !item)
+ return;
+ mSellShopItems->addItemNoDup(item->getId(), amount, price);
+ updateButtonsAndLabels();
+}
+
+void ShopWindow::loadList()
+{
+ if (!mBuyShopItems || !mSellShopItems)
+ return;
+
+ std::ifstream shopFile;
+ struct stat statbuf;
+
+ mBuyShopItems->clear();
+ mSellShopItems->clear();
+
+ std::string shopListName = Client::getServerConfigDirectory()
+ + "/shoplist.txt";
+
+ if (!stat(shopListName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode))
+ {
+ shopFile.open(shopListName.c_str(), std::ios::in);
+ char line[101];
+ while (shopFile.getline(line, 100))
+ {
+ std::string buf;
+ std::string str = line;
+ if (!str.empty())
+ {
+ std::vector<int> tokens;
+
+ std::stringstream ss(str);
+
+ while (ss >> buf)
+ tokens.push_back(atoi(buf.c_str()));
+
+ if (tokens.size() == 5 && tokens[0])
+ {
+ if (tokens[1] && tokens[2] && mBuyShopItems)
+ {
+ mBuyShopItems->addItem(
+ tokens[0], tokens[1], tokens[2]);
+ }
+ if (tokens[3] && tokens[4] && mSellShopItems)
+ {
+ mSellShopItems->addItem(
+ tokens[0], tokens[3], tokens[4]);
+ }
+ }
+ }
+ }
+ shopFile.close();
+ }
+}
+
+void ShopWindow::saveList()
+{
+ if (!mBuyShopItems || !mSellShopItems)
+ return;
+
+ std::ofstream shopFile;
+ std::string shopListName = Client::getServerConfigDirectory()
+ + "/shoplist.txt";
+ std::list<int> procesList;
+ std::map<int, ShopItem*> mapItems;
+
+ shopFile.open(shopListName.c_str(), std::ios::binary);
+ if (!shopFile.is_open())
+ {
+ logger->log1("Unable to open shoplist.txt for writing");
+ return;
+ }
+
+ std::vector<ShopItem*> items = mBuyShopItems->items();
+ std::vector<ShopItem*>::iterator it;
+ for (it = items.begin(); it != items.end(); it++)
+ {
+ ShopItem *item = *(it);
+ if (item)
+ mapItems[item->getId()] = item;
+ }
+
+ items = mSellShopItems->items();
+ for (it = items.begin(); it != items.end(); it++)
+ {
+ ShopItem *sellItem = *(it);
+ ShopItem *buyItem = mapItems[sellItem->getId()];
+
+ shopFile << sellItem->getId();
+ if (buyItem)
+ {
+ shopFile << strprintf(" %d %d ", buyItem->getQuantity(),
+ buyItem->getPrice());
+ mapItems.erase(sellItem->getId());
+ }
+ else
+ {
+ shopFile << " 0 0 ";
+ }
+
+ if (sellItem)
+ {
+ shopFile << strprintf("%d %d", sellItem->getQuantity(),
+ sellItem->getPrice()) << std::endl;
+ }
+ }
+
+ std::map<int, ShopItem*>::iterator mapIt;
+ for (mapIt = mapItems.begin(); mapIt != mapItems.end(); mapIt++)
+ {
+ ShopItem *buyItem = (*mapIt).second;
+ if (buyItem)
+ {
+ shopFile << buyItem->getId();
+ shopFile << strprintf(" %d %d ", buyItem->getQuantity(),
+ buyItem->getPrice());
+ shopFile << "0 0" << std::endl;
+ }
+ }
+
+ shopFile.close();
+}
+
+void ShopWindow::announce(ShopItems *list, int mode)
+{
+ if (!list)
+ return;
+
+ std::string data = "\302\202";
+ if (mode == BUY)
+ data += "Buy ";
+ else
+ data += "Sell ";
+
+ if (mAnnonceTime && (mAnnonceTime + (2 * 60) > cur_time
+ || mAnnonceTime > cur_time))
+ {
+ return;
+ }
+
+ mAnnonceTime = cur_time;
+ if (mBuyAnnounceButton)
+ mBuyAnnounceButton->setEnabled(false);
+ if (mSellAnnounceButton)
+ mSellAnnounceButton->setEnabled(false);
+
+ std::vector<ShopItem*> items = list->items();
+ std::vector<ShopItem*>::iterator it;
+
+ for (it = items.begin(); it != items.end(); it++)
+ {
+ ShopItem *item = *(it);
+ if (item->getQuantity() > 1)
+ {
+ if (mAnnounceLinks->isSelected())
+ {
+ data += strprintf("[@@%d|%s@@] (%dGP) %d, ", item->getId(),
+ item->getInfo().getName().c_str(),
+ item->getPrice(), item->getQuantity());
+ }
+ else
+ {
+ data += strprintf("%s (%dGP) %d, ",
+ item->getInfo().getName().c_str(),
+ item->getPrice(), item->getQuantity());
+ }
+ }
+ else
+ {
+ if (mAnnounceLinks->isSelected())
+ {
+ data += strprintf("[@@%d|%s@@] (%dGP), ", item->getId(),
+ item->getInfo().getName().c_str(),
+ item->getPrice());
+ }
+ else
+ {
+ data += strprintf("%s (%dGP), ",
+ item->getInfo().getName().c_str(),
+ item->getPrice());
+ }
+ }
+ }
+
+ Net::getChatHandler()->talk(data);
+}
+
+void ShopWindow::giveList(const std::string &nick, int mode)
+{
+ if (!checkFloodCounter(mLastRequestTimeList))
+ return;
+
+ std::string data = "\302\202";
+
+ ShopItems *list;
+ if (mode == BUY)
+ {
+ list = mBuyShopItems;
+ data += "S1";
+ }
+ else
+ {
+ list = mSellShopItems;
+ data += "B1";
+ }
+ if (!list)
+ return;
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ std::vector<ShopItem*> items = list->items();
+ std::vector<ShopItem*>::iterator it;
+
+ for (it = items.begin(); it != items.end(); it++)
+ {
+ ShopItem *item = *(it);
+ if (!item)
+ continue;
+
+ if (mode == SELL)
+ {
+ Item *item2 = inv->findItem(item->getId());
+ if (item2)
+ {
+ int amount = item->getQuantity();
+ if (item2->getQuantity() < amount)
+ amount = item2->getQuantity();
+
+ if (amount)
+ {
+ data += strprintf("%s%s%s",
+ encodeStr(item->getId(), 2).c_str(),
+ encodeStr(item->getPrice(), 4).c_str(),
+ encodeStr(amount, 3).c_str());
+ }
+ }
+ }
+ else
+ {
+ int amount = item->getQuantity();
+ if (item->getPrice() * amount > PlayerInfo::getAttribute(MONEY))
+ amount = PlayerInfo::getAttribute(MONEY) / item->getPrice();
+
+ if (amount > 0)
+ {
+ data += strprintf("%s%s%s",
+ encodeStr(item->getId(), 2).c_str(),
+ encodeStr(item->getPrice(), 4).c_str(),
+ encodeStr(amount, 3).c_str());
+ }
+ }
+ }
+ sendMessage(nick, data, true);
+}
+
+void ShopWindow::sendMessage(const std::string &nick,
+ std::string data, bool random)
+{
+ if (!chatWindow)
+ return;
+
+ if (random)
+ {
+ mRandCounter ++;
+ if (mRandCounter > 200)
+ mRandCounter = 0;
+ data += encodeStr(mRandCounter, 2);
+ }
+
+ if (config.getBoolValue("hideShopMessages"))
+ Net::getChatHandler()->privateMessage(nick, data);
+ else if (chatWindow)
+ chatWindow->whisper(nick, data, BY_PLAYER);
+//here was true
+}
+
+void ShopWindow::showList(const std::string &nick, std::string data)
+{
+ BuyDialog *buyDialog = 0;
+ SellDialog *sellDialog = 0;
+ if (data.find("B1") == 0)
+ {
+ data = data.substr(2);
+ buyDialog = new BuyDialog(nick);
+ }
+ else if (data.find("S1") == 0)
+ {
+ data = data.substr(2);
+ sellDialog = new SellDialog(nick);
+ }
+ else
+ {
+ return;
+ }
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ if (buyDialog)
+ buyDialog->setMoney(PlayerInfo::getAttribute(MONEY));
+ if (sellDialog)
+ sellDialog->setMoney(PlayerInfo::getAttribute(MONEY));
+
+ for (unsigned f = 0; f < data.length(); f += 9)
+ {
+ if (f + 9 > data.length())
+ break;
+
+ int id = decodeStr(data.substr(f, 2));
+ int price = decodeStr(data.substr(f + 2, 4));
+ int amount = decodeStr(data.substr(f + 6, 3));
+ if (buyDialog && amount > 0)
+ buyDialog->addItem(id, amount, price);
+ if (sellDialog)
+ {
+ Item *item = inv->findItem(id);
+ if (item)
+ {
+ if (item->getQuantity() < amount)
+ amount = item->getQuantity();
+ if (amount > 0)
+ sellDialog->addItem(id, amount, price);
+ else
+ sellDialog->addItem(id, -1, price);
+ }
+ }
+ }
+}
+
+void ShopWindow::processRequest(std::string nick, std::string data, int mode)
+{
+ if (!player_node || !mTradeNick.empty() || PlayerInfo::isTrading()
+ || !actorSpriteManager
+ || !actorSpriteManager->findBeingByName(nick, Being::PLAYER))
+ {
+ return;
+ }
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ unsigned long idx = 0;
+
+ idx = data.find(" ");
+ if (idx == std::string::npos)
+ return;
+
+ if (!checkFloodCounter(mLastRequestTimeItem))
+ return;
+
+ if (!mTradeNick.empty())
+ {
+ sendMessage(nick, "error: player busy ", true);
+ return;
+ }
+
+ data = data.substr(idx + 1);
+
+ std::string part1;
+ std::string part2;
+ std::string part3;
+ std::stringstream ss(data);
+ std::string msg;
+ int id;
+ int price;
+ int amount;
+
+ if (!(ss >> part1))
+ return;
+
+ if (!(ss >> part2))
+ return;
+
+ if (!(ss >> part3))
+ return;
+
+ id = atoi(part1.c_str());
+ price = atoi(part2.c_str());
+ amount = atoi(part3.c_str());
+
+ delete mTradeItem;
+ mTradeItem = new ShopItem(-1, id, amount, price);
+
+ if (mode == BUY)
+ {
+ Item *item2 = inv->findItem(mTradeItem->getId());
+ if (!item2 || item2->getQuantity() < amount
+ || !findShopItem(mTradeItem, SELL))
+ {
+ sendMessage(nick, "error: Cant sell this item ", true);
+ return;
+ }
+ msg = "buy";
+ mTradeMoney = 0;
+ }
+ else
+ {
+ if (!findShopItem(mTradeItem, BUY))
+ {
+ sendMessage(nick, "error: Cant buy this item ", true);
+ return;
+ }
+ msg = "sell";
+ mTradeMoney = mTradeItem->getPrice() * mTradeItem->getQuantity();
+ }
+
+ mTradeNick = nick;
+
+ if (config.getBoolValue("autoShop"))
+ {
+ sound.playGuiSfx("system/newmessage.ogg");
+ startTrade();
+ }
+ else
+ {
+ ConfirmDialog *confirmDlg = new ConfirmDialog(_("Request for Trade"),
+ strprintf(_("%s wants to %s %s do you "
+ "accept?"), nick.c_str(), msg.c_str(),
+ mTradeItem->getInfo().getName().c_str()), true);
+ confirmDlg->addActionListener(this);
+ }
+}
+
+void ShopWindow::updateTimes()
+{
+ if (mAnnonceTime + (2 * 60) < cur_time
+ || mAnnonceTime > cur_time)
+ {
+ mBuyAnnounceButton->setEnabled(true);
+ mSellAnnounceButton->setEnabled(true);
+ }
+}
+
+bool ShopWindow::checkFloodCounter(int &counterTime)
+{
+ if (!counterTime || counterTime > cur_time)
+ counterTime = cur_time;
+ else if (counterTime + 10 > cur_time)
+ return false;
+
+ counterTime = cur_time;
+ return true;
+}
+
+bool ShopWindow::findShopItem(ShopItem *shopItem, int mode)
+{
+ if (!shopItem)
+ return false;
+
+ std::vector<ShopItem*> items;
+ std::vector<ShopItem*>::iterator it;
+ if (mode == SELL)
+ {
+ if (!mSellShopItems)
+ return false;
+ items = mSellShopItems->items();
+ }
+ else
+ {
+ if (!mBuyShopItems)
+ return false;
+ items = mBuyShopItems->items();
+ }
+
+ for (it = items.begin(); it != items.end(); it++)
+ {
+ ShopItem *item = *(it);
+ if (!item)
+ continue;
+
+ if (item && item->getId() == shopItem->getId()
+ && item->getPrice() == shopItem->getPrice()
+ && item->getQuantity() >= shopItem->getQuantity())
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+int ShopWindow::sumAmount(Item *shopItem)
+{
+ if (!player_node || !shopItem)
+ return 0;
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return 0;
+ int sum = 0;
+
+ for (unsigned f = 0; f < inv->getSize(); f ++)
+ {
+ Item *item = inv->getItem(f);
+ if (item && item->getId() == shopItem->getId())
+ sum += item->getQuantity();
+ }
+ return sum;
+}
diff --git a/src/gui/shopwindow.h b/src/gui/shopwindow.h
new file mode 100644
index 000000000..6b31b67a5
--- /dev/null
+++ b/src/gui/shopwindow.h
@@ -0,0 +1,173 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SHOP_H
+#define SHOP_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class CheckBox;
+class Item;
+class ListBox;
+class ShopItem;
+class ShopItems;
+class ShopListBox;
+
+/**
+ * The buy dialog.
+ *
+ * \ingroup Interface
+ */
+class ShopWindow : public Window, public gcn::ActionListener,
+ public gcn::SelectionListener
+{
+ public:
+
+ enum ShopMode
+ {
+ BUY = 0,
+ SELL = 1
+ };
+
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ ShopWindow();
+
+ /**
+ * Destructor
+ */
+ ~ShopWindow();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Updates the labels according to the selected item.
+ */
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ /**
+ * Updates the state of buttons and labels.
+ */
+ void updateButtonsAndLabels();
+
+ /**
+ * Sets the visibility of this window.
+ */
+ void setVisible(bool visible);
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive()
+ { return instances.size() > 0; }
+
+ void setItemSelected(int id)
+ { mSelectedItem = id; updateButtonsAndLabels(); }
+
+ void addBuyItem(Item *item, int amount, int price);
+
+ void addSellItem(Item *item, int amount, int price);
+
+ void loadList();
+
+ void saveList();
+
+ void announce(ShopItems *list, int mode);
+
+ void giveList(const std::string &nick, int mode);
+
+ void setAcceptPlayer(std::string name)
+ { mAcceptPlayer = name; }
+
+ const std::string &getAcceptPlayer()
+ { return mAcceptPlayer; }
+
+ void sendMessage(const std::string &nick, std::string data,
+ bool random = false);
+
+ void showList(const std::string &nick, std::string data);
+
+ void processRequest(std::string nick, std::string data, int mode);
+
+ bool findShopItem(ShopItem *shopItem, int mode);
+
+ int sumAmount(Item *shopItem);
+
+ void updateTimes();
+
+ bool checkFloodCounter(int &counterTime);
+
+ private:
+ void startTrade();
+
+ typedef std::list<ShopWindow*> DialogList;
+ static DialogList instances;
+
+ gcn::Button *mCloseButton;
+ ShopListBox *mBuyShopItemList;
+ ShopListBox *mSellShopItemList;
+ gcn::ScrollArea *mBuyScrollArea;
+ gcn::ScrollArea *mSellScrollArea;
+ gcn::Label *mBuyLabel;
+ gcn::Label *mSellLabel;
+ gcn::Button *mBuyAddButton;
+ gcn::Button *mBuyDeleteButton;
+ gcn::Button *mBuyAnnounceButton;
+ gcn::Button *mSellAddButton;
+ gcn::Button *mSellDeleteButton;
+ gcn::Button *mSellAnnounceButton;
+ gcn::CheckBox *mAnnounceLinks;
+
+ ShopItems *mBuyShopItems;
+ ShopItems *mSellShopItems;
+
+ int mSelectedItem;
+ int mAnnonceTime;
+ int mLastRequestTimeList;
+ int mLastRequestTimeItem;
+ int mRandCounter;
+ std::string mAcceptPlayer;
+ ShopItem *mTradeItem;
+ std::string mTradeNick;
+ int mTradeMoney;
+ int mAnnounceCounter[2];
+};
+
+extern ShopWindow *shopWindow;
+
+#endif
diff --git a/src/gui/shortcutwindow.cpp b/src/gui/shortcutwindow.cpp
new file mode 100644
index 000000000..c3aa8454f
--- /dev/null
+++ b/src/gui/shortcutwindow.cpp
@@ -0,0 +1,152 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/shortcutwindow.h"
+
+#include "configuration.h"
+
+#include "gui/setup.h"
+
+#include "gui/widgets/layout.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/shortcutcontainer.h"
+#include "gui/widgets/tab.h"
+#include "gui/widgets/tabbedarea.h"
+
+static const int SCROLL_PADDING = 0;
+
+int ShortcutWindow::mBoxesWidth = 0;
+
+class ShortcutTab : public Tab
+{
+ public:
+ ShortcutTab(std::string name, ShortcutContainer* content)
+ {
+ setCaption(name);
+ mContent = content;
+ }
+
+ ShortcutContainer* mContent;
+};
+
+ShortcutWindow::ShortcutWindow(const std::string &title,
+ ShortcutContainer *content,
+ int width, int height)
+{
+ setWindowName(title);
+ // no title presented, title bar is padding so window can be moved.
+ gcn::Window::setTitleBarHeight(gcn::Window::getPadding());
+ setShowTitle(false);
+ setResizable(true);
+ setDefaultVisible(false);
+ setSaveVisible(true);
+
+ setupWindow->registerWindowForReset(this);
+
+ mTabs = 0;
+ mItems = content;
+
+ const int border = SCROLL_PADDING * 2 + getPadding() * 2;
+ setMinWidth(mItems->getBoxWidth() + border);
+ setMinHeight(mItems->getBoxHeight() + border);
+ setMaxWidth(mItems->getBoxWidth() * mItems->getMaxItems() + border);
+ setMaxHeight(mItems->getBoxHeight() * mItems->getMaxItems() + border);
+
+ if (width == 0)
+ width = mItems->getBoxWidth() + border;
+ if (height == 0)
+ height = (mItems->getBoxHeight() * mItems->getMaxItems()) + border;
+
+ setDefaultSize(width, height, ImageRect::LOWER_RIGHT);
+
+ mBoxesWidth += mItems->getBoxWidth() + border;
+
+ mScrollArea = new ScrollArea(mItems);
+ mScrollArea->setPosition(SCROLL_PADDING, SCROLL_PADDING);
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mScrollArea->setOpaque(false);
+
+ place(0, 0, mScrollArea, 5, 5).setPadding(0);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+ layout.setMargin(0);
+
+ loadWindowState();
+}
+
+ShortcutWindow::ShortcutWindow(const std::string &title, int width, int height)
+{
+ setWindowName(title);
+ // no title presented, title bar is padding so window can be moved.
+ gcn::Window::setTitleBarHeight(gcn::Window::getPadding());
+ setShowTitle(false);
+ setResizable(true);
+ setDefaultVisible(false);
+ setSaveVisible(true);
+
+ setupWindow->registerWindowForReset(this);
+
+ mTabs = new TabbedArea;
+
+ mItems = 0;
+
+ const int border = SCROLL_PADDING * 2 + getPadding() * 2;
+
+ if (width && height)
+ setDefaultSize(width, height, ImageRect::LOWER_RIGHT);
+
+ setMinWidth(32 + border);
+ setMinHeight(32 + border);
+
+ place(0, 0, mTabs, 5, 5);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+ layout.setMargin(0);
+
+ loadWindowState();
+}
+
+ShortcutWindow::~ShortcutWindow()
+{
+ delete mTabs;
+ mTabs = 0;
+ delete mItems;
+ mItems = 0;
+}
+
+void ShortcutWindow::addTab(std::string name, ShortcutContainer *content)
+{
+ ScrollArea *scroll = new ScrollArea(content);
+ scroll->setPosition(SCROLL_PADDING, SCROLL_PADDING);
+ scroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ scroll->setOpaque(false);
+ Tab *tab = new ShortcutTab(name, content);
+ mTabs->addTab(tab, scroll);
+}
+
+int ShortcutWindow::getTabIndex()
+{
+ if (!mTabs)
+ return 0;
+ return mTabs->getSelectedTabIndex();
+}
diff --git a/src/gui/shortcutwindow.h b/src/gui/shortcutwindow.h
new file mode 100644
index 000000000..bd3dedb61
--- /dev/null
+++ b/src/gui/shortcutwindow.h
@@ -0,0 +1,71 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SHORTCUTWINDOW_H
+#define SHORTCUTWINDOW_H
+
+#include "gui/widgets/window.h"
+
+class ScrollArea;
+class ShortcutContainer;
+class TabbedArea;
+
+/**
+ * A window around a ShortcutContainer.
+ *
+ * \ingroup Interface
+ */
+class ShortcutWindow : public Window
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ShortcutWindow(const std::string &title, ShortcutContainer *content,
+ int width = 0, int height = 0);
+
+ ShortcutWindow(const std::string &title,
+ int width = 0, int height = 0);
+
+ /**
+ * Destructor.
+ */
+ ~ShortcutWindow();
+
+ void addTab(std::string name, ShortcutContainer *content);
+
+ int getTabIndex();
+
+ private:
+ ShortcutWindow();
+ ShortcutContainer *mItems;
+
+ ScrollArea *mScrollArea;
+ TabbedArea *mTabs;
+
+ static int mBoxesWidth;
+};
+
+extern ShortcutWindow *itemShortcutWindow;
+extern ShortcutWindow *emoteShortcutWindow;
+extern ShortcutWindow *dropShortcutWindow;
+
+#endif
diff --git a/src/gui/skilldialog.cpp b/src/gui/skilldialog.cpp
new file mode 100644
index 000000000..04222d6f5
--- /dev/null
+++ b/src/gui/skilldialog.cpp
@@ -0,0 +1,523 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/skilldialog.h"
+
+#include "log.h"
+#include "playerinfo.h"
+#include "configuration.h"
+
+#include "gui/setup.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/container.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/progressbar.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/tab.h"
+#include "gui/widgets/tabbedarea.h"
+#include "gui/widgets/windowcontainer.h"
+
+#include "net/net.h"
+#include "net/playerhandler.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+#include "utils/xml.h"
+
+#include <guichan/font.hpp>
+
+#include <set>
+#include <string>
+
+class SkillModel;
+class SkillEntry;
+
+
+struct SkillInfo
+{
+ unsigned short id;
+ std::string name;
+ Image *icon;
+ bool modifiable;
+ bool visible;
+ SkillModel *model;
+
+ std::string skillLevel;
+ int skillLevelWidth;
+
+ std::string skillExp;
+ float progress;
+ gcn::Color color;
+
+ SkillInfo() :
+ id(0), name(""), icon(0), modifiable(false), visible(false),
+ model(0), skillLevel(""), skillLevelWidth(0), skillExp(""),
+ progress(0.0f)
+ {
+ }
+
+ ~SkillInfo()
+ {
+ if (icon)
+ icon->decRef();
+ }
+
+ void setIcon(const std::string &iconPath)
+ {
+ ResourceManager *res = ResourceManager::getInstance();
+ if (!iconPath.empty())
+ {
+ icon = res->getImage(iconPath);
+ }
+
+ if (!icon)
+ {
+ icon = Theme::getImageFromTheme(
+ paths.getStringValue("unknownItemFile"));
+ }
+ }
+
+ void update();
+
+ void draw(Graphics *graphics, int y, int width);
+};
+
+
+typedef std::vector<SkillInfo*> SkillList;
+
+class SkillModel : public gcn::ListModel
+{
+public:
+ int getNumberOfElements()
+ { return static_cast<int>(mVisibleSkills.size()); }
+
+ SkillInfo *getSkillAt(int i) const
+ { return mVisibleSkills.at(i); }
+
+ std::string getElementAt(int i)
+ {
+ if (getSkillAt(i))
+ return getSkillAt(i)->name;
+ else
+ return "";
+ }
+
+ void updateVisibilities();
+
+ void addSkill(SkillInfo *info)
+ { mSkills.push_back(info); }
+
+private:
+ SkillList mSkills;
+ SkillList mVisibleSkills;
+};
+
+class SkillListBox : public ListBox
+{
+public:
+ SkillListBox(SkillModel *model):
+ ListBox(model)
+ {}
+
+ SkillInfo *getSelectedInfo()
+ {
+ const int selected = getSelected();
+ if (!mListModel || selected < 0
+ || selected > mListModel->getNumberOfElements())
+ {
+ return 0;
+ }
+
+ return static_cast<SkillModel*>(mListModel)->getSkillAt(selected);
+ }
+
+ void draw(gcn::Graphics *gcnGraphics)
+ {
+ if (!mListModel)
+ return;
+
+ SkillModel* model = static_cast<SkillModel*>(mListModel);
+
+ updateAlpha();
+
+ Graphics *graphics = static_cast<Graphics*>(gcnGraphics);
+
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT,
+ static_cast<int>(mAlpha * 255.0f)));
+ graphics->setFont(getFont());
+
+ // Draw filled rectangle around the selected list element
+ if (mSelected >= 0)
+ {
+ graphics->fillRectangle(gcn::Rectangle(0, getRowHeight()
+ * mSelected, getWidth(), getRowHeight()));
+ }
+
+ // Draw the list elements
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ for (int i = 0, y = 1;
+ i < model->getNumberOfElements();
+ ++i, y += getRowHeight())
+ {
+ SkillInfo *e = model->getSkillAt(i);
+
+ if (e)
+ e->draw(graphics, y, getWidth());
+ }
+ }
+
+ unsigned int getRowHeight() const
+ { return 34; }
+};
+
+class SkillTab : public Tab
+{
+public:
+ SkillTab(const std::string &name, SkillListBox *listBox):
+ mListBox(listBox)
+ {
+ setCaption(name);
+ }
+
+ ~SkillTab()
+ {
+ delete mListBox;
+ mListBox = 0;
+ }
+
+ SkillInfo *getSelectedInfo()
+ {
+ if (mListBox)
+ return mListBox->getSelectedInfo();
+ else
+ return 0;
+ }
+
+private:
+ SkillListBox *mListBox;
+};
+
+SkillDialog::SkillDialog():
+ Window(_("Skills"))
+{
+ setWindowName("Skills");
+ setCloseButton(true);
+ setResizable(true);
+ setSaveVisible(true);
+ setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425);
+ setupWindow->registerWindowForReset(this);
+
+ mTabs = new TabbedArea();
+ mPointsLabel = new Label("0");
+ mIncreaseButton = new Button(_("Up"), "inc", this);
+
+ place(0, 0, mTabs, 5, 5);
+ place(0, 5, mPointsLabel, 4);
+ place(4, 5, mIncreaseButton);
+
+ setLocationRelativeTo(getParent());
+ loadWindowState();
+}
+
+SkillDialog::~SkillDialog()
+{
+ // Clear gui
+ loadSkills("");
+}
+
+void SkillDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "inc")
+ {
+ SkillTab *tab = static_cast<SkillTab*>(mTabs->getSelectedTab());
+ if (tab)
+ {
+ if (SkillInfo *info = tab->getSelectedInfo())
+ Net::getPlayerHandler()->increaseSkill(info->id);
+ }
+ }
+ else if (event.getId() == "close")
+ {
+ setVisible(false);
+ }
+}
+
+std::string SkillDialog::update(int id)
+{
+ SkillMap::iterator i = mSkills.find(id);
+
+ if (i != mSkills.end())
+ {
+ SkillInfo *info = i->second;
+ if (info)
+ {
+ info->update();
+ return info->name;
+ }
+ }
+
+ return std::string();
+}
+
+void SkillDialog::update()
+{
+ mPointsLabel->setCaption(strprintf(_("Skill points available: %d"),
+ PlayerInfo::getAttribute(SKILL_POINTS)));
+ mPointsLabel->adjustSize();
+
+ for (SkillMap::iterator it = mSkills.begin(); it != mSkills.end(); it++)
+ {
+ if ((*it).second && (*it).second->modifiable)
+ (*it).second->update();
+ }
+}
+
+void SkillDialog::loadSkills(const std::string &file)
+{
+ // Fixes issues with removing tabs
+ if (mTabs->getSelectedTabIndex() != -1)
+ {
+ mTabs->setSelectedTab(static_cast<unsigned int>(0));
+
+ while (mTabs->getSelectedTabIndex() != -1)
+ {
+ gcn::Tab *tab = mTabs->getSelectedTab();
+ if (tab)
+ mTabs->removeTabWithIndex(mTabs->getSelectedTabIndex());
+ delete tab;
+ }
+ }
+
+ delete_all(mSkills);
+ mSkills.clear();
+
+ if (file.length() == 0)
+ return;
+
+ XML::Document doc(file);
+ xmlNodePtr root = doc.rootNode();
+
+ int setCount = 0;
+ std::string setName;
+ ScrollArea *scroll;
+ SkillListBox *listbox;
+ SkillTab *tab;
+
+ if (!root || !xmlStrEqual(root->name, BAD_CAST "skills"))
+ {
+ logger->log("Error loading skills file: %s", file.c_str());
+
+ if (Net::getNetworkType() == ServerInfo::TMWATHENA)
+ {
+ SkillModel *model = new SkillModel();
+ SkillInfo *skill = new SkillInfo;
+ skill->id = 1;
+ skill->name = "basic";
+ skill->setIcon("");
+ skill->modifiable = true;
+ skill->visible = true;
+ skill->model = model;
+ skill->update();
+
+ model->addSkill(skill);
+ mSkills[1] = skill;
+
+ model->updateVisibilities();
+
+ listbox = new SkillListBox(model);
+ scroll = new ScrollArea(listbox);
+ scroll->setOpaque(false);
+ scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
+
+ tab = new SkillTab("Skills", listbox);
+
+ mTabs->addTab(tab, scroll);
+
+ update();
+ }
+ return;
+ }
+
+ for_each_xml_child_node(set, root)
+ {
+ if (xmlStrEqual(set->name, BAD_CAST "set"))
+ {
+ setCount++;
+ setName = XML::getProperty(set, "name",
+ strprintf(_("Skill Set %d"), setCount));
+
+ SkillModel *model = new SkillModel();
+
+ for_each_xml_child_node(node, set)
+ {
+ if (xmlStrEqual(node->name, BAD_CAST "skill"))
+ {
+ int id = atoi(XML::getProperty(node, "id", "-1").c_str());
+ std::string name = XML::getProperty(node, "name",
+ strprintf(_("Skill %d"), id));
+ std::string icon = XML::getProperty(node, "icon", "");
+
+ SkillInfo *skill = new SkillInfo;
+ skill->id = static_cast<short unsigned>(id);
+ skill->name = name;
+ skill->setIcon(icon);
+ skill->modifiable = false;
+ skill->visible = false;
+ skill->model = model;
+ skill->update();
+
+ model->addSkill(skill);
+
+ mSkills[id] = skill;
+ }
+ }
+
+ model->updateVisibilities();
+
+ // possible leak listbox, scroll
+ listbox = new SkillListBox(model);
+ scroll = new ScrollArea(listbox);
+ scroll->setOpaque(false);
+ scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
+
+ tab = new SkillTab(setName, listbox);
+
+ mTabs->addTab(tab, scroll);
+ }
+ }
+ update();
+}
+
+void SkillDialog::setModifiable(int id, bool modifiable)
+{
+ SkillMap::iterator it = mSkills.find(id);
+
+ if (it != mSkills.end())
+ {
+ SkillInfo *info = it->second;
+ if (info)
+ {
+ info->modifiable = modifiable;
+ info->update();
+ }
+ }
+}
+
+void SkillModel::updateVisibilities()
+{
+ mVisibleSkills.clear();
+
+ for (SkillList::iterator it = mSkills.begin(); it != mSkills.end(); it++)
+ {
+ if ((*it)->visible)
+ mVisibleSkills.push_back((*it));
+ }
+}
+
+void SkillInfo::update()
+{
+ int baseLevel = PlayerInfo::getStatBase(id);
+ int effLevel = PlayerInfo::getStatEffective(id);
+
+ std::pair<int, int> exp = PlayerInfo::getStatExperience(id);
+
+ if (!modifiable && baseLevel == 0 && effLevel == 0 && exp.second == 0)
+ {
+ if (visible)
+ {
+ visible = false;
+ if (model)
+ model->updateVisibilities();
+ }
+
+ return;
+ }
+
+ bool updateVisibility = !visible;
+ visible = true;
+
+ if (effLevel != baseLevel)
+ {
+ skillLevel = strprintf(_("Lvl: %d (%+d)"), baseLevel,
+ effLevel - baseLevel);
+ }
+ else
+ {
+ if (baseLevel == 0)
+ skillLevel.clear();
+ else
+ skillLevel = strprintf(_("Lvl: %d"), baseLevel);
+ }
+ skillLevelWidth = -1;
+
+ if (exp.second)
+ {
+ skillExp = strprintf("%d / %d", exp.first, exp.second);
+ progress = static_cast<float>(exp.first)
+ / static_cast<float>(exp.second);
+ }
+ else
+ {
+ skillExp.clear();
+ progress = 0.0f;
+ }
+
+ color = Theme::getProgressColor(Theme::PROG_EXP, progress);
+
+ if (updateVisibility && model)
+ model->updateVisibilities();
+}
+
+void SkillInfo::draw(Graphics *graphics, int y, int width)
+{
+ graphics->drawImage(icon, 1, y);
+ graphics->drawText(name, 34, y);
+
+ if (skillLevelWidth < 0)
+ {
+ // Add one for padding
+ skillLevelWidth = graphics->getFont()->getWidth(skillLevel) + 1;
+ }
+
+ graphics->drawText(skillLevel, width - skillLevelWidth, y);
+
+ if (!skillExp.empty())
+ {
+ gcn::Rectangle rect(33, y + 15, width - 33, 17);
+
+ ProgressBar::render(graphics, rect, color, progress, skillExp);
+ }
+}
+
+SkillInfo* SkillDialog::getSkill(int id)
+{
+ return mSkills[id];
+}
diff --git a/src/gui/skilldialog.h b/src/gui/skilldialog.h
new file mode 100644
index 000000000..4ba4afe8e
--- /dev/null
+++ b/src/gui/skilldialog.h
@@ -0,0 +1,91 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SKILLDIALOG_H
+#define SKILLDIALOG_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+//include "resources/image.h"
+//include "resources/resourcemanager.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <map>
+
+class Button;
+//class Image;
+class Label;
+class ScrollArea;
+class Tab;
+class TabbedArea;
+
+struct SkillInfo;
+
+/**
+ * The skill dialog.
+ *
+ * \ingroup Interface
+ */
+class SkillDialog : public Window, public gcn::ActionListener
+{
+ public:
+ SkillDialog();
+
+ ~SkillDialog();
+
+ /**
+ * Called when receiving actions from widget.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Update the given skill's display
+ */
+ std::string update(int id);
+
+ /**
+ * Update other parts of the display
+ */
+ void update();
+
+ void loadSkills(const std::string &file);
+
+ void setModifiable(int id, bool modifiable);
+
+ SkillInfo* getSkill(int id);
+
+ bool hasSkills()
+ { return !mSkills.empty(); }
+
+ private:
+ typedef std::map<int, SkillInfo*> SkillMap;
+ SkillMap mSkills;
+ TabbedArea *mTabs;
+ Label *mPointsLabel;
+ Button *mIncreaseButton;
+};
+
+extern SkillDialog *skillDialog;
+
+#endif
diff --git a/src/gui/socialwindow.cpp b/src/gui/socialwindow.cpp
new file mode 100644
index 000000000..7e86e4d9c
--- /dev/null
+++ b/src/gui/socialwindow.cpp
@@ -0,0 +1,1306 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/socialwindow.h"
+
+#include "actorspritemanager.h"
+#include "guild.h"
+#include "keyboardconfig.h"
+#include "localplayer.h"
+#include "log.h"
+#include "map.h"
+#include "party.h"
+
+#include "gui/confirmdialog.h"
+#include "gui/okdialog.h"
+#include "gui/outfitwindow.h"
+#include "gui/setup.h"
+#include "gui/textdialog.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/avatarlistbox.h"
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/container.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/linkhandler.h"
+#include "gui/widgets/popup.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/tab.h"
+#include "gui/widgets/tabbedarea.h"
+
+#include "net/net.h"
+#include "net/guildhandler.h"
+#include "net/partyhandler.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+class SocialTab : public Tab
+{
+protected:
+ friend class SocialWindow;
+
+ SocialTab():
+ mInviteDialog(0),
+ mConfirmDialog(0),
+ mScroll(0),
+ mList(0)
+ {}
+
+ virtual ~SocialTab()
+ {
+ // Cleanup dialogs
+ if (mInviteDialog)
+ {
+ mInviteDialog->close();
+ mInviteDialog->scheduleDelete();
+ mInviteDialog = NULL;
+ }
+
+ if (mConfirmDialog)
+ {
+ mConfirmDialog->close();
+ mConfirmDialog->scheduleDelete();
+ mConfirmDialog = NULL;
+ }
+ }
+
+ virtual void invite() = 0;
+
+ virtual void leave() = 0;
+
+ virtual void updateList() = 0;
+
+ virtual void updateAvatar(std::string name) = 0;
+
+ virtual void resetDamage(std::string name) = 0;
+
+ virtual void selectIndex(unsigned num _UNUSED_)
+ { }
+
+ TextDialog *mInviteDialog;
+ ConfirmDialog *mConfirmDialog;
+ ScrollArea *mScroll;
+ AvatarListBox *mList;
+};
+
+class GuildTab : public SocialTab, public gcn::ActionListener
+{
+public:
+ GuildTab(Guild *guild):
+ mGuild(guild)
+ {
+ setCaption(_("Guild"));
+
+ setTabColor(&Theme::getThemeColor(Theme::GUILD_SOCIAL_TAB));
+
+ mList = new AvatarListBox(guild);
+ mScroll = new ScrollArea(mList);
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+ }
+
+ ~GuildTab()
+ {
+ delete mList;
+ mList = 0;
+ delete mScroll;
+ mScroll = 0;
+ }
+
+ void action(const gcn::ActionEvent &event)
+ {
+ if (event.getId() == "do invite")
+ {
+ std::string name = mInviteDialog->getText();
+ Net::getGuildHandler()->invite(mGuild->getId(), name);
+
+ if (localChatTab)
+ {
+ localChatTab->chatLog(strprintf(
+ _("Invited user %s to guild %s."),
+ name.c_str(), mGuild->getName().c_str()), BY_SERVER);
+ }
+ mInviteDialog = 0;
+ }
+ else if (event.getId() == "~do invite")
+ {
+ mInviteDialog = 0;
+ }
+ else if (event.getId() == "yes")
+ {
+ Net::getGuildHandler()->leave(mGuild->getId());
+ if (localChatTab)
+ {
+ localChatTab->chatLog(strprintf(_("Guild %s quit requested."),
+ mGuild->getName().c_str()), BY_SERVER);
+ }
+ mConfirmDialog = 0;
+ }
+ else if (event.getId() == "~yes")
+ {
+ mConfirmDialog = 0;
+ }
+ }
+
+ void updateList()
+ {
+ }
+
+ void updateAvatar(std::string name _UNUSED_)
+ {
+ }
+
+ void resetDamage(std::string name _UNUSED_)
+ {
+ }
+
+protected:
+ void invite()
+ {
+ // TODO - Give feedback on whether the invite succeeded
+ mInviteDialog = new TextDialog(_("Member Invite to Guild"),
+ strprintf(_("Who would you like to invite to guild %s?"),
+ mGuild->getName().c_str()),
+ socialWindow);
+ mInviteDialog->setActionEventId("do invite");
+ mInviteDialog->addActionListener(this);
+ }
+
+ void leave()
+ {
+ mConfirmDialog = new ConfirmDialog(_("Leave Guild?"),
+ strprintf(_("Are you sure you want to leave guild %s?"),
+ mGuild->getName().c_str()),
+ socialWindow);
+
+ mConfirmDialog->addActionListener(this);
+ }
+
+private:
+ Guild *mGuild;
+};
+
+class PartyTab : public SocialTab, public gcn::ActionListener
+{
+public:
+ PartyTab(Party *party):
+ mParty(party)
+ {
+ setCaption(_("Party"));
+
+ setTabColor(&Theme::getThemeColor(Theme::PARTY_SOCIAL_TAB));
+
+ mList = new AvatarListBox(party);
+ mScroll = new ScrollArea(mList);
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+ }
+
+ ~PartyTab()
+ {
+ delete mList;
+ mList = 0;
+ delete mScroll;
+ mScroll = 0;
+ }
+
+ void action(const gcn::ActionEvent &event)
+ {
+ if (event.getId() == "do invite")
+ {
+ std::string name = mInviteDialog->getText();
+ Net::getPartyHandler()->invite(name);
+
+ if (localChatTab)
+ {
+ localChatTab->chatLog(strprintf(_("Invited user %s to party."),
+ name.c_str()), BY_SERVER);
+ }
+ mInviteDialog = NULL;
+ }
+ else if (event.getId() == "~do invite")
+ {
+ mInviteDialog = NULL;
+ }
+ else if (event.getId() == "yes")
+ {
+ Net::getPartyHandler()->leave();
+ if (localChatTab)
+ {
+ localChatTab->chatLog(strprintf(_("Party %s quit requested."),
+ mParty->getName().c_str()), BY_SERVER);
+ }
+ mConfirmDialog = NULL;
+ }
+ else if (event.getId() == "~yes")
+ {
+ mConfirmDialog = NULL;
+ }
+ }
+
+ void updateList()
+ {
+ }
+
+ void updateAvatar(std::string name _UNUSED_)
+ {
+ }
+
+ void resetDamage(std::string name _UNUSED_)
+ {
+ }
+
+protected:
+ void invite()
+ {
+ // TODO - Give feedback on whether the invite succeeded
+ mInviteDialog = new TextDialog(_("Member Invite to Party"),
+ strprintf(_("Who would you like to invite to party %s?"),
+ mParty->getName().c_str()),
+ socialWindow);
+ mInviteDialog->setActionEventId("do invite");
+ mInviteDialog->addActionListener(this);
+ }
+
+ void leave()
+ {
+ mConfirmDialog = new ConfirmDialog(_("Leave Party?"),
+ strprintf(_("Are you sure you want to leave party %s?"),
+ mParty->getName().c_str()),
+ socialWindow);
+
+ mConfirmDialog->addActionListener(this);
+ }
+
+private:
+ Party *mParty;
+};
+
+/*class BuddyTab : public SocialTab
+{
+ // TODO?
+};*/
+
+
+class BeingsListModal : public AvatarListModel
+{
+public:
+ BeingsListModal()
+ {
+ }
+
+ ~BeingsListModal()
+ {
+ delete_all(mMembers);
+ mMembers.clear();
+ }
+
+ std::vector<Avatar*> *getMembers()
+ {
+ return &mMembers;
+ }
+
+ virtual Avatar *getAvatarAt(int index)
+ {
+ return mMembers[index];
+ }
+
+ int getNumberOfElements()
+ {
+ return static_cast<int>(mMembers.size());
+ }
+
+ std::vector<Avatar*> mMembers;
+};
+
+class PlayersTab : public SocialTab
+{
+public:
+ PlayersTab(std::string name)
+ {
+ mBeings = new BeingsListModal();
+
+ mList = new AvatarListBox(mBeings);
+ mScroll = new ScrollArea(mList);
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+// mBeings->getMembers().push_back(new Avatar("test"));
+ updateList();
+ setCaption(name);
+ }
+
+ ~PlayersTab()
+ {
+ delete mList;
+ mList = 0;
+ delete mScroll;
+ mScroll = 0;
+ delete mBeings;
+ mBeings = 0;
+ }
+
+ void updateList()
+ {
+ getPlayersAvatars();
+ }
+
+ void updateAvatar(std::string name)
+ {
+ if (!actorSpriteManager)
+ return;
+
+ Avatar *avatar = findAvatarbyName(name);
+ if (!avatar)
+ return;
+ if (Party::getParty(1))
+ {
+ PartyMember *pm = Party::getParty(1)->getMember(name);
+ if (pm && pm->getMaxHp() > 0)
+ {
+ avatar->setMaxHp(pm->getMaxHp());
+ avatar->setHp(pm->getHp());
+ }
+ }
+ Being* being = actorSpriteManager->findBeingByName(
+ name, Being::PLAYER);
+ if (being)
+ {
+ avatar->setDamageHp(being->getDamageTaken());
+ avatar->setLevel(being->getLevel());
+ avatar->setGender(being->getGender());
+ avatar->setIp(being->getIp());
+ }
+ }
+
+ void resetDamage(std::string name)
+ {
+ if (!actorSpriteManager)
+ return;
+
+ Avatar *avatar = findAvatarbyName(name);
+ if (!avatar)
+ return;
+ avatar->setDamageHp(0);
+ Being* being = actorSpriteManager->findBeingByName(
+ name, Being::PLAYER);
+
+ if (being)
+ being->setDamageTaken(0);
+ }
+
+ Avatar* findAvatarbyName(std::string name)
+ {
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+ if (!avatars)
+ return 0;
+
+ Avatar *ava = 0;
+ std::vector<Avatar*>::iterator i = avatars->begin();
+ while (i != avatars->end())
+ {
+ ava = (*i);
+ if (ava && ava->getName() == name)
+ return ava;
+ ++i;
+ }
+ ava = new Avatar(name);
+ ava->setOnline(true);
+ avatars->push_back(ava);
+ return ava;
+ }
+
+ void getPlayersAvatars()
+ {
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+ if (!avatars)
+ return;
+
+ if (actorSpriteManager)
+ {
+// std::list<Being*> beings = actorSpriteManager->getAll();
+ std::vector<std::string> names;
+ actorSpriteManager->getPlayerNames(names, false);
+
+ std::vector<Avatar*>::iterator ai = avatars->begin();
+ while (ai != avatars->end())
+ {
+ bool finded = false;
+ Avatar *ava = (*ai);
+ if (!ava)
+ break;
+
+ std::vector<std::string>::iterator i = names.begin();
+ while (i != names.end())
+ {
+ if (ava->getName() == (*i) && (*i) != "")
+ {
+ finded = true;
+ break;
+ }
+ ++i;
+ }
+
+ if (!finded)
+ avatars->erase(ai);
+ else
+ ++ai;
+ }
+
+ std::vector<std::string>::iterator i = names.begin();
+
+ while (i != names.end())
+ {
+ if ((*i) != "")
+ updateAvatar(*i);
+ ++i;
+ }
+ }
+ }
+
+protected:
+ void invite()
+ {
+ }
+
+ void leave()
+ {
+ }
+
+private:
+ BeingsListModal *mBeings;
+};
+
+
+class NavigationTab : public SocialTab
+{
+
+public:
+ NavigationTab()
+ {
+ mBeings = new BeingsListModal();
+
+ mList = new AvatarListBox(mBeings);
+ mScroll = new ScrollArea(mList);
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ setCaption(_("Nav"));
+
+ }
+
+ ~NavigationTab()
+ {
+ delete mList;
+ mList = 0;
+ delete mScroll;
+ mScroll = 0;
+ delete mBeings;
+ mBeings = 0;
+ }
+
+ void invite()
+ {
+ }
+
+ void leave()
+ {
+ }
+
+ void updateList()
+ {
+ if (!socialWindow || !player_node)
+ return;
+
+ Map* map = socialWindow->getMap();
+ if (!map)
+ return;
+
+ if (socialWindow->getProcessedPortals())
+ return;
+
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+ std::list<MapItem*> portals = map->getPortals();
+
+ std::list<MapItem*>::iterator i = portals.begin();
+ SpecialLayer *specialLayer = map->getSpecialLayer();
+
+ avatars->clear();
+
+ int idx = 0;
+ while (i != portals.end())
+ {
+ MapItem *portal = *i;
+ if (!portal)
+ continue;
+
+ int x = portal->getX();
+ int y = portal->getY();
+
+ std::string name = strprintf("%s [%d %d]",
+ portal->getComment().c_str(), x, y);
+
+ Avatar *ava = new Avatar(name);
+ if (player_node)
+ ava->setOnline(player_node->isReachable(x, y, 0));
+ else
+ ava->setOnline(false);
+ ava->setLevel(-1);
+ ava->setType(portal->getType());
+ ava->setX(x);
+ ava->setY(y);
+ avatars->push_back(ava);
+
+ if (config.getBoolValue("drawHotKeys") && idx < 80 && outfitWindow)
+ {
+ Being *being = actorSpriteManager->findPortalByTile(x, y);
+ if (being)
+ {
+ being->setName(keyboard.getKeyShortString(
+ outfitWindow->keyName(idx)));
+ }
+
+ if (specialLayer)
+ {
+ portal = specialLayer->getTile(ava->getX(), ava->getY());
+ if (portal)
+ {
+ portal->setName(keyboard.getKeyShortString(
+ outfitWindow->keyName(idx)));
+ }
+ }
+ }
+
+ i++;
+ idx ++;
+ }
+ if (socialWindow)
+ socialWindow->setProcessedPortals(true);
+ }
+
+
+ virtual void selectIndex(unsigned num)
+ {
+ if (!player_node)
+ return;
+
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+ if (!avatars || avatars->size() <= num)
+ return;
+
+ Avatar *ava = avatars->at(num);
+ if (ava && player_node)
+ player_node->navigateTo(ava->getX(), ava->getY());
+ }
+
+ void updateNames()
+ {
+ if (!socialWindow)
+ return;
+
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+ if (!avatars)
+ return;
+
+ Map *map = socialWindow->getMap();
+ if (!map)
+ return;
+
+ Avatar *ava = 0;
+ std::vector<Avatar*>::iterator i = avatars->begin();
+ while (i != avatars->end())
+ {
+ ava = (*i);
+ if (!ava)
+ break;
+
+ MapItem *item = map->findPortalXY(ava->getX(), ava->getY());
+ if (item)
+ {
+ std::string name = strprintf("%s [%d %d]",
+ item->getComment().c_str(), item->getX(), item->getY());
+ ava->setName(name);
+ ava->setOriginalName(name);
+ }
+
+ ++i;
+ }
+ }
+
+ int getPortalIndex(int x, int y)
+ {
+ if (!socialWindow)
+ return -1;
+
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+ if (!avatars)
+ return -1;
+
+ Map *map = socialWindow->getMap();
+ if (!map)
+ return 01;
+
+ Avatar *ava = 0;
+ std::vector<Avatar*>::iterator i = avatars->begin();
+ unsigned num = 0;
+ while (i != avatars->end())
+ {
+ ava = (*i);
+
+ if (!ava)
+ break;
+
+ if (ava->getX() == x && ava->getY() == y)
+ return num;
+
+ ++i;
+ num ++;
+ }
+ return -1;
+ }
+
+ void addPortal(int x, int y)
+ {
+ if (!socialWindow || !player_node)
+ return;
+
+ Map* map = socialWindow->getMap();
+ if (!map)
+ return;
+
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+
+ if (!avatars)
+ return;
+
+ MapItem *portal = map->findPortalXY(x, y);
+ if (!portal)
+ return;
+
+ std::string name = strprintf("%s [%d %d]",
+ portal->getComment().c_str(), x, y);
+
+ Avatar *ava = new Avatar(name);
+ if (player_node)
+ ava->setOnline(player_node->isReachable(x, y, 0));
+ else
+ ava->setOnline(false);
+ ava->setLevel(-1);
+ ava->setType(portal->getType());
+ ava->setX(x);
+ ava->setY(y);
+ avatars->push_back(ava);
+ }
+
+ void removePortal(int x, int y)
+ {
+ if (!socialWindow || !player_node)
+ return;
+
+ Map* map = socialWindow->getMap();
+ if (!map)
+ return;
+
+ std::vector<Avatar*> *avatars = mBeings->getMembers();
+
+ std::vector<Avatar*>::iterator i = avatars->begin();
+
+ if (!avatars)
+ return;
+
+ while (i != avatars->end())
+ {
+ Avatar *ava = (*i);
+
+ if (!ava)
+ break;
+
+ if (ava && ava->getX() == x && ava->getY() == y)
+ {
+ avatars->erase(i);
+ return;
+ }
+
+ ++ i;
+ }
+ }
+
+ void updateAvatar(std::string)
+ {
+ }
+
+ void resetDamage(std::string)
+ {
+ }
+
+private:
+ BeingsListModal *mBeings;
+
+protected:
+// friend class SocialWindow;
+};
+
+
+class CreatePopup : public Popup, public LinkHandler
+{
+public:
+ CreatePopup():
+ Popup("SocialCreatePopup")
+ {
+ mBrowserBox = new BrowserBox;
+ mBrowserBox->setPosition(4, 4);
+ mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
+ mBrowserBox->setOpaque(false);
+ mBrowserBox->setLinkHandler(this);
+
+ if (Net::getGuildHandler()->isSupported())
+ mBrowserBox->addRow(strprintf("@@guild|%s@@", _("Create Guild")));
+ mBrowserBox->addRow(strprintf("@@party|%s@@", _("Create Party")));
+ mBrowserBox->addRow("##3---");
+ mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));
+
+ add(mBrowserBox);
+
+ setContentSize(mBrowserBox->getWidth() + 8,
+ mBrowserBox->getHeight() + 8);
+ }
+
+ void handleLink(const std::string &link, gcn::MouseEvent *event _UNUSED_)
+ {
+ if (link == "guild" && socialWindow)
+ {
+ socialWindow->showGuildCreate();
+ }
+ else if (link == "party" && socialWindow)
+ {
+ socialWindow->showPartyCreate();
+ }
+
+ setVisible(false);
+ }
+
+ void show(gcn::Widget *parent)
+ {
+ if (!parent)
+ return;
+
+ int x, y;
+ parent->getAbsolutePosition(x, y);
+ y += parent->getHeight();
+ setPosition(x, y);
+ setVisible(true);
+ requestMoveToTop();
+ }
+
+private:
+ BrowserBox* mBrowserBox;
+};
+
+SocialWindow::SocialWindow() :
+ Window(_("Social")),
+ mGuildInvited(0),
+ mGuildAcceptDialog(0),
+ mPartyAcceptDialog(0),
+ mMap(0),
+ mLastUpdateTime(0),
+ mNeedUpdate(false),
+ mProcessedPortals(false)
+{
+ setWindowName("Social");
+ setVisible(false);
+ setSaveVisible(true);
+ setResizable(true);
+ setSaveVisible(true);
+ setCloseButton(true);
+ setMinWidth(120);
+ setMinHeight(55);
+ setDefaultSize(590, 200, 150, 120);
+ setupWindow->registerWindowForReset(this);
+
+ mCreateButton = new Button(_("Create"), "create", this);
+ mInviteButton = new Button(_("Invite"), "invite", this);
+ mLeaveButton = new Button(_("Leave"), "leave", this);
+ mTabs = new TabbedArea;
+
+ place(0, 0, mCreateButton);
+ place(1, 0, mInviteButton);
+ place(2, 0, mLeaveButton);
+ place(0, 1, mTabs, 4, 4);
+
+ widgetResized(NULL);
+
+ mCreatePopup = new CreatePopup();
+
+ loadWindowState();
+
+ mPlayers = new PlayersTab("P");
+ mTabs->addTab(mPlayers, mPlayers->mScroll);
+
+ mNavigation = new NavigationTab();
+ mTabs->addTab(mNavigation, mNavigation->mScroll);
+
+ if (player_node && player_node->getParty())
+ addTab(player_node->getParty());
+
+ if (player_node && player_node->getGuild())
+ addTab(player_node->getGuild());
+
+ updateButtons();
+}
+
+SocialWindow::~SocialWindow()
+{
+ // Cleanup invites
+ if (mGuildAcceptDialog)
+ {
+ mGuildAcceptDialog->close();
+ mGuildAcceptDialog->scheduleDelete();
+ mGuildAcceptDialog = NULL;
+
+ mGuildInvited = 0;
+ }
+
+ if (mPartyAcceptDialog)
+ {
+ mPartyAcceptDialog->close();
+ mPartyAcceptDialog->scheduleDelete();
+ mPartyAcceptDialog = NULL;
+
+ mPartyInviter = "";
+ }
+ delete mCreatePopup;
+ mCreatePopup = 0;
+ delete mPlayers;
+ mPlayers = 0;
+}
+
+bool SocialWindow::addTab(Guild *guild)
+{
+ if (mGuilds.find(guild) != mGuilds.end())
+ return false;
+
+ GuildTab *tab = new GuildTab(guild);
+ mGuilds[guild] = tab;
+
+ mTabs->addTab(tab, tab->mScroll);
+
+ updateButtons();
+
+ return true;
+}
+
+bool SocialWindow::removeTab(Guild *guild)
+{
+ GuildMap::iterator it = mGuilds.find(guild);
+ if (it == mGuilds.end())
+ return false;
+
+ mTabs->removeTab(it->second);
+ delete it->second;
+ mGuilds.erase(it);
+
+ updateButtons();
+
+ return true;
+}
+
+bool SocialWindow::addTab(Party *party)
+{
+ if (mParties.find(party) != mParties.end())
+ return false;
+
+ PartyTab *tab = new PartyTab(party);
+ mParties[party] = tab;
+
+ mTabs->addTab(tab, tab->mScroll);
+
+ updateButtons();
+
+ return true;
+}
+
+bool SocialWindow::removeTab(Party *party)
+{
+ PartyMap::iterator it = mParties.find(party);
+ if (it == mParties.end())
+ return false;
+
+ mTabs->removeTab(it->second);
+ delete it->second;
+ mParties.erase(it);
+
+ updateButtons();
+
+ return true;
+}
+
+void SocialWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+
+ if (event.getSource() == mPartyAcceptDialog)
+ {
+ // check if they accepted the invite
+ if (eventId == "yes")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ strprintf(_("Accepted party invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ Net::getPartyHandler()->inviteResponse(mPartyInviter, true);
+ }
+ else if (eventId == "no")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ strprintf(_("Rejected party invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ Net::getPartyHandler()->inviteResponse(mPartyInviter, false);
+ }
+
+ mPartyInviter = "";
+ mPartyAcceptDialog = NULL;
+ }
+ else if (event.getSource() == mGuildAcceptDialog)
+ {
+ // check if they accepted the invite
+ if (eventId == "yes")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ strprintf(_("Accepted guild invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ Net::getGuildHandler()->inviteResponse(mGuildInvited, true);
+ }
+ else if (eventId == "no")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ strprintf(_("Rejected guild invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ Net::getGuildHandler()->inviteResponse(mGuildInvited, false);
+ }
+
+ mGuildInvited = 0;
+ mGuildAcceptDialog = NULL;
+ }
+ else if (event.getId() == "create")
+ {
+ if (Net::getGuildHandler()->isSupported())
+ {
+ if (mCreatePopup)
+ mCreatePopup->show(mCreateButton);
+ }
+ else
+ {
+ showPartyCreate();
+ }
+ }
+ else if (event.getId() == "invite" && mTabs->getSelectedTabIndex() > -1)
+ {
+ if (mTabs->getSelectedTab())
+ static_cast<SocialTab*>(mTabs->getSelectedTab())->invite();
+ }
+ else if (event.getId() == "leave" && mTabs->getSelectedTabIndex() > -1)
+ {
+ if (mTabs->getSelectedTab())
+ static_cast<SocialTab*>(mTabs->getSelectedTab())->leave();
+ }
+ else if (event.getId() == "create guild")
+ {
+ std::string name = mGuildCreateDialog->getText();
+
+ if (name.size() > 16)
+ {
+ // TODO : State too many characters in input.
+ return;
+ }
+
+ Net::getGuildHandler()->create(name);
+ if (localChatTab)
+ {
+ localChatTab->chatLog(strprintf(_("Creating guild called %s."),
+ name.c_str()), BY_SERVER);
+ }
+
+ mGuildCreateDialog = NULL;
+ }
+ else if (event.getId() == "~create guild")
+ {
+ mGuildCreateDialog = NULL;
+ }
+ else if (event.getId() == "create party")
+ {
+ std::string name = mPartyCreateDialog->getText();
+
+ if (name.size() > 16)
+ {
+ // TODO : State too many characters in input.
+ return;
+ }
+
+ Net::getPartyHandler()->create(name);
+ if (localChatTab)
+ {
+ localChatTab->chatLog(strprintf(_("Creating party called %s."),
+ name.c_str()), BY_SERVER);
+ }
+
+ mPartyCreateDialog = NULL;
+ }
+ else if (event.getId() == "~create party")
+ {
+ mPartyCreateDialog = NULL;
+ }
+}
+
+void SocialWindow::showGuildCreate()
+{
+ mGuildCreateDialog = new TextDialog(_("Guild Name"),
+ _("Choose your guild's name."), this);
+ mGuildCreateDialog->setActionEventId("create guild");
+ mGuildCreateDialog->addActionListener(this);
+}
+
+void SocialWindow::showGuildInvite(const std::string &guildName,
+ const int guildId,
+ const std::string &inviterName)
+{
+ // check there isnt already an invite showing
+ if (mGuildInvited != 0)
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(_("Received guild request, but one already "
+ "exists."), BY_SERVER);
+ }
+ return;
+ }
+
+ std::string msg = strprintf(_("%s has invited you to join the guild %s."),
+ inviterName.c_str(), guildName.c_str());
+ if (localChatTab)
+ localChatTab->chatLog(msg, BY_SERVER);
+
+ // show invite
+ mGuildAcceptDialog = new ConfirmDialog(_("Accept Guild Invite"),
+ msg, false, false, this);
+ mGuildAcceptDialog->addActionListener(this);
+
+ mGuildInvited = guildId;
+}
+
+void SocialWindow::showPartyInvite(const std::string &partyName,
+ const std::string &inviter)
+{
+ // check there isnt already an invite showing
+ if (mPartyInviter != "")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(_("Received party request, but one already "
+ "exists."), BY_SERVER);
+ }
+ return;
+ }
+
+ std::string msg;
+ if (inviter.empty())
+ {
+ if (partyName.empty())
+ {
+ msg = _("You have been invited you to join a party.");
+ }
+ else
+ {
+ msg = strprintf(_("You have been invited to join the %s party."),
+ partyName.c_str());
+ }
+ }
+ else
+ {
+ if (partyName.empty())
+ {
+ msg = strprintf(_("%s has invited you to join their party."),
+ inviter.c_str());
+ }
+ else
+ {
+ msg = strprintf(_("%s has invited you to join the %s party."),
+ inviter.c_str(), partyName.c_str());
+ }
+ }
+
+ if (localChatTab)
+ localChatTab->chatLog(msg, BY_SERVER);
+
+ // show invite
+ mPartyAcceptDialog = new ConfirmDialog(_("Accept Party Invite"),
+ msg, false, false, this);
+ mPartyAcceptDialog->addActionListener(this);
+
+ mPartyInviter = inviter;
+}
+
+void SocialWindow::showPartyCreate()
+{
+ if (!player_node)
+ return;
+
+ if (player_node->getParty())
+ {
+ new OkDialog(_("Create Party"),
+ _("Cannot create party. You are already in a party"),
+ this);
+ return;
+ }
+
+ mPartyCreateDialog = new TextDialog(_("Party Name"),
+ _("Choose your party's name."), this);
+ mPartyCreateDialog->setActionEventId("create party");
+ mPartyCreateDialog->addActionListener(this);
+}
+
+void SocialWindow::updateActiveList()
+{
+ mNeedUpdate = true;
+}
+
+void SocialWindow::logic()
+{
+ unsigned int nowTime = cur_time;
+ if (nowTime - mLastUpdateTime > 1 && mNeedUpdate)
+ {
+ mPlayers->updateList();
+ mNeedUpdate = false;
+ mLastUpdateTime = nowTime;
+ }
+ else if (nowTime - mLastUpdateTime > 5)
+ {
+ mPlayers->updateList();
+ mNeedUpdate = false;
+ mLastUpdateTime = nowTime;
+ }
+
+ Window::logic();
+}
+
+void SocialWindow::updateAvatar(std::string name)
+{
+ mPlayers->updateAvatar(name);
+}
+
+void SocialWindow::resetDamage(std::string name)
+{
+ mPlayers->resetDamage(name);
+}
+
+void SocialWindow::updateButtons()
+{
+ if (!mTabs)
+ return;
+
+ bool hasTabs = mTabs->getNumberOfTabs() > 0;
+ mInviteButton->setEnabled(hasTabs);
+ mLeaveButton->setEnabled(hasTabs);
+}
+
+void SocialWindow::updatePortals()
+{
+ if (mNavigation)
+ mNavigation->updateList();
+}
+
+void SocialWindow::updatePortalNames()
+{
+ if (mNavigation)
+ static_cast<NavigationTab*>(mNavigation)->updateNames();
+}
+
+void SocialWindow::selectPortal(unsigned num)
+{
+ if (mNavigation)
+ mNavigation->selectIndex(num);
+}
+
+int SocialWindow::getPortalIndex(int x, int y)
+{
+ if (mNavigation)
+ return static_cast<NavigationTab*>(mNavigation)->getPortalIndex(x, y);
+ else
+ return -1;
+}
+
+void SocialWindow::addPortal(int x, int y)
+{
+ if (mNavigation)
+ static_cast<NavigationTab*>(mNavigation)->addPortal(x, y);
+}
+
+void SocialWindow::removePortal(int x, int y)
+{
+ if (mNavigation)
+ static_cast<NavigationTab*>(mNavigation)->removePortal(x, y);
+}
+
+void SocialWindow::nextTab()
+{
+ if (!mTabs)
+ return;
+
+ int tab = mTabs->getSelectedTabIndex();
+
+ tab++;
+ if (tab == mTabs->getNumberOfTabs())
+ tab = 0;
+
+ mTabs->setSelectedTab(tab);
+}
+
+void SocialWindow::prevTab()
+{
+ if (!mTabs)
+ return;
+
+ int tab = mTabs->getSelectedTabIndex();
+
+ if (tab == 0)
+ tab = mTabs->getNumberOfTabs();
+ tab--;
+
+ mTabs->setSelectedTab(tab);
+} \ No newline at end of file
diff --git a/src/gui/socialwindow.h b/src/gui/socialwindow.h
new file mode 100644
index 000000000..6e654c4c6
--- /dev/null
+++ b/src/gui/socialwindow.h
@@ -0,0 +1,159 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SOCIALWINDOW_H
+#define SOCIALWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionevent.hpp>
+#include <guichan/actionlistener.hpp>
+
+#include <string>
+#include <map>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Button;
+class ConfirmDialog;
+class CreatePopup;
+class Guild;
+class Map;
+class NavigateTab;
+class Party;
+class SocialTab;
+class Tab;
+class TabbedArea;
+class TextDialog;
+class PlayersTab;
+
+/**
+ * Party window.
+ *
+ * \ingroup Interface
+ */
+class SocialWindow : public Window, gcn::ActionListener
+{
+public:
+ SocialWindow();
+
+ ~SocialWindow();
+
+ bool addTab(Guild *guild);
+
+ bool removeTab(Guild *guild);
+
+ bool addTab(Party *party);
+
+ bool removeTab(Party *party);
+
+ /**
+ * Handle events.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ void showGuildInvite(const std::string &guildName, const int guildId,
+ const std::string &inviterName);
+
+ void showGuildCreate();
+
+ void showPartyInvite(const std::string &partyName,
+ const std::string &inviter = "");
+
+ void showPartyCreate();
+
+ void updateActiveList();
+
+ void updateAvatar(std::string name);
+
+ void resetDamage(std::string name);
+
+ void logic();
+
+ void updatePortals();
+
+ void updatePortalNames();
+
+ int getPortalIndex(int x, int y);
+
+ void addPortal(int x, int y);
+
+ void removePortal(int x, int y);
+
+ void nextTab();
+
+ void prevTab();
+
+ Map* getMap()
+ { return mMap; }
+
+ void setMap(Map *map)
+ { mMap = map; mProcessedPortals = false; }
+
+ bool getProcessedPortals()
+ { return mProcessedPortals; }
+
+ void setProcessedPortals(int n)
+ { mProcessedPortals = n; }
+
+ void selectPortal(unsigned num);
+
+protected:
+ friend class SocialTab;
+
+ void updateButtons();
+
+ int mGuildInvited;
+ ConfirmDialog *mGuildAcceptDialog;
+ TextDialog *mGuildCreateDialog;
+
+ std::string mPartyInviter;
+ ConfirmDialog *mPartyAcceptDialog;
+ TextDialog *mPartyCreateDialog;
+
+ typedef std::map<Guild*, SocialTab*> GuildMap;
+ GuildMap mGuilds;
+
+ typedef std::map<Party*, SocialTab*> PartyMap;
+ PartyMap mParties;
+
+ SocialTab *mPlayers;
+ SocialTab *mNavigation;
+
+ CreatePopup *mCreatePopup;
+
+ Button *mCreateButton;
+ Button *mInviteButton;
+ Button *mLeaveButton;
+ TabbedArea *mTabs;
+ Map *mMap;
+
+ int mLastUpdateTime;
+ bool mNeedUpdate;
+ bool mProcessedPortals;
+};
+
+extern SocialWindow *socialWindow;
+
+#endif // SOCIALWINDOW_H
diff --git a/src/gui/specialswindow.cpp b/src/gui/specialswindow.cpp
new file mode 100644
index 000000000..bd968191d
--- /dev/null
+++ b/src/gui/specialswindow.cpp
@@ -0,0 +1,257 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/specialswindow.h"
+
+#include "log.h"
+
+#include "gui/setup.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/container.h"
+#include "gui/widgets/icon.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/progressbar.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/tab.h"
+#include "gui/widgets/tabbedarea.h"
+#include "gui/widgets/flowcontainer.h"
+#include "gui/widgets/windowcontainer.h"
+
+#include "net/net.h"
+#include "net/specialhandler.h"
+
+#include "resources/specialdb.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+#include "utils/xml.h"
+
+#include <string>
+
+#define SPECIALS_WIDTH 200
+#define SPECIALS_HEIGHT 32
+
+class SpecialEntry;
+
+
+class SpecialEntry : public Container
+{
+ public:
+ SpecialEntry(SpecialInfo *info);
+
+ void update(int current, int needed);
+
+ protected:
+ friend class SpecialsWindow;
+ SpecialInfo *mInfo;
+
+ private:
+ Icon *mIcon; // icon to display
+ Label *mNameLabel; // name to display
+ Label *mLevelLabel; // level number label (only shown when applicable)
+ Button *mUse; // use button (only shown when applicable)
+ ProgressBar *mRechargeBar; // recharge bar (only shown when applicable)
+};
+
+SpecialsWindow::SpecialsWindow():
+ Window(_("Specials"))
+{
+ setWindowName("Specials");
+ setCloseButton(true);
+ setResizable(true);
+ setSaveVisible(true);
+ setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425);
+ setupWindow->registerWindowForReset(this);
+
+ mTabs = new TabbedArea();
+
+ place(0, 0, mTabs, 5, 5);
+
+ setLocationRelativeTo(getParent());
+ loadWindowState();
+}
+
+SpecialsWindow::~SpecialsWindow()
+{
+ // Clear gui
+}
+
+void SpecialsWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "use")
+ {
+ if (!event.getSource())
+ return;
+
+ SpecialEntry *disp = dynamic_cast<SpecialEntry*>(
+ event.getSource()->getParent());
+
+ if (disp)
+ {
+ /*Being *target = player_node->getTarget();
+
+ if (target)
+ Net::getSpecialHandler()->use(disp->mInfo->id,
+ 1, target->getId());
+ else*/
+ Net::getSpecialHandler()->use(disp->mInfo->id);
+ }
+ }
+ else if (event.getId() == "close")
+ {
+ setVisible(false);
+ }
+}
+
+void SpecialsWindow::draw(gcn::Graphics *graphics)
+{
+ // update the progress bars
+ std::map<int, Special> specialData = PlayerInfo::getSpecialStatus();
+ bool foundNew = false;
+ unsigned int found = 0; // number of entries in specialData
+ // which match mEntries
+
+ for (std::map<int, Special>::iterator i = specialData.begin();
+ i != specialData.end(); i++)
+ {
+ std::map<int, SpecialEntry *>::iterator e = mEntries.find(i->first);
+ if (e == mEntries.end())
+ {
+ // found a new special - abort update and rebuild from scratch
+ foundNew = true;
+ break;
+ }
+ else
+ {
+ // update progress bar of special
+ if (e->second)
+ e->second->update(i->second.currentMana, i->second.neededMana);
+ found++;
+ }
+ }
+ // a rebuild is needed when a) the number of specials changed or b)
+ // an existing entry isn't found anymore
+ if (foundNew || found != mEntries.size())
+ rebuild(specialData);
+
+ Window::draw(graphics);
+}
+
+void SpecialsWindow::rebuild(const std::map<int, Special> &specialData)
+{
+ make_dtor(mEntries);
+ mEntries.clear();
+ int vPos = 0; //vertical position of next placed element
+
+ for (std::map<int, Special>::const_iterator i = specialData.begin();
+ i != specialData.end();
+ i++)
+ {
+ logger->log("Updating special GUI for %d", i->first);
+
+ SpecialInfo* info = SpecialDB::get(i->first);
+ if (info)
+ {
+ info->rechargeCurrent = i->second.currentMana;
+ info->rechargeNeeded = i->second.neededMana;
+ SpecialEntry* entry = new SpecialEntry(info);
+ entry->setPosition(0, vPos);
+ vPos += entry->getHeight();
+ add(entry);
+ mEntries[i->first] = entry;
+ }
+ else
+ {
+ logger->log("Warning: No info available of special %d", i->first);
+ }
+ }
+}
+
+
+SpecialEntry::SpecialEntry(SpecialInfo *info) :
+ mInfo(info),
+ mIcon(NULL),
+ mLevelLabel(NULL),
+ mUse(NULL),
+ mRechargeBar(NULL)
+{
+ setFrameSize(1);
+ setOpaque(false);
+ setSize(SPECIALS_WIDTH, SPECIALS_HEIGHT);
+
+ if (info && !info->icon.empty())
+ mIcon = new Icon(info->icon);
+ else
+ mIcon = new Icon(Theme::resolveThemePath("unknown-item.png"));
+
+ mIcon->setPosition(1, 0);
+ add(mIcon);
+
+ if (info)
+ mNameLabel = new Label(info->name);
+ else
+ mNameLabel = new Label("");
+
+ mNameLabel->setPosition(35, 0);
+ add(mNameLabel);
+
+ if (info && info->hasLevel)
+ {
+ mLevelLabel = new Label(toString(info->level));
+ mLevelLabel->setPosition(getWidth() - mLevelLabel->getWidth(), 0);
+ add(mLevelLabel);
+ }
+
+ if (info && info->isActive)
+ {
+ mUse = new Button("Use", "use", specialsWindow);
+ mUse->setPosition(getWidth() - mUse->getWidth(), 13);
+ add(mUse);
+ }
+
+ if (info->hasRechargeBar)
+ {
+ float progress = 0;
+ if (info->rechargeNeeded)
+ {
+ progress = (float)info->rechargeCurrent
+ / (float)info->rechargeNeeded;
+ }
+ mRechargeBar = new ProgressBar(progress, 100, 10, Theme::PROG_MP);
+ mRechargeBar->setSmoothProgress(false);
+ mRechargeBar->setPosition(0, 13);
+ add(mRechargeBar);
+ }
+
+}
+
+void SpecialEntry::update(int current, int needed)
+{
+ if (mRechargeBar && needed)
+ {
+ float progress = (float)current / (float)needed;
+ mRechargeBar->setProgress(progress);
+ }
+}
diff --git a/src/gui/specialswindow.h b/src/gui/specialswindow.h
new file mode 100644
index 000000000..66ef5e375
--- /dev/null
+++ b/src/gui/specialswindow.h
@@ -0,0 +1,73 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SPECIALSWINDOW_H
+#define SPECIALSWINDOW_H
+
+#include <vector>
+
+#include "guichanfwd.h"
+
+#include "playerinfo.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <map>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Label;
+class ScrollArea;
+class Tab;
+class TabbedArea;
+
+struct SpecialEntry;
+
+class SpecialsWindow : public Window, public gcn::ActionListener
+{
+ public:
+ SpecialsWindow();
+
+ ~SpecialsWindow();
+
+ /**
+ * Called when receiving actions from widget.
+ */
+ void action(const gcn::ActionEvent &actionEvent);
+
+ void draw(gcn::Graphics *graphics);
+
+ private:
+ // (re)constructs the list of specials
+ void rebuild(const std::map<int, Special> &specialData);
+
+ TabbedArea *mTabs;
+ std::map<int, SpecialEntry *> mEntries;
+};
+
+extern SpecialsWindow *specialsWindow;
+
+#endif // SPECIALSWINDOW_H
diff --git a/src/gui/speechbubble.cpp b/src/gui/speechbubble.cpp
new file mode 100644
index 000000000..08d000380
--- /dev/null
+++ b/src/gui/speechbubble.cpp
@@ -0,0 +1,91 @@
+/*
+ * Speech bubbles
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/speechbubble.h"
+
+#include "graphics.h"
+
+#include "gui/gui.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/textbox.h"
+
+#include <guichan/font.hpp>
+
+#include <guichan/widgets/label.hpp>
+
+SpeechBubble::SpeechBubble():
+ Popup("Speech", "speechbubble.xml")
+{
+ setContentSize(140, 46);
+ setMinWidth(29);
+ setMinHeight(29);
+
+ mCaption = new gcn::Label;
+ mCaption->setFont(boldFont);
+
+ mSpeechBox = new TextBox;
+ mSpeechBox->setEditable(false);
+ mSpeechBox->setOpaque(false);
+ mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::CHAT));
+
+ add(mCaption);
+ add(mSpeechBox);
+}
+
+void SpeechBubble::setCaption(const std::string &name, const gcn::Color *color)
+{
+ mCaption->setCaption(name);
+ mCaption->adjustSize();
+ mCaption->setForegroundColor(*color);
+}
+
+void SpeechBubble::setText(const std::string &text, bool showName)
+{
+ if (text == mText && (mCaption->getWidth() <= mSpeechBox->getMinWidth()))
+ return;
+
+ mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::TEXT));
+
+ int width = mCaption->getWidth() + 2 * getPadding();
+ mSpeechBox->setTextWrapped(text, 130 > width ? 130 : width);
+ const int speechWidth = mSpeechBox->getMinWidth() + 2 * getPadding();
+
+ const int fontHeight = getFont()->getHeight();
+ const int nameHeight = showName ? mCaption->getHeight() +
+ (getPadding() / 2) : 0;
+ const int numRows = mSpeechBox->getNumberOfRows();
+ const int height = (numRows * fontHeight) + nameHeight + getPadding();
+
+ if (width < speechWidth)
+ width = speechWidth;
+
+ width += 2 * getPadding();
+
+ setContentSize(width, height);
+
+ const int xPos = ((getWidth() - width) / 2);
+ const int yPos = ((getHeight() - height) / 2) + nameHeight;
+
+ mCaption->setPosition(xPos, getPadding());
+ mSpeechBox->setPosition(xPos, yPos);
+}
diff --git a/src/gui/speechbubble.h b/src/gui/speechbubble.h
new file mode 100644
index 000000000..8682ab7e9
--- /dev/null
+++ b/src/gui/speechbubble.h
@@ -0,0 +1,58 @@
+/*
+ * Speech bubbles
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SPEECHBUBBLE_H
+#define SPEECHBUBBLE_H
+
+#include "gui/theme.h"
+
+#include "gui/widgets/popup.h"
+
+class TextBox;
+
+class SpeechBubble : public Popup
+{
+ public:
+ /**
+ * Constructor. Initializes the speech bubble.
+ */
+ SpeechBubble();
+
+ /**
+ * Sets the name displayed for the speech bubble, and in what color.
+ */
+ void setCaption(const std::string &name,
+ const gcn::Color *color =
+ &Theme::getThemeColor(Theme::TEXT));
+
+ /**
+ * Sets the text to be displayed.
+ */
+ void setText(const std::string &text, bool showName = true);
+
+ private:
+ std::string mText;
+ gcn::Label *mCaption;
+ TextBox *mSpeechBox;
+};
+
+#endif
diff --git a/src/gui/spellpopup.cpp b/src/gui/spellpopup.cpp
new file mode 100644
index 000000000..80fa9f378
--- /dev/null
+++ b/src/gui/spellpopup.cpp
@@ -0,0 +1,105 @@
+/*
+ * The Mana World
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/spellpopup.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+
+#include "textcommand.h"
+
+#include "graphics.h"
+#include "units.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+#include <guichan/widgets/label.hpp>
+
+SpellPopup::SpellPopup():
+ Popup("SpellPopup")
+{
+ // Item Name
+ mItemName = new gcn::Label;
+ mItemName->setFont(boldFont);
+ mItemName->setPosition(getPadding(), getPadding());
+
+ add(mItemName);
+ addMouseListener(this);
+}
+
+SpellPopup::~SpellPopup()
+{
+}
+
+void SpellPopup::setItem(TextCommand *spell)
+{
+ if (spell)
+ mItemName->setCaption(spell->getName());
+ else
+ mItemName->setCaption("?");
+
+ mItemName->adjustSize();
+ int minWidth = mItemName->getWidth();
+
+ minWidth += 8;
+ setWidth(minWidth);
+
+ setContentSize(minWidth, getPadding() + getFont()->getHeight());
+}
+
+void SpellPopup::view(int x, int y)
+{
+ const int distance = 20;
+
+ int posX = std::max(0, x - getWidth() / 2);
+ int posY = y + distance;
+
+ if (posX + getWidth() > graphics->getWidth())
+ {
+ if (graphics->getWidth() > getWidth())
+ posX = graphics->getWidth() - getWidth();
+ else
+ posX = 0;
+ }
+ if (posY + getHeight() > graphics->getHeight())
+ {
+ if (y > getHeight() + distance)
+ posY = y - getHeight() - distance;
+ else
+ y = 0;
+ }
+
+ setPosition(posX, posY);
+ setVisible(true);
+ requestMoveToTop();
+}
+
+void SpellPopup::mouseMoved(gcn::MouseEvent &event)
+{
+ Popup::mouseMoved(event);
+
+ // When the mouse moved on top of the popup, hide it
+ setVisible(false);
+}
diff --git a/src/gui/spellpopup.h b/src/gui/spellpopup.h
new file mode 100644
index 000000000..1b14e0e4c
--- /dev/null
+++ b/src/gui/spellpopup.h
@@ -0,0 +1,67 @@
+/*
+ * The Mana World
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef SPELLPOPUP_H
+#define SPELLPOPUP_H
+
+#include "gui/widgets/popup.h"
+
+#include "textcommand.h"
+
+#include <guichan/mouselistener.hpp>
+
+class TextBox;
+
+/**
+ * A popup that displays information about an item.
+ */
+class SpellPopup : public Popup
+{
+ public:
+ /**
+ * Constructor. Initializes the item popup.
+ */
+ SpellPopup();
+
+ /**
+ * Destructor. Cleans up the item popup on deletion.
+ */
+ ~SpellPopup();
+
+ /**
+ * Sets the info to be displayed given a particular item.
+ */
+ void setItem(TextCommand *spell);
+
+ /**
+ * Sets the location to display the item popup.
+ */
+ void view(int x, int y);
+
+ void mouseMoved(gcn::MouseEvent &mouseEvent);
+
+ private:
+ gcn::Label *mItemName;
+};
+
+#endif // SPELLPOPUP_H
diff --git a/src/gui/statuspopup.cpp b/src/gui/statuspopup.cpp
new file mode 100644
index 000000000..4d2c48fa7
--- /dev/null
+++ b/src/gui/statuspopup.cpp
@@ -0,0 +1,543 @@
+/*
+ * The Mana World
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/statuspopup.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+
+#include "gui/widgets/layout.h"
+#include "gui/widgets/textbox.h"
+
+#include "graphics.h"
+#include "localplayer.h"
+#include "units.h"
+#include "viewport.h"
+#include "keyboardconfig.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+#include <guichan/widgets/label.hpp>
+
+StatusPopup::StatusPopup():
+ Popup("StatusPopup")
+{
+
+ const int fontHeight = getFont()->getHeight();
+
+ mMoveType = new gcn::Label;
+ mMoveType->setPosition(getPadding(), getPadding());
+
+ mCrazyMoveType = new gcn::Label;
+ mCrazyMoveType->setPosition(getPadding(), fontHeight + getPadding());
+
+ mMoveToTargetType = new gcn::Label;
+ mMoveToTargetType->setPosition(getPadding(),
+ 2 * fontHeight + getPadding());
+
+ mFollowMode = new gcn::Label;
+ mFollowMode->setPosition(getPadding(), 3 * fontHeight + getPadding());
+
+ mAttackWeaponType = new gcn::Label;
+ mAttackWeaponType->setPosition(getPadding(),
+ 4 + 4 * fontHeight + getPadding());
+
+ mAttackType = new gcn::Label;
+ mAttackType->setPosition(getPadding(), 4 + 5 * fontHeight + getPadding());
+
+ mMagicAttackType = new gcn::Label;
+ mMagicAttackType->setPosition(getPadding(),
+ 4 + 6 * fontHeight + getPadding());
+
+ mDropCounter = new gcn::Label;
+ mDropCounter->setPosition(getPadding(), 8 + 7 * fontHeight + getPadding());
+
+ mPickUpType = new gcn::Label;
+ mPickUpType->setPosition(getPadding(), 8 + 8 * fontHeight + getPadding());
+
+ mMapType = new gcn::Label;
+ mMapType->setPosition(getPadding(), 12 + 9 * fontHeight + getPadding());
+
+ mImitationMode = new gcn::Label;
+ mImitationMode->setPosition(getPadding(),
+ 16 + 10 * fontHeight + getPadding());
+
+ mAwayMode = new gcn::Label;
+ mAwayMode->setPosition(getPadding(), 16 + 11 * fontHeight + getPadding());
+
+ mCameraMode = new gcn::Label;
+ mCameraMode->setPosition(getPadding(),
+ 16 + 12 * fontHeight + getPadding());
+
+ mDisableGameModifiers = new gcn::Label;
+ mDisableGameModifiers->setPosition(getPadding(),
+ 20 + 13 * fontHeight + getPadding());
+
+ add(mMoveType);
+ add(mCrazyMoveType);
+ add(mMoveToTargetType);
+ add(mFollowMode);
+ add(mAttackWeaponType);
+ add(mAttackType);
+ add(mDropCounter);
+ add(mPickUpType);
+ add(mMapType);
+ add(mMagicAttackType);
+ add(mDisableGameModifiers);
+ add(mImitationMode);
+ add(mAwayMode);
+ add(mCameraMode);
+
+// addMouseListener(this);
+}
+
+StatusPopup::~StatusPopup()
+{
+}
+
+void StatusPopup::update()
+{
+ updateLabels();
+
+ int minWidth = mMoveType->getWidth();
+
+ if (mMoveToTargetType->getWidth() > minWidth)
+ minWidth = mMoveToTargetType->getWidth();
+ if (mFollowMode->getWidth() > minWidth)
+ minWidth = mFollowMode->getWidth();
+ if (mCrazyMoveType->getWidth() > minWidth)
+ minWidth = mCrazyMoveType->getWidth();
+ if (mAttackWeaponType->getWidth() > minWidth)
+ minWidth = mAttackWeaponType->getWidth();
+ if (mAttackType->getWidth() > minWidth)
+ minWidth = mAttackType->getWidth();
+ if (mDropCounter->getWidth() > minWidth)
+ minWidth = mDropCounter->getWidth();
+ if (mPickUpType->getWidth() > minWidth)
+ minWidth = mPickUpType->getWidth();
+ if (mMapType->getWidth() > minWidth)
+ minWidth = mMapType->getWidth();
+ if (mMagicAttackType->getWidth() > minWidth)
+ minWidth = mMagicAttackType->getWidth();
+ if (mDisableGameModifiers->getWidth() > minWidth)
+ minWidth = mDisableGameModifiers->getWidth();
+ if (mAwayMode->getWidth() > minWidth)
+ minWidth = mAwayMode->getWidth();
+ if (mCameraMode->getWidth() > minWidth)
+ minWidth = mCameraMode->getWidth();
+ if (mImitationMode->getWidth() > minWidth)
+ minWidth = mImitationMode->getWidth();
+
+ minWidth += 16 + 2 * getPadding();
+ setWidth(minWidth);
+
+ const int fontHeight = getFont()->getHeight();
+
+ setHeight(24 + 8 + 14 * fontHeight + getPadding());
+}
+
+void StatusPopup::view(int x, int y)
+{
+ const int distance = 20;
+
+ int posX = std::max(0, x - getWidth() / 2);
+ int posY = y + distance;
+
+ if (posX + getWidth() > graphics->getWidth())
+ posX = graphics->getWidth() - getWidth();
+ if (posY + getHeight() > graphics->getHeight())
+ posY = y - getHeight() - distance;
+
+ update();
+
+ setPosition(posX, posY);
+ setVisible(true);
+ requestMoveToTop();
+}
+
+void StatusPopup::updateLabels()
+{
+ if (!player_node || !viewport)
+ return;
+
+ switch (player_node->getInvertDirection())
+ {
+ case 0:
+ mMoveType->setCaption("(D) default moves "
+ + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION));
+ break;
+
+ case 1:
+ mMoveType->setCaption("(I) invert moves "
+ + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION));
+ break;
+
+ case 2:
+ mMoveType->setCaption("(c) moves with some crazy moves "
+ + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION));
+
+ case 3:
+ mMoveType->setCaption("(C) moves with crazy moves "
+ + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION));
+ break;
+
+ case 4:
+ mMoveType->setCaption("(d) double normal + crazy "
+ + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION));
+ break;
+
+ default:
+ mMoveType->setCaption("(?) move "
+ + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION));
+ break;
+ }
+ mMoveType->adjustSize();
+
+ if (player_node->getCrazyMoveType() < 10)
+ {
+ mCrazyMoveType->setCaption(strprintf("(%d) crazy move number %d ",
+ player_node->getCrazyMoveType(), player_node->getCrazyMoveType())
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE));
+ }
+ else
+ {
+ switch (player_node->getCrazyMoveType())
+ {
+ case 10:
+ mCrazyMoveType->setCaption("(a) custom crazy move "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE));
+ break;
+ default:
+ mCrazyMoveType->setCaption("(?) crazy move "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE));
+ break;
+ }
+ }
+ mCrazyMoveType->adjustSize();
+
+ switch (player_node->getMoveToTargetType())
+ {
+ case 0:
+ mMoveToTargetType->setCaption("(0) default moves to target "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ case 1:
+ mMoveToTargetType->setCaption("(1) moves to target in distance 1 "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ case 2:
+ mMoveToTargetType->setCaption("(2) moves to target in distance 3 "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ case 3:
+ mMoveToTargetType->setCaption("(3) moves to target in distance 3 "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ case 4:
+ mMoveToTargetType->setCaption("(5) moves to target in distance 5 "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ case 5:
+ mMoveToTargetType->setCaption("(7) moves to target in distance 7 "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ case 6:
+ mMoveToTargetType->setCaption(
+ "(A) moves to target in attack range "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ default:
+ mMoveToTargetType->setCaption("(?) move to target "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_MOVE_TO_TARGET));
+ break;
+ }
+ mMoveToTargetType->adjustSize();
+
+ switch (player_node->getFollowMode())
+ {
+ case 0:
+ mFollowMode->setCaption("(D) default follow "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE));
+ break;
+ case 1:
+ mFollowMode->setCaption("(R) relative follow "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE));
+ break;
+ case 2:
+ mFollowMode->setCaption("(M) mirror follow "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE));
+ break;
+ case 3:
+ mFollowMode->setCaption("(P) pet follow "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE));
+ break;
+ default:
+ mFollowMode->setCaption("(?) unknown follow "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE));
+ break;
+ }
+ mFollowMode->adjustSize();
+
+ switch (player_node->getAttackWeaponType())
+ {
+ case 1:
+ mAttackWeaponType->setCaption("(D) default attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE));
+ break;
+ case 2:
+ mAttackWeaponType->setCaption("(s) switch attack without shield "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE));
+ break;
+ case 3:
+ mAttackWeaponType->setCaption("(S) switch attack with shield "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE));
+ break;
+ default:
+ mAttackWeaponType->setCaption("(?) attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE));
+ break;
+ }
+ mAttackWeaponType->adjustSize();
+
+ switch (player_node->getAttackType())
+ {
+ case 0:
+ mAttackType->setCaption("(D) default attack "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE));
+ break;
+ case 1:
+ mAttackType->setCaption("(G) go and attack "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE));
+ break;
+ case 2:
+ mAttackType->setCaption("(A) go, attack, pickup "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE));
+ break;
+ case 3:
+ mAttackType->setCaption("(d) without auto attack "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE));
+ break;
+ default:
+ mAttackType->setCaption("(?) attack "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE));
+ break;
+ }
+ mAttackType->adjustSize();
+
+ mDropCounter->setCaption(strprintf("(%d) drop counter %d ",
+ player_node->getQuickDropCounter(), player_node->getQuickDropCounter())
+ + keyboard.getKeyValueString(keyboard.KEY_SWITCH_QUICK_DROP));
+ mDropCounter->adjustSize();
+
+ switch (player_node->getPickUpType())
+ {
+ case 0:
+ mPickUpType->setCaption("(S) small pick up 1x1 cells "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ case 1:
+ mPickUpType->setCaption("(D) default pick up 2x1 cells "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ case 2:
+ mPickUpType->setCaption("(F) forward pick up 2x3 cells "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ case 3:
+ mPickUpType->setCaption("(3) pick up 3x3 cells "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ case 4:
+ mPickUpType->setCaption("(g) go and pick up in distance 4 "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ case 5:
+ mPickUpType->setCaption("(G) go and pick up in distance 8 "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ case 6:
+ mPickUpType->setCaption("(A) go and pick up in max distance "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ default:
+ mPickUpType->setCaption("(?) pick up "
+ + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE));
+ break;
+ }
+ mPickUpType->adjustSize();
+
+ switch (viewport->getDebugPath())
+ {
+ case 0:
+ mMapType->setCaption("(N) normal map view "
+ + keyboard.getKeyValueString(keyboard.KEY_PATHFIND));
+ break;
+ case 1:
+ mMapType->setCaption("(D) debug map view "
+ + keyboard.getKeyValueString(keyboard.KEY_PATHFIND));
+ break;
+ case 2:
+ mMapType->setCaption("(u) ultra map view "
+ + keyboard.getKeyValueString(keyboard.KEY_PATHFIND));
+ break;
+ case 3:
+ mMapType->setCaption("(U) ultra map view 2 "
+ + keyboard.getKeyValueString(keyboard.KEY_PATHFIND));
+ break;
+ case 4:
+ mMapType->setCaption("(e) empty map view "
+ + keyboard.getKeyValueString(keyboard.KEY_PATHFIND));
+ break;
+ case 5:
+ mMapType->setCaption("(b) black & white map view "
+ + keyboard.getKeyValueString(keyboard.KEY_PATHFIND));
+ break;
+ default:
+ mMapType->setCaption("(?) map view "
+ + keyboard.getKeyValueString(keyboard.KEY_PATHFIND));
+ break;
+ }
+ mMapType->adjustSize();
+
+ switch (player_node->getMagicAttackType())
+ {
+ case 0:
+ mMagicAttackType->setCaption("(f) use #flar for magic attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_SWITCH_MAGIC_ATTACK));
+ break;
+ case 1:
+ mMagicAttackType->setCaption("(c) use #chiza for magic attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_SWITCH_MAGIC_ATTACK));
+ break;
+ case 2:
+ mMagicAttackType->setCaption("(I) use #ingrav for magic attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_SWITCH_MAGIC_ATTACK));
+ break;
+ case 3:
+ mMagicAttackType->setCaption("(F) use #frillyar for magic attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_SWITCH_MAGIC_ATTACK));
+ break;
+ case 4:
+ mMagicAttackType->setCaption("(U) use #upmarmu for magic attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_SWITCH_MAGIC_ATTACK));
+ break;
+ default:
+ mMagicAttackType->setCaption("(?) magic attack "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_SWITCH_MAGIC_ATTACK));
+ break;
+ }
+ mMagicAttackType->adjustSize();
+
+ switch (player_node->getImitationMode())
+ {
+ case 0:
+ mImitationMode->setCaption("(D) default imitation "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_IMITATION_MODE));
+ break;
+ case 1:
+ mImitationMode->setCaption("(O) outfits imitation "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_IMITATION_MODE));
+ break;
+ default:
+ mImitationMode->setCaption("(?) imitation "
+ + keyboard.getKeyValueString(
+ keyboard.KEY_CHANGE_IMITATION_MODE));
+ break;
+ }
+ mImitationMode->adjustSize();
+
+ switch (player_node->getAwayMode())
+ {
+ case 0:
+ mAwayMode->setCaption("(O) on keyboard "
+ + keyboard.getKeyValueString(keyboard.KEY_AWAY));
+ break;
+ case 1:
+ mAwayMode->setCaption("(A) away "
+ + keyboard.getKeyValueString(keyboard.KEY_AWAY));
+ break;
+ default:
+ mAwayMode->setCaption("(?) away "
+ + keyboard.getKeyValueString(keyboard.KEY_AWAY));
+ break;
+ }
+ mAwayMode->adjustSize();
+
+ switch (viewport->getCameraMode())
+ {
+ case 0:
+ mCameraMode->setCaption("(G) game camera mode "
+ + keyboard.getKeyValueString(keyboard.KEY_CAMERA));
+ break;
+ case 1:
+ mCameraMode->setCaption("(F) free camera mode "
+ + keyboard.getKeyValueString(keyboard.KEY_CAMERA));
+ break;
+ case 2:
+ mCameraMode->setCaption("(D) design camera mode "
+ + keyboard.getKeyValueString(keyboard.KEY_CAMERA));
+ break;
+ default:
+ mCameraMode->setCaption("(?) away "
+ + keyboard.getKeyValueString(keyboard.KEY_CAMERA));
+ break;
+ }
+ mCameraMode->adjustSize();
+
+ if (player_node->getDisableGameModifiers())
+ {
+ mDisableGameModifiers->setCaption("Game modifiers are disabled "
+ + keyboard.getKeyValueString(keyboard.KEY_DISABLE_GAME_MODIFIERS));
+ }
+ else
+ {
+ mDisableGameModifiers->setCaption("Game modifiers are enabled "
+ + keyboard.getKeyValueString(keyboard.KEY_DISABLE_GAME_MODIFIERS));
+ }
+ mDisableGameModifiers->adjustSize();
+}
diff --git a/src/gui/statuspopup.h b/src/gui/statuspopup.h
new file mode 100644
index 000000000..62d952384
--- /dev/null
+++ b/src/gui/statuspopup.h
@@ -0,0 +1,78 @@
+/*
+ * The Mana World
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef StatusPopup_H
+#define StatusPopup_H
+
+#include "gui/widgets/popup.h"
+
+#include "resources/iteminfo.h"
+
+#include <guichan/mouselistener.hpp>
+
+class TextBox;
+
+/**
+ * A popup that displays information about an item.
+ */
+class StatusPopup : public Popup
+{
+ public:
+ /**
+ * Constructor. Initializes the item popup.
+ */
+ StatusPopup();
+
+ /**
+ * Destructor. Cleans up the item popup on deletion.
+ */
+ ~StatusPopup();
+
+ /**
+ * Sets the location to display the item popup.
+ */
+ void view(int x, int y);
+
+// void mouseMoved(gcn::MouseEvent &mouseEvent);
+
+ void update();
+
+ private:
+ void updateLabels();
+ gcn::Label *mMoveType;
+ gcn::Label *mCrazyMoveType;
+ gcn::Label *mMoveToTargetType;
+ gcn::Label *mFollowMode;
+ gcn::Label *mAttackType;
+ gcn::Label *mAttackWeaponType;
+ gcn::Label *mDropCounter;
+ gcn::Label *mPickUpType;
+ gcn::Label *mMapType;
+ gcn::Label *mMagicAttackType;
+ gcn::Label *mDisableGameModifiers;
+ gcn::Label *mImitationMode;
+ gcn::Label *mAwayMode;
+ gcn::Label *mCameraMode;
+};
+
+#endif // StatusPopup_H
diff --git a/src/gui/statuswindow.cpp b/src/gui/statuswindow.cpp
new file mode 100644
index 000000000..349893bfa
--- /dev/null
+++ b/src/gui/statuswindow.cpp
@@ -0,0 +1,880 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/statuswindow.h"
+
+#include "configuration.h"
+#include "event.h"
+#include "localplayer.h"
+#include "playerinfo.h"
+#include "units.h"
+#include "viewport.h"
+
+#include "gui/setup.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/progressbar.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/vertcontainer.h"
+#include "gui/widgets/windowcontainer.h"
+
+#include "net/net.h"
+#include "net/playerhandler.h"
+#include "net/gamehandler.h"
+
+#include "utils/gettext.h"
+#include "utils/mathutils.h"
+#include "utils/stringutils.h"
+
+class AttrDisplay : public Container
+{
+ public:
+ enum Type
+ {
+ DERIVED = 0,
+ CHANGEABLE,
+ UNKNOWN
+ };
+
+ ~AttrDisplay();
+
+ virtual std::string update();
+
+ virtual Type getType()
+ { return UNKNOWN; }
+
+ protected:
+ AttrDisplay(int id, const std::string &name);
+
+ const int mId;
+ const std::string mName;
+
+ LayoutHelper *mLayout;
+ Label *mLabel;
+ Label *mValue;
+};
+
+class DerDisplay : public AttrDisplay
+{
+ public:
+ DerDisplay(int id, const std::string &name);
+
+ virtual Type getType()
+ { return DERIVED; }
+};
+
+class ChangeDisplay : public AttrDisplay, gcn::ActionListener
+{
+ public:
+ ChangeDisplay(int id, const std::string &name);
+
+ std::string update();
+
+ virtual Type getType()
+ { return CHANGEABLE; }
+
+ void setPointsNeeded(int needed);
+
+ private:
+ void action(const gcn::ActionEvent &event);
+
+ int mNeeded;
+
+ Label *mPoints;
+ Button *mDec;
+ Button *mInc;
+};
+
+StatusWindow::StatusWindow():
+ Window(player_node ? player_node->getName() : "?")
+{
+ listen(CHANNEL_ATTRIBUTES);
+
+ setWindowName("Status");
+ setupWindow->registerWindowForReset(this);
+ setResizable(true);
+ setCloseButton(true);
+ setSaveVisible(true);
+ setDefaultSize((windowContainer->getWidth() - 365) / 2,
+ (windowContainer->getHeight() - 255) / 2, 365, 275);
+
+ // ----------------------
+ // Status Part
+ // ----------------------
+
+ mLvlLabel = new Label(strprintf(_("Level: %d"), 0));
+ mMoneyLabel = new Label(strprintf(_("Money: %s"), ""));
+
+ int max = PlayerInfo::getAttribute(MAX_HP);
+ if (!max)
+ max = 1;
+
+ mHpLabel = new Label(_("HP:"));
+ mHpBar = new ProgressBar(max ?
+ static_cast<float>(PlayerInfo::getAttribute(HP))
+ / static_cast<float>(max):
+ static_cast<float>(0), 80, 15, Theme::PROG_HP);
+
+ max = PlayerInfo::getAttribute(EXP_NEEDED);
+ mXpLabel = new Label(_("Exp:"));
+ mXpBar = new ProgressBar(max ?
+ static_cast<float>(PlayerInfo::getAttribute(EXP))
+ / static_cast<float>(max):
+ static_cast<float>(0), 80, 15, Theme::PROG_EXP);
+
+ bool magicBar = Net::getGameHandler()->canUseMagicBar();
+
+ int job = Net::getPlayerHandler()->getJobLocation()
+ && serverConfig.getValueBool("showJob", false);
+
+ if (magicBar)
+ {
+ max = PlayerInfo::getAttribute(MAX_MP);
+ mMpLabel = new Label(_("MP:"));
+ mMpBar = new ProgressBar(max ?
+ static_cast<float>(PlayerInfo::getAttribute(MAX_MP))
+ / static_cast<float>(max) : static_cast<float>(0),
+ 80, 15, Net::getPlayerHandler()->canUseMagic() ?
+ Theme::PROG_MP : Theme::PROG_NO_MP);
+ }
+ else
+ {
+ mMpLabel = 0;
+ mMpBar = 0;
+ }
+
+ place(0, 0, mLvlLabel, 3);
+ // 5, 0 Job Level
+ place(8, 0, mMoneyLabel, 3);
+ place(0, 1, mHpLabel).setPadding(3);
+ place(1, 1, mHpBar, 4);
+ place(5, 1, mXpLabel).setPadding(3);
+ place(6, 1, mXpBar, 5);
+ if (magicBar)
+ {
+ place(0, 2, mMpLabel).setPadding(3);
+ // 5, 2 and 6, 2 Job Progress Bar
+ if (job)
+ place(1, 2, mMpBar, 4);
+ else
+ place(1, 2, mMpBar, 10);
+ }
+
+ if (job)
+ {
+ mJobLvlLabel = new Label(strprintf(_("Job: %d"), 0));
+ mJobLabel = new Label(_("Job:"));
+ mJobBar = new ProgressBar(0.0f, 80, 15, Theme::PROG_JOB);
+
+ place(5, 0, mJobLvlLabel, 3);
+ place(5, 2, mJobLabel).setPadding(3);
+ place(6, 2, mJobBar, 5);
+ }
+ else
+ {
+ mJobLvlLabel = 0;
+ mJobLabel = 0;
+ mJobBar = 0;
+ }
+
+ // ----------------------
+ // Stats Part
+ // ----------------------
+
+ mAttrCont = new VertContainer(32);
+ mAttrScroll = new ScrollArea(mAttrCont);
+ mAttrScroll->setOpaque(false);
+ mAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ mAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO);
+ place(0, 3, mAttrScroll, 5, 3);
+
+ mDAttrCont = new VertContainer(32);
+ mDAttrScroll = new ScrollArea(mDAttrCont);
+ mDAttrScroll->setOpaque(false);
+ mDAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ mDAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO);
+ place(6, 3, mDAttrScroll, 5, 3);
+
+ getLayout().setRowHeight(3, Layout::AUTO_SET);
+
+ mCharacterPointsLabel = new Label("C");
+ place(0, 6, mCharacterPointsLabel, 5);
+
+ if (Net::getPlayerHandler()->canCorrectAttributes())
+ {
+ mCorrectionPointsLabel = new Label("C");
+ place(0, 7, mCorrectionPointsLabel, 5);
+ }
+
+ loadWindowState();
+
+ // Update bars
+ updateHPBar(mHpBar, true);
+ if (magicBar)
+ updateMPBar(mMpBar, true);
+ updateXPBar(mXpBar, false);
+
+ mMoneyLabel->setCaption(strprintf(_("Money: %s"),
+ Units::formatCurrency(PlayerInfo::getAttribute(MONEY)).c_str()));
+ mMoneyLabel->adjustSize();
+ mCharacterPointsLabel->setCaption(strprintf(_("Character points: %d"),
+ PlayerInfo::getAttribute(CHAR_POINTS)));
+ mCharacterPointsLabel->adjustSize();
+
+ if (player_node && player_node->isGM())
+ {
+ mLvlLabel->setCaption(strprintf(_("Level: %d (GM %d)"),
+ PlayerInfo::getAttribute(LEVEL), player_node->getGMLevel()));
+ }
+ else
+ {
+ mLvlLabel->setCaption(strprintf(_("Level: %d"),
+ PlayerInfo::getAttribute(LEVEL)));
+ }
+ mLvlLabel->adjustSize();
+}
+
+void StatusWindow::event(Channels channel _UNUSED_,
+ const Mana::Event &event)
+{
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ switch(event.getInt("id"))
+ {
+ case HP: case MAX_HP:
+ updateHPBar(mHpBar, true);
+ break;
+
+ case MP: case MAX_MP:
+ updateMPBar(mMpBar, true);
+ break;
+
+ case EXP: case EXP_NEEDED:
+ updateXPBar(mXpBar, false);
+ break;
+
+ case MONEY:
+ mMoneyLabel->setCaption(strprintf(_("Money: %s"),
+ Units::formatCurrency(event.getInt("newValue")).c_str()));
+ mMoneyLabel->adjustSize();
+ break;
+
+ case CHAR_POINTS:
+ mCharacterPointsLabel->setCaption(strprintf(
+ _("Character points: %d"), event.getInt("newValue")));
+
+ mCharacterPointsLabel->adjustSize();
+ // Update all attributes
+ for (Attrs::iterator it = mAttrs.begin();
+ it != mAttrs.end(); it++)
+ {
+ if (it->second)
+ it->second->update();
+ }
+ break;
+
+ case CORR_POINTS:
+ mCorrectionPointsLabel->setCaption(strprintf(
+ _("Correction points: %d"), event.getInt("newValue")));
+ mCorrectionPointsLabel->adjustSize();
+ // Update all attributes
+ for (Attrs::iterator it = mAttrs.begin();
+ it != mAttrs.end(); it++)
+ {
+ if (it->second)
+ it->second->update();
+ }
+ break;
+
+ case LEVEL:
+ mLvlLabel->setCaption(strprintf(_("Level: %d"),
+ event.getInt("newValue")));
+ mLvlLabel->adjustSize();
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (event.getName() == EVENT_UPDATESTAT)
+ {
+ int id = event.getInt("id");
+ if (id == Net::getPlayerHandler()->getJobLocation())
+ {
+ if (mJobLvlLabel)
+ {
+ mJobLvlLabel->setCaption(strprintf(_("Job: %d"),
+ PlayerInfo::getStatBase(id)));
+ mJobLvlLabel->adjustSize();
+
+ updateProgressBar(mJobBar, id, false);
+ }
+ }
+ else
+ {
+ updateMPBar(mMpBar, true);
+ Attrs::iterator it = mAttrs.find(id);
+ if (it != mAttrs.end() && it->second)
+ {
+ if (it->second)
+ it->second->update();
+ }
+ }
+ }
+}
+
+void StatusWindow::setPointsNeeded(int id, int needed)
+{
+ Attrs::iterator it = mAttrs.find(id);
+
+ if (it != mAttrs.end())
+ {
+ AttrDisplay *disp = it->second;
+ if (disp && disp->getType() == AttrDisplay::CHANGEABLE)
+ static_cast<ChangeDisplay*>(disp)->setPointsNeeded(needed);
+ }
+}
+
+void StatusWindow::addAttribute(int id, const std::string &name,
+ bool modifiable,
+ const std::string &description _UNUSED_)
+{
+ AttrDisplay *disp;
+
+ if (modifiable)
+ {
+ disp = new ChangeDisplay(id, name);
+ mAttrCont->add(disp);
+ }
+ else
+ {
+ disp = new DerDisplay(id, name);
+ mDAttrCont->add(disp);
+ }
+
+ mAttrs[id] = disp;
+}
+
+void StatusWindow::updateHPBar(ProgressBar *bar, bool showMax)
+{
+ if (!bar)
+ return;
+
+ if (showMax)
+ bar->setText(toString(PlayerInfo::getAttribute(HP)) +
+ "/" + toString(PlayerInfo::getAttribute(MAX_HP)));
+ else
+ bar->setText(toString(PlayerInfo::getAttribute(HP)));
+
+ float prog = 1.0;
+
+ if (PlayerInfo::getAttribute(MAX_HP) > 0)
+ {
+ prog = static_cast<float>(PlayerInfo::getAttribute(HP))
+ / static_cast<float>(PlayerInfo::getAttribute(MAX_HP));
+ }
+ bar->setProgress(prog);
+}
+
+void StatusWindow::updateMPBar(ProgressBar *bar, bool showMax)
+{
+ if (!bar)
+ return;
+
+ if (showMax)
+ {
+ bar->setText(toString(PlayerInfo::getAttribute(MP)) +
+ "/" + toString(PlayerInfo::getAttribute(MAX_MP)));
+ }
+ else
+ {
+ bar->setText(toString(PlayerInfo::getAttribute(MP)));
+ }
+
+ float prog = 1.0f;
+
+ if (PlayerInfo::getAttribute(MAX_MP) > 0)
+ {
+ if (PlayerInfo::getAttribute(MAX_MP))
+ {
+ prog = static_cast<float>(PlayerInfo::getAttribute(MP))
+ / static_cast<float>(PlayerInfo::getAttribute(MAX_MP));
+ }
+ else
+ {
+ prog = static_cast<float>(PlayerInfo::getAttribute(MP));
+ }
+ }
+
+ if (Net::getPlayerHandler()->canUseMagic())
+ bar->setProgressPalette(Theme::PROG_MP);
+ else
+ bar->setProgressPalette(Theme::PROG_NO_MP);
+
+ bar->setProgress(prog);
+}
+
+void StatusWindow::updateProgressBar(ProgressBar *bar, int value, int max,
+ bool percent)
+{
+ if (!bar)
+ return;
+
+ if (max == 0)
+ {
+ bar->setText(_("Max"));
+ bar->setProgress(1.0);
+ }
+ else
+ {
+ float progress = static_cast<float>(value)
+ / static_cast<float>(max);
+
+ if (percent)
+ bar->setText(strprintf("%2.5f", 100 * progress) + "%");
+ else
+ bar->setText(toString(value) + "/" + toString(max));
+
+ bar->setProgress(progress);
+ }
+}
+
+void StatusWindow::updateXPBar(ProgressBar *bar, bool percent)
+{
+ if (!bar)
+ return;
+
+ updateProgressBar(bar, PlayerInfo::getAttribute(EXP),
+ PlayerInfo::getAttribute(EXP_NEEDED), percent);
+}
+
+void StatusWindow::updateProgressBar(ProgressBar *bar, int id, bool percent)
+{
+ std::pair<int, int> exp = PlayerInfo::getStatExperience(id);
+ updateProgressBar(bar, exp.first, exp.second, percent);
+}
+
+void StatusWindow::updateStatusBar(ProgressBar *bar, bool percent _UNUSED_)
+{
+ if (!player_node || !viewport)
+ return;
+
+ std::string str;
+
+ switch (player_node->getInvertDirection())
+ {
+ case 0:
+ str = "D";
+ break;
+ case 1:
+ str = "I";
+ break;
+ case 2:
+ str = "c";
+ break;
+ case 3:
+ str = "C";
+ break;
+ case 4:
+ str = "d";
+ break;
+ default:
+ str = "?";
+ break;
+ }
+
+ if (player_node->getCrazyMoveType() < 10)
+ str += toString(player_node->getCrazyMoveType());
+ else
+ {
+ switch (player_node->getCrazyMoveType())
+ {
+ case 10:
+ str += "a";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+ }
+
+ switch (player_node->getMoveToTargetType())
+ {
+ case 0:
+ str += "0";
+ break;
+ case 1:
+ str += "1";
+ break;
+ case 2:
+ str += "2";
+ break;
+ case 3:
+ str += "3";
+ break;
+ case 4:
+ str += "5";
+ break;
+ case 5:
+ str += "7";
+ break;
+ case 6:
+ str += "A";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ switch (player_node->getFollowMode())
+ {
+ case 0:
+ str += "D";
+ break;
+ case 1:
+ str += "R";
+ break;
+ case 2:
+ str += "M";
+ break;
+ case 3:
+ str += "P";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ str += " ";
+ switch (player_node->getAttackWeaponType())
+ {
+ case 1:
+ str += "D";
+ break;
+ case 2:
+ str += "s";
+ break;
+ case 3:
+ str += "S";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ switch (player_node->getAttackType())
+ {
+ case 0:
+ str += "D";
+ break;
+ case 1:
+ str += "G";
+ break;
+ case 2:
+ str += "A";
+ break;
+ case 3:
+ str += "d";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ switch (player_node->getMagicAttackType())
+ {
+ case 0:
+ str += "f";
+ break;
+ case 1:
+ str += "c";
+ break;
+ case 2:
+ str += "I";
+ break;
+ case 3:
+ str += "F";
+ break;
+ case 4:
+ str += "U";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ str += " " + toString(player_node->getQuickDropCounter());
+
+ switch (player_node->getPickUpType())
+ {
+ case 0:
+ str += "S";
+ break;
+ case 1:
+ str += "D";
+ break;
+ case 2:
+ str += "F";
+ break;
+ case 3:
+ str += "3";
+ break;
+ case 4:
+ str += "g";
+ break;
+ case 5:
+ str += "G";
+ break;
+ case 6:
+ str += "A";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ switch (viewport->getDebugPath())
+ {
+ case 0:
+ str += " N";
+ break;
+ case 1:
+ str += " D";
+ break;
+ case 2:
+ str += " u";
+ break;
+ case 3:
+ str += " U";
+ break;
+ case 4:
+ str += " e";
+ break;
+ case 5:
+ str += " b";
+ break;
+ default:
+ str += " ?";
+ break;
+ }
+
+ switch (player_node->getImitationMode())
+ {
+ case 0:
+ str += " D";
+ break;
+ case 1:
+ str += " O";
+ break;
+ default:
+ str += " ?";
+ break;
+ }
+
+ switch (viewport->getCameraMode())
+ {
+ case 0:
+ str += "G";
+ break;
+ case 1:
+ str += "F";
+ break;
+ case 2:
+ str += "D";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ switch (player_node->getAwayMode())
+ {
+ case 0:
+ str += "O";
+ break;
+ case 1:
+ str += "A";
+ break;
+ default:
+ str += "?";
+ break;
+ }
+
+ bar->setText(str);
+ bar->setProgress(50);
+ if (player_node->getDisableGameModifiers())
+ {
+ gcn::Color col;
+ col.r = 100;
+ col.g = 100;
+ col.b = 100;
+// bar->setColor(new gcn::Color(100, 100, 100));
+ bar->setColor(col);
+ }
+ else
+ {
+ gcn::Color col;
+ col.r = 255;
+ col.g = 255;
+ col.b = 0;
+// bar->setColor(new gcn::Color(255, 255, 0));
+ bar->setColor(col);
+ }
+}
+
+AttrDisplay::AttrDisplay(int id, const std::string &name):
+ mId(id),
+ mName(name)
+{
+ setSize(100, 32);
+ mLabel = new Label(name);
+ mValue = new Label("1 ");
+
+ mLabel->setAlignment(Graphics::CENTER);
+ mValue->setAlignment(Graphics::CENTER);
+
+ mLayout = new LayoutHelper(this);
+}
+
+AttrDisplay::~AttrDisplay()
+{
+ delete mLayout;
+}
+
+std::string AttrDisplay::update()
+{
+ int base = PlayerInfo::getStatBase(mId);
+ int bonus = PlayerInfo::getStatMod(mId);
+ std::string value = toString(base + bonus);
+ if (bonus)
+ value += strprintf("=%d%+d", base, bonus);
+ mValue->setCaption(value);
+ return mName;
+}
+
+DerDisplay::DerDisplay(int id, const std::string &name):
+ AttrDisplay(id, name)
+{
+ // Do the layout
+ LayoutHelper h(this);
+ ContainerPlacer place = mLayout->getPlacer(0, 0);
+
+ place(0, 0, mLabel, 3);
+ place(3, 0, mValue, 2);
+
+ update();
+}
+
+ChangeDisplay::ChangeDisplay(int id, const std::string &name):
+ AttrDisplay(id, name), mNeeded(1)
+{
+ mPoints = new Label(_("Max"));
+ mInc = new Button(_("+"), "inc", this);
+
+ // Do the layout
+ ContainerPlacer place = mLayout->getPlacer(0, 0);
+
+ place(0, 0, mLabel, 3);
+ place(4, 0, mValue, 2);
+ place(6, 0, mInc);
+ place(7, 0, mPoints);
+
+ if (Net::getPlayerHandler()->canCorrectAttributes())
+ {
+ mDec = new Button(_("-"), "dec", this);
+ mDec->setWidth(mInc->getWidth());
+
+ place(3, 0, mDec);
+ }
+ else
+ {
+ mDec = 0;
+ }
+
+ update();
+}
+
+std::string ChangeDisplay::update()
+{
+ if (mNeeded > 0)
+ mPoints->setCaption(toString(mNeeded));
+ else
+ mPoints->setCaption(_("Max"));
+
+ if (mDec)
+ mDec->setEnabled(PlayerInfo::getAttribute(CORR_POINTS));
+
+ mInc->setEnabled(PlayerInfo::getAttribute(CHAR_POINTS) >= mNeeded &&
+ mNeeded > 0);
+
+ return AttrDisplay::update();
+}
+
+void ChangeDisplay::setPointsNeeded(int needed)
+{
+ mNeeded = needed;
+
+ update();
+}
+
+void ChangeDisplay::action(const gcn::ActionEvent &event)
+{
+ if (Net::getPlayerHandler()->canCorrectAttributes() &&
+ event.getSource() == mDec)
+ {
+ int newcorpoints = PlayerInfo::getAttribute(CORR_POINTS) - 1;
+ PlayerInfo::setAttribute(CORR_POINTS, newcorpoints);
+
+ int newpoints = PlayerInfo::getAttribute(CHAR_POINTS) + 1;
+ PlayerInfo::setAttribute(CHAR_POINTS, newpoints);
+
+ int newbase = PlayerInfo::getStatBase(mId) - 1;
+ PlayerInfo::setStatBase(mId, newbase);
+
+ Net::getPlayerHandler()->decreaseAttribute(mId);
+ }
+ else if (event.getSource() == mInc)
+ {
+ int cnt = 1;
+ if (config.getBoolValue("quickStats"))
+ {
+ cnt = mInc->getClickCount();
+ if (cnt > 10)
+ cnt = 10;
+ }
+
+ int newpoints = PlayerInfo::getAttribute(CHAR_POINTS) - cnt;
+ PlayerInfo::setAttribute(CHAR_POINTS, newpoints);
+
+ int newbase = PlayerInfo::getStatBase(mId) + cnt;
+ PlayerInfo::setStatBase(mId, newbase);
+
+ for (unsigned f = 0; f < mInc->getClickCount(); f ++)
+ {
+ Net::getPlayerHandler()->increaseAttribute(mId);
+ if (cnt != 1)
+ SDL_Delay(100);
+ }
+ }
+}
diff --git a/src/gui/statuswindow.h b/src/gui/statuswindow.h
new file mode 100644
index 000000000..4b324073e
--- /dev/null
+++ b/src/gui/statuswindow.h
@@ -0,0 +1,99 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef STATUS_H
+#define STATUS_H
+
+#include "guichanfwd.h"
+#include "listener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <map>
+
+class AttrDisplay;
+class ProgressBar;
+class ScrollArea;
+class VertContainer;
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+/**
+ * The player status dialog.
+ *
+ * \ingroup Interface
+ */
+class StatusWindow : public Window, public Mana::Listener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ StatusWindow();
+
+ void event(Channels channel, const Mana::Event &event);
+
+ void setPointsNeeded(int id, int needed);
+
+ void addAttribute(int id, const std::string &name, bool modifiable,
+ const std::string &description);
+
+ static void updateHPBar(ProgressBar *bar, bool showMax = false);
+ static void updateMPBar(ProgressBar *bar, bool showMax = false);
+ static void updateXPBar(ProgressBar *bar, bool percent = true);
+ static void updateStatusBar(ProgressBar *bar, bool percent = true);
+ static void updateProgressBar(ProgressBar *bar, int value, int max,
+ bool percent);
+ void updateProgressBar(ProgressBar *bar, int id,
+ bool percent = true);
+
+ private:
+ /**
+ * Status Part
+ */
+ gcn::Label *mLvlLabel, *mMoneyLabel;
+ gcn::Label *mHpLabel, *mMpLabel, *mXpLabel;
+ ProgressBar *mHpBar, *mMpBar, *mXpBar;
+
+ gcn::Label *mJobLvlLabel, *mJobLabel;
+ ProgressBar *mJobBar;
+
+ VertContainer *mAttrCont;
+ ScrollArea *mAttrScroll;
+ VertContainer *mDAttrCont;
+ ScrollArea *mDAttrScroll;
+
+ gcn::Label *mCharacterPointsLabel;
+ gcn::Label *mCorrectionPointsLabel;
+
+ typedef std::map<int, AttrDisplay*> Attrs;
+ Attrs mAttrs;
+};
+
+extern StatusWindow *statusWindow;
+
+#endif
diff --git a/src/gui/textcommandeditor.cpp b/src/gui/textcommandeditor.cpp
new file mode 100644
index 000000000..2300033a5
--- /dev/null
+++ b/src/gui/textcommandeditor.cpp
@@ -0,0 +1,390 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/textcommandeditor.h"
+
+#include <SDL.h>
+#include <SDL_thread.h>
+#include <vector>
+#include <algorithm>
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/inttextfield.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/layouthelper.h"
+#include "gui/widgets/radiobutton.h"
+#include "gui/widgets/table.h"
+#include "gui/widgets/textfield.h"
+
+#include "chat.h"
+#include "configuration.h"
+#include "item.h"
+#include "localplayer.h"
+#include "main.h"
+#include "keyboardconfig.h"
+#include "spellmanager.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include "resources/itemdb.h"
+#include "resources/iteminfo.h"
+
+class IconsModal : public gcn::ListModel
+{
+public:
+ IconsModal()
+ {
+ std::map<int, ItemInfo*> info = ItemDB::getItemInfos();
+ std::list<std::string> tempStrings;
+
+ for (std::map<int, ItemInfo*>::const_iterator
+ i = info.begin(), i_end = info.end();
+ i != i_end; ++i)
+ {
+ if (i->first < 0)
+ continue;
+
+ ItemInfo info = (*i->second);
+ std::string name = info.getName();
+ if (name != "unnamed" && !info.getName().empty()
+ && info.getName() != "unnamed")
+ {
+ tempStrings.push_back(name);
+ }
+ }
+ tempStrings.sort();
+ mStrings.push_back("");
+ for (std::list<std::string>::const_iterator i = tempStrings.begin(),
+ i_end = tempStrings.end(); i != i_end; ++i)
+ {
+ mStrings.push_back(*i);
+ }
+ }
+
+ virtual ~IconsModal()
+ {}
+
+ virtual int getNumberOfElements()
+ {
+ return static_cast<int>(mStrings.size());
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i < 0 || i >= getNumberOfElements())
+ return _("???");
+
+ return mStrings.at(i);
+ }
+private:
+ std::vector<std::string> mStrings;
+};
+
+
+const char *TARGET_TYPE_TEXT[3] =
+{
+ N_("No Target"),
+ N_("Allow Target"),
+ N_("Need Target"),
+};
+
+const char *MAGIC_SCHOOL_TEXT[6] =
+{
+ N_("General Magic"),
+ N_("Life Magic"),
+ N_("War Magic"),
+ N_("Transmute Magic"),
+ N_("Nature Magic"),
+ N_("Astral Magic")
+};
+
+class TargetTypeModel : public gcn::ListModel
+{
+public:
+ virtual ~TargetTypeModel() { }
+
+ virtual int getNumberOfElements()
+ {
+ return 3;
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return TARGET_TYPE_TEXT[i];
+ }
+};
+
+class MagicSchoolModel : public gcn::ListModel
+{
+public:
+ virtual ~MagicSchoolModel() { }
+
+ virtual int getNumberOfElements()
+ {
+ return 6;
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return _("???");
+
+ return MAGIC_SCHOOL_TEXT[i];
+ }
+};
+
+
+TextCommandEditor::TextCommandEditor(TextCommand *command):
+ Window(_("Command Editor"))
+{
+ int w = 350;
+ int h = 350;
+
+ mEnabledKeyboard = keyboard.isEnabled();
+ keyboard.setEnabled(false);
+
+ setWindowName("TextCommandEditor");
+ //setCloseButton(true);
+ setDefaultSize(w, h, ImageRect::CENTER);
+
+ mAdvanced = false;
+ mCommand = command;
+
+ mIsMagicCommand = (command->getCommandType() == TEXT_COMMAND_MAGIC);
+
+ mIsMagic = new RadioButton(_("magic"), "magic", mIsMagicCommand);
+ mIsMagic->setActionEventId("magic");
+ mIsMagic->addActionListener(this);
+
+ mIsOther = new RadioButton(_("other"), "magic", !mIsMagicCommand);
+ mIsOther->setActionEventId("other");
+ mIsOther->addActionListener(this);
+
+
+ mSymbolLabel = new Label(_("Symbol:"));
+ mSymbolTextField = new TextField();
+
+ mCommandLabel = new Label(_("Command:"));
+ mCommandTextField = new TextField();
+
+ mManaLabel = new Label(_("Mana:"));
+ mManaField = new IntTextField(0);
+ mManaField->setRange(0, 500);
+ mManaField->setWidth(20);
+
+ mTypeLabel = new Label(_("Target Type:"));
+ mTypeDropDown = new DropDown(new TargetTypeModel);
+ mTypeDropDown->setActionEventId("type");
+ mTypeDropDown->addActionListener(this);
+
+ mIconLabel = new Label(_("Icon:"));
+ mIconDropDown = new DropDown(new IconsModal);
+ mIconDropDown->setActionEventId("icon");
+ mIconDropDown->addActionListener(this);
+ mIconDropDown->setSelectedString(mCommand->getIcon());
+
+ mMagicLvlLabel = new Label(_("Magic level:"));
+ mMagicLvlField = new IntTextField(0);
+ mMagicLvlField->setRange(0, 5);
+ mMagicLvlField->setWidth(20);
+
+ mSchoolLabel = new Label(_("Magic School:"));
+ mSchoolDropDown = new DropDown(new MagicSchoolModel);
+ mSchoolDropDown->setActionEventId("school");
+ mSchoolDropDown->addActionListener(this);
+ mSchoolDropDown->setSelected(0);
+
+ mSchoolLvlLabel = new Label(_("School level:"));
+ mSchoolLvlField = new IntTextField(0);
+ mSchoolLvlField->setRange(0, 5);
+ mSchoolLvlField->setWidth(20);
+
+ mSaveButton = new Button(_("Save"), "save", this);
+ mSaveButton->adjustSize();
+
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+ mCancelButton->adjustSize();
+
+ mDeleteButton = new Button(_("Delete"), "delete", this);
+ mDeleteButton->adjustSize();
+
+ if (command->getCommandType() == TEXT_COMMAND_MAGIC)
+ showControls(true);
+ else
+ showControls(false);
+
+ mSymbolTextField->setText(command->getSymbol());
+ mCommandTextField->setText(command->getCommand());
+ mManaField->setValue(command->getMana());
+ mTypeDropDown->setSelected(command->getTargetType());
+ mMagicLvlField->setValue(command->getBaseLvl());
+ mSchoolDropDown->setSelected(command->getSchool() - MAGIC_START_ID);
+ mSchoolLvlField->setValue(command->getSchoolLvl());
+
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+
+ place(0, 0, mIsMagic, 1);
+ place(2, 0, mIsOther, 1);
+ place(0, 1, mSymbolLabel, 2).setPadding(3);
+ place(2, 1, mSymbolTextField, 3).setPadding(3);
+ place(0, 2, mCommandLabel, 2).setPadding(3);
+ place(2, 2, mCommandTextField, 4).setPadding(3);
+ place(0, 3, mTypeLabel, 2).setPadding(3);
+ place(2, 3, mTypeDropDown, 3).setPadding(3);
+
+ place(0, 4, mIconLabel, 2).setPadding(3);
+ place(2, 4, mIconDropDown, 3).setPadding(3);
+
+ place(0, 5, mManaLabel, 2).setPadding(3);
+ place(2, 5, mManaField, 3).setPadding(3);
+ place(0, 6, mMagicLvlLabel, 2).setPadding(3);
+ place(2, 6, mMagicLvlField, 3).setPadding(3);
+
+ place(0, 7, mSchoolLabel, 2).setPadding(3);
+ place(2, 7, mSchoolDropDown, 3).setPadding(3);
+ place(0, 8, mSchoolLvlLabel, 2).setPadding(3);
+ place(2, 8, mSchoolLvlField, 3).setPadding(3);
+
+ place(0, 9, mSaveButton, 2).setPadding(3);
+ place(2, 9, mCancelButton, 2).setPadding(3);
+ place(4, 9, mDeleteButton, 2).setPadding(3);
+
+ setWidth(w);
+ setHeight(h);
+
+ center();
+
+ setVisible(true);
+}
+
+TextCommandEditor::~TextCommandEditor()
+{
+}
+
+void TextCommandEditor::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+
+ if (eventId == "magic")
+ {
+ mIsMagicCommand = true;
+ showControls(true);
+ }
+ else if (eventId == "other")
+ {
+ mIsMagicCommand = false;
+ showControls(false);
+ }
+ else if (eventId == "save")
+ {
+ save();
+ scheduleDelete();
+ }
+ else if (eventId == "cancel")
+ {
+ scheduleDelete();
+ }
+ else if (eventId == "delete")
+ {
+ deleteCommand();
+ scheduleDelete();
+ }
+}
+
+void TextCommandEditor::update()
+{
+}
+
+void TextCommandEditor::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+}
+
+void TextCommandEditor::updateList()
+{
+}
+
+void TextCommandEditor::reset()
+{
+}
+
+void TextCommandEditor::showControls(bool show)
+{
+ mManaField->setVisible(show);
+ mManaLabel->setVisible(show);
+ mMagicLvlLabel->setVisible(show);
+ mMagicLvlField->setVisible(show);
+ mSchoolLabel->setVisible(show);
+ mSchoolDropDown->setVisible(show);
+ mSchoolLvlLabel->setVisible(show);
+ mSchoolLvlField->setVisible(show);
+}
+
+void TextCommandEditor::scheduleDelete()
+{
+ keyboard.setEnabled(mEnabledKeyboard);
+ Window::scheduleDelete();
+}
+
+void TextCommandEditor::save()
+{
+ if (mIsMagicCommand)
+ mCommand->setCommandType(TEXT_COMMAND_MAGIC);
+ else
+ mCommand->setCommandType(TEXT_COMMAND_TEXT);
+
+ mCommand->setSymbol(mSymbolTextField->getText());
+ mCommand->setCommand(mCommandTextField->getText());
+ mCommand->setMana(mManaField->getValue());
+ mCommand->setTargetType(
+ static_cast<SpellTarget>(mTypeDropDown->getSelected()));
+ mCommand->setIcon(mIconDropDown->getSelectedString());
+ mCommand->setBaseLvl(mMagicLvlField->getValue());
+ mCommand->setSchool(static_cast<MagicSchool>(
+ mSchoolDropDown->getSelected() + MAGIC_START_ID));
+ mCommand->setSchoolLvl(mSchoolLvlField->getValue());
+ if (spellManager)
+ spellManager->save();
+}
+
+void TextCommandEditor::deleteCommand()
+{
+ mCommand->setCommandType(TEXT_COMMAND_TEXT);
+ mCommand->setSymbol("");
+ mCommand->setCommand("");
+ mCommand->setMana(0);
+ mCommand->setTargetType(NOTARGET);
+ mCommand->setIcon("");
+ mCommand->setBaseLvl(0);
+ mCommand->setSchool(SKILL_MAGIC);
+ mCommand->setSchoolLvl(0);
+ if (spellManager)
+ spellManager->save();
+}
diff --git a/src/gui/textcommandeditor.h b/src/gui/textcommandeditor.h
new file mode 100644
index 000000000..9f8c0ad51
--- /dev/null
+++ b/src/gui/textcommandeditor.h
@@ -0,0 +1,105 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TEXTCOMMANDEDITOR_H
+#define TEXTCOMMANDEDITOR_H
+
+#include "gui/widgets/window.h"
+
+#include "textcommand.h"
+
+#include <guichan/actionlistener.hpp>
+
+class RadioButton;
+class Label;
+class TextBox;
+class TextField;
+class DropDown;
+class ListModel;
+class Button;
+class TextCommand;
+class IntTextField;
+
+class TextCommandEditor : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ TextCommandEditor(TextCommand *command);
+
+ /**
+ * Destructor.
+ */
+ ~TextCommandEditor();
+
+ void action(const gcn::ActionEvent &event);
+
+ void update();
+
+ void widgetResized(const gcn::Event &event);
+
+ void updateList();
+
+ void reset();
+
+ void scheduleDelete();
+
+ private:
+ void showControls(bool show);
+
+ void save();
+
+ void deleteCommand();
+
+ TextCommand *mCommand;
+ bool mAdvanced;
+
+ RadioButton *mIsMagic;
+ RadioButton *mIsOther;
+ Label *mSymbolLabel;
+ TextField *mSymbolTextField;
+ Label *mCommandLabel;
+ TextField *mCommandTextField;
+ Label *mTypeLabel;
+ DropDown *mTypeDropDown;
+ Label *mIconLabel;
+ DropDown *mIconDropDown;
+ Label *mManaLabel;
+ IntTextField *mManaField;
+ Label *mMagicLvlLabel;
+ IntTextField *mMagicLvlField;
+ Label *mSchoolLabel;
+ DropDown *mSchoolDropDown;
+ Label *mSchoolLvlLabel;
+ IntTextField *mSchoolLvlField;
+
+ //Button *mAdvancedButton;
+ Button *mCancelButton;
+ Button *mSaveButton;
+ Button *mDeleteButton;
+
+ bool mEnabledKeyboard;
+ bool mIsMagicCommand;
+};
+
+#endif
diff --git a/src/gui/textdialog.cpp b/src/gui/textdialog.cpp
new file mode 100644
index 000000000..c47e028c4
--- /dev/null
+++ b/src/gui/textdialog.cpp
@@ -0,0 +1,94 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/textdialog.h"
+
+#include "keyboardconfig.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/textfield.h"
+
+#include "utils/gettext.h"
+
+int TextDialog::instances = 0;
+
+TextDialog::TextDialog(const std::string &title, const std::string &msg,
+ Window *parent):
+ Window(title, true, parent),
+ mTextField(new TextField)
+{
+ mEnabledKeyboard = keyboard.isEnabled();
+ keyboard.setEnabled(false);
+
+ gcn::Label *textLabel = new Label(msg);
+ mOkButton = new Button(_("OK"), "OK", this);
+ gcn::Button *cancelButton = new Button(_("Cancel"), "CANCEL", this);
+
+ place(0, 0, textLabel, 4);
+ place(0, 1, mTextField, 4);
+ place(2, 2, mOkButton);
+ place(3, 2, cancelButton);
+
+ reflowLayout(static_cast<short>(textLabel->getWidth() + 20));
+
+ if (getParent())
+ {
+ setLocationRelativeTo(getParent());
+ getParent()->moveToTop(this);
+ }
+ setVisible(true);
+ requestModalFocus();
+ mTextField->requestFocus();
+
+ instances++;
+}
+
+TextDialog::~TextDialog()
+{
+ instances--;
+}
+
+void TextDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "CANCEL")
+ setActionEventId("~" + getActionEventId());
+
+ distributeActionEvent();
+ close();
+}
+
+const std::string &TextDialog::getText() const
+{
+ return mTextField->getText();
+}
+
+void TextDialog::setText(std::string text)
+{
+ if (mTextField)
+ mTextField->setText(text);
+}
+
+void TextDialog::close()
+{
+ keyboard.setEnabled(mEnabledKeyboard);
+ scheduleDelete();
+} \ No newline at end of file
diff --git a/src/gui/textdialog.h b/src/gui/textdialog.h
new file mode 100644
index 000000000..9c7e8d312
--- /dev/null
+++ b/src/gui/textdialog.h
@@ -0,0 +1,74 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_GUILD_DIALOG_H
+#define GUI_GUILD_DIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class TextField;
+
+/**
+* An option dialog.
+ *
+ * \ingroup GUI
+ */
+class TextDialog : public Window, public gcn::ActionListener
+{
+public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ TextDialog(const std::string &title, const std::string &msg,
+ Window *parent = 0);
+
+ ~TextDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Get the text in the textfield
+ */
+ const std::string &getText() const;
+
+ void setText(std::string text);
+
+ static bool isActive()
+ { return instances; }
+
+ void close();
+
+private:
+ static int instances;
+
+ TextField *mTextField;
+ gcn::Button *mOkButton;
+ bool mEnabledKeyboard;
+};
+
+#endif
diff --git a/src/gui/textpopup.cpp b/src/gui/textpopup.cpp
new file mode 100644
index 000000000..270b0f759
--- /dev/null
+++ b/src/gui/textpopup.cpp
@@ -0,0 +1,99 @@
+/*
+ * The Mana World
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/textpopup.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+
+#include "graphics.h"
+#include "units.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+#include <guichan/widgets/label.hpp>
+
+TextPopup::TextPopup():
+ Popup("TextPopup")
+{
+ const int fontHeight = getFont()->getHeight();
+
+ mText1 = new gcn::Label;
+ mText1->setPosition(getPadding(), getPadding());
+
+ mText2 = new gcn::Label;
+ mText2->setPosition(getPadding(), fontHeight + 2 * getPadding());
+
+ add(mText1);
+ add(mText2);
+ addMouseListener(this);
+}
+
+TextPopup::~TextPopup()
+{
+}
+
+void TextPopup::show(int x, int y, const std::string &str1,
+ const std::string &str2)
+{
+ mText1->setCaption(str1);
+ mText1->adjustSize();
+ mText2->setCaption(str2);
+ mText2->adjustSize();
+
+ int minWidth = mText1->getWidth();
+ if (mText2->getWidth() > minWidth)
+ minWidth = mText2->getWidth();
+
+ minWidth += 4 * getPadding();
+ setWidth(minWidth);
+
+ if (!str2.empty())
+ setHeight((2 * getPadding() + mText1->getFont()->getHeight()) * 2);
+ else
+ setHeight(2 * getPadding() + mText1->getFont()->getHeight());
+
+ const int distance = 20;
+
+ int posX = std::max(0, x - getWidth() / 2);
+ int posY = y + distance;
+
+ if (posX + getWidth() > graphics->getWidth())
+ posX = graphics->getWidth() - getWidth();
+ if (posY + getHeight() > graphics->getHeight())
+ posY = y - getHeight() - distance;
+
+ setPosition(posX, posY);
+ setVisible(true);
+ requestMoveToTop();
+}
+
+void TextPopup::mouseMoved(gcn::MouseEvent &event)
+{
+ Popup::mouseMoved(event);
+
+ // When the mouse moved on top of the popup, hide it
+ setVisible(false);
+}
diff --git a/src/gui/textpopup.h b/src/gui/textpopup.h
new file mode 100644
index 000000000..3b4158f6e
--- /dev/null
+++ b/src/gui/textpopup.h
@@ -0,0 +1,68 @@
+/*
+ * The Mana World
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TEXTPOPUP_H
+#define TEXTPOPUP_H
+
+#include "gui/widgets/popup.h"
+
+#include <guichan/mouselistener.hpp>
+
+class TextBox;
+
+/**
+ * A popup that displays information about an item.
+ */
+class TextPopup : public Popup
+{
+ public:
+ /**
+ * Constructor. Initializes the item popup.
+ */
+ TextPopup();
+
+ /**
+ * Destructor. Cleans up the item popup on deletion.
+ */
+ ~TextPopup();
+
+ /**
+ * Sets the text to be displayed.
+ */
+ void show(int x, int y, const std::string &str1)
+ { show(x, y, str1, static_cast<const char*>("")); };
+
+ /**
+ * Sets the text to be displayed.
+ */
+ void show(int x, int y, const std::string &str1,
+ const std::string &str2);
+
+ void mouseMoved(gcn::MouseEvent &mouseEvent);
+
+ private:
+ gcn::Label *mText1;
+ gcn::Label *mText2;
+};
+
+#endif // TEXTPOPUP_H
diff --git a/src/gui/theme.cpp b/src/gui/theme.cpp
new file mode 100644
index 000000000..b45cef5b0
--- /dev/null
+++ b/src/gui/theme.cpp
@@ -0,0 +1,791 @@
+/*
+ * Gui Skinning
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2009 Aethyra Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/theme.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "log.h"
+
+#include "resources/dye.h"
+#include "resources/image.h"
+#include "resources/imageset.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/dtor.h"
+#include "utils/stringutils.h"
+#include "utils/xml.h"
+
+#include <physfs.h>
+
+#include <algorithm>
+#include <physfs.h>
+
+static std::string defaultThemePath;
+
+std::string Theme::mThemePath;
+std::string Theme::mThemeName;
+Theme *Theme::mInstance = 0;
+
+// Set the theme path...
+static void initDefaultThemePath()
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+ defaultThemePath = branding.getStringValue("guiThemePath");
+
+ logger->log("defaultThemePath: " + defaultThemePath);
+ if (!defaultThemePath.empty() && resman->isDirectory(defaultThemePath))
+ return;
+ else
+ defaultThemePath = "themes/";
+}
+
+Skin::Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown,
+ const std::string &filePath,
+ const std::string &name):
+ instances(0),
+ mFilePath(filePath),
+ mName(name),
+ mBorder(skin),
+ mCloseImage(close),
+ mStickyImageUp(stickyUp),
+ mStickyImageDown(stickyDown)
+{}
+
+Skin::~Skin()
+{
+ // Clean up static resources
+ for (int i = 0; i < 9; i++)
+ {
+ delete mBorder.grid[i];
+ mBorder.grid[i] = 0;
+ }
+
+ if (mCloseImage)
+ {
+ mCloseImage->decRef();
+ mCloseImage = 0;
+ }
+ delete mStickyImageUp;
+ mStickyImageUp = 0;
+ delete mStickyImageDown;
+ mStickyImageDown = 0;
+}
+
+void Skin::updateAlpha(float minimumOpacityAllowed)
+{
+ const float alpha = static_cast<float>(
+ std::max(static_cast<double>(minimumOpacityAllowed),
+ static_cast<double>(Client::getGuiAlpha())));
+
+ for_each(mBorder.grid, mBorder.grid + 9,
+ std::bind2nd(std::mem_fun(&Image::setAlpha), alpha));
+
+ if (mCloseImage)
+ mCloseImage->setAlpha(alpha);
+ if (mStickyImageUp)
+ mStickyImageUp->setAlpha(alpha);
+ if (mStickyImageDown)
+ mStickyImageDown->setAlpha(alpha);
+}
+
+int Skin::getMinWidth() const
+{
+ if (!mBorder.grid[ImageRect::UPPER_LEFT]
+ || !mBorder.grid[ImageRect::UPPER_RIGHT])
+ {
+ return 1;
+ }
+
+ return mBorder.grid[ImageRect::UPPER_LEFT]->getWidth() +
+ mBorder.grid[ImageRect::UPPER_RIGHT]->getWidth();
+}
+
+int Skin::getMinHeight() const
+{
+ if (!mBorder.grid[ImageRect::UPPER_LEFT]
+ || !mBorder.grid[ImageRect::LOWER_LEFT])
+ {
+ return 1;
+ }
+
+ return mBorder.grid[ImageRect::UPPER_LEFT]->getHeight() +
+ mBorder.grid[ImageRect::LOWER_LEFT]->getHeight();
+}
+
+Theme::Theme():
+ Palette(THEME_COLORS_END),
+ mMinimumOpacity(-1.0f),
+ mProgressColors(ProgressColors(THEME_PROG_END))
+{
+ initDefaultThemePath();
+
+ config.addListener("guialpha", this);
+ loadColors();
+
+ mColors[HIGHLIGHT].ch = 'H';
+ mColors[CHAT].ch = 'C';
+ mColors[GM].ch = 'G';
+ mColors[PLAYER].ch = 'Y';
+ mColors[WHISPER].ch = 'W';
+ mColors[WHISPER_OFFLINE].ch = 'w';
+ mColors[IS].ch = 'I';
+ mColors[PARTY_CHAT_TAB].ch = 'P';
+ mColors[GUILD_CHAT_TAB].ch = 'U';
+ mColors[SERVER].ch = 'S';
+ mColors[LOGGER].ch = 'L';
+ mColors[HYPERLINK].ch = '<';
+}
+
+Theme::~Theme()
+{
+ delete_all(mSkins);
+ config.removeListener("guialpha", this);
+ delete_all(mProgressColors);
+}
+
+Theme *Theme::instance()
+{
+ if (!mInstance)
+ mInstance = new Theme;
+
+ return mInstance;
+}
+
+void Theme::deleteInstance()
+{
+ delete mInstance;
+ mInstance = 0;
+}
+
+gcn::Color Theme::getProgressColor(int type, float progress)
+{
+ int color[3] = {0, 0, 0};
+
+ if (mInstance)
+ {
+ DyePalette *dye = mInstance->mProgressColors[type];
+
+ if (dye)
+ dye->getColor(progress, color);
+ else
+ logger->log("color not found: " + toString(type));
+ }
+
+ return gcn::Color(color[0], color[1], color[2]);
+}
+
+Skin *Theme::load(const std::string &filename, const std::string &defaultPath)
+{
+ // Check if this skin was already loaded
+
+ SkinIterator skinIterator = mSkins.find(filename);
+ if (mSkins.end() != skinIterator)
+ {
+ if (skinIterator->second)
+ skinIterator->second->instances++;
+ return skinIterator->second;
+ }
+
+ Skin *skin = readSkin(filename);
+
+ if (!skin)
+ {
+ // Try falling back on the defaultPath if this makes sense
+ if (filename != defaultPath)
+ {
+ logger->log("Error loading skin '%s', falling back on default.",
+ filename.c_str());
+
+ skin = readSkin(defaultPath);
+ }
+
+ if (!skin)
+ {
+ logger->log(strprintf("Error: Loading default skin '%s' failed. "
+ "Make sure the skin file is valid.",
+ defaultPath.c_str()));
+ }
+ }
+
+ // Add the skin to the loaded skins
+ mSkins[filename] = skin;
+
+ return skin;
+}
+
+void Theme::setMinimumOpacity(float minimumOpacity)
+{
+ if (minimumOpacity > 1.0f)
+ return;
+
+ mMinimumOpacity = minimumOpacity;
+ updateAlpha();
+}
+
+void Theme::updateAlpha()
+{
+ for (SkinIterator iter = mSkins.begin(); iter != mSkins.end(); ++iter)
+ {
+ if (iter->second)
+ iter->second->updateAlpha(mMinimumOpacity);
+ }
+}
+
+void Theme::optionChanged(const std::string &)
+{
+ updateAlpha();
+}
+
+Skin *Theme::readSkin(const std::string &filename)
+{
+ if (filename.empty())
+ return 0;
+
+// std::string filename = filename0;
+// ResourceManager *resman = ResourceManager::getInstance();
+ logger->log("Loading skin '%s'.", filename.c_str());
+// filename = resman->mapPathToSkin(filename0);
+
+ XML::Document doc(resolveThemePath(filename));
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "skinset"))
+ return 0;
+
+ const std::string skinSetImage = XML::getProperty(rootNode, "image", "");
+
+ if (skinSetImage.empty())
+ {
+ logger->log1("Theme::readSkin(): Skinset does not define an image!");
+ return 0;
+ }
+
+ logger->log("Theme::load(): <skinset> defines '%s' as a skin image.",
+ skinSetImage.c_str());
+
+ Image *dBorders = Theme::getImageFromTheme(skinSetImage);
+ ImageRect border;
+ memset(&border, 0, sizeof(ImageRect));
+
+ // iterate <widget>'s
+ for_each_xml_child_node(widgetNode, rootNode)
+ {
+ if (!xmlStrEqual(widgetNode->name, BAD_CAST "widget"))
+ continue;
+
+ const std::string widgetType =
+ XML::getProperty(widgetNode, "type", "unknown");
+ if (widgetType == "Window")
+ {
+ // Iterate through <part>'s
+ // LEEOR / TODO:
+ // We need to make provisions to load in a CloseButton image. For
+ // now it can just be hard-coded.
+ for_each_xml_child_node(partNode, widgetNode)
+ {
+ if (!xmlStrEqual(partNode->name, BAD_CAST "part"))
+ continue;
+
+ const std::string partType =
+ XML::getProperty(partNode, "type", "unknown");
+ // TOP ROW
+ const int xPos = XML::getProperty(partNode, "xpos", 0);
+ const int yPos = XML::getProperty(partNode, "ypos", 0);
+ const int width = XML::getProperty(partNode, "width", 1);
+ const int height = XML::getProperty(partNode, "height", 1);
+
+ if (partType == "top-left-corner")
+ {
+ if (dBorders)
+ {
+ border.grid[0] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[0] = 0;
+ }
+ }
+ else if (partType == "top-edge")
+ {
+ if (dBorders)
+ {
+ border.grid[1] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[1] = 0;
+ }
+ }
+ else if (partType == "top-right-corner")
+ {
+ if (dBorders)
+ {
+ border.grid[2] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[2] = 0;
+ }
+ }
+
+ // MIDDLE ROW
+ else if (partType == "left-edge")
+ {
+ if (dBorders)
+ {
+ border.grid[3] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[3] = 0;
+ }
+ }
+ else if (partType == "bg-quad")
+ {
+ if (dBorders)
+ {
+ border.grid[4] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[4] = 0;
+ }
+ }
+ else if (partType == "right-edge")
+ {
+ if (dBorders)
+ {
+ border.grid[5] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[5] = 0;
+ }
+ }
+
+ // BOTTOM ROW
+ else if (partType == "bottom-left-corner")
+ {
+ if (dBorders)
+ {
+ border.grid[6] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[6] = 0;
+ }
+ }
+ else if (partType == "bottom-edge")
+ {
+ if (dBorders)
+ {
+ border.grid[7] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[7] = 0;
+ }
+ }
+ else if (partType == "bottom-right-corner")
+ {
+ if (dBorders)
+ {
+ border.grid[8] = dBorders->getSubImage(
+ xPos, yPos, width, height);
+ }
+ else
+ {
+ border.grid[8] = 0;
+ }
+ }
+
+ else
+ {
+ logger->log("Theme::readSkin(): Unknown part type '%s'",
+ partType.c_str());
+ }
+ }
+ }
+ else
+ {
+ logger->log("Theme::readSkin(): Unknown widget type '%s'",
+ widgetType.c_str());
+ }
+ }
+
+ if (dBorders)
+ dBorders->decRef();
+
+ logger->log1("Finished loading skin.");
+
+ // Hard-coded for now until we update the above code
+ // to look for window buttons
+ Image *closeImage = Theme::getImageFromTheme("close_button.png");
+ Image *sticky = Theme::getImageFromTheme("sticky_button.png");
+ Image *stickyImageUp = 0;
+ Image *stickyImageDown = 0;
+ if (sticky)
+ {
+ stickyImageUp = sticky->getSubImage(0, 0, 15, 15);
+ stickyImageDown = sticky->getSubImage(15, 0, 15, 15);
+ sticky->decRef();
+ }
+
+ Skin *skin = new Skin(border, closeImage, stickyImageUp, stickyImageDown,
+ filename);
+ skin->updateAlpha(mMinimumOpacity);
+ return skin;
+}
+
+bool Theme::tryThemePath(std::string themeName)
+{
+ if (!themeName.empty())
+ {
+ std::string path = defaultThemePath + themeName;
+ if (PHYSFS_exists(path.c_str()))
+ {
+ mThemePath = path;
+ mThemeName = themeName;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void Theme::fillSkinsList(std::vector<std::string> &list)
+{
+ char **skins = PHYSFS_enumerateFiles(
+ branding.getStringValue("guiThemePath").c_str());
+
+ for (char **i = skins; *i != 0; i++)
+ {
+ if (PHYSFS_isDirectory((
+ branding.getStringValue("guiThemePath") + *i).c_str()))
+ {
+ list.push_back(*i);
+ }
+ }
+
+ PHYSFS_freeList(skins);
+}
+
+void Theme::fillFontsList(std::vector<std::string> &list)
+{
+ PHYSFS_permitSymbolicLinks(1);
+ char **fonts = PHYSFS_enumerateFiles(
+ branding.getStringValue("fontsPath").c_str());
+
+ for (char **i = fonts; *i != 0; i++)
+ {
+ if (!PHYSFS_isDirectory((
+ branding.getStringValue("fontsPath") + *i).c_str()))
+ {
+ list.push_back(*i);
+ }
+ }
+
+ PHYSFS_freeList(fonts);
+ PHYSFS_permitSymbolicLinks(0);
+}
+
+void Theme::selectSkin()
+{
+ prepareThemePath();
+}
+
+void Theme::prepareThemePath()
+{
+ initDefaultThemePath();
+
+ mThemePath = "";
+ mThemeName = "";
+
+ // Try theme from settings
+ if (tryThemePath(config.getValue("selectedSkin", "")))
+ return;
+
+ // Try theme from settings
+ if (tryThemePath(config.getValue("theme", "")))
+ return;
+
+ // Try theme from branding
+ if (tryThemePath(branding.getValue("theme", "")))
+ return;
+
+ if (mThemePath.empty())
+ mThemePath = "graphics/gui";
+
+ instance()->loadColors(mThemePath);
+
+ logger->log("Selected Theme: " + mThemePath);
+}
+
+std::string Theme::resolveThemePath(const std::string &path)
+{
+ // Need to strip off any dye info for the existence tests
+ int pos = static_cast<int>(path.find('|'));
+ std::string file;
+ if (pos > 0)
+ file = path.substr(0, pos);
+ else
+ file = path;
+
+ // Might be a valid path already
+ if (PHYSFS_exists(file.c_str()))
+ return path;
+
+ // Try the theme
+ file = getThemePath() + "/" + file;
+ if (PHYSFS_exists(file.c_str()))
+ return getThemePath() + "/" + path;
+
+ // Backup
+ return branding.getStringValue("guiPath") + path;
+}
+
+Image *Theme::getImageFromTheme(const std::string &path)
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+ return resman->getImage(resolveThemePath(path));
+}
+
+ImageSet *Theme::getImageSetFromTheme(const std::string &path,
+ int w, int h)
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+ return resman->getImageSet(resolveThemePath(path), w, h);
+}
+
+static int readColorType(const std::string &type)
+{
+ static std::string colors[] =
+ {
+ "TEXT",
+ "SHADOW",
+ "OUTLINE",
+ "PROGRESS_BAR",
+ "BUTTON",
+ "BUTTON_DISABLED",
+ "TAB",
+ "PARTY_CHAT_TAB",
+ "PARTY_SOCIAL_TAB",
+ "GUILD_CHAT_TAB",
+ "GUILD_SOCIAL_TAB",
+ "BACKGROUND",
+ "HIGHLIGHT",
+ "TAB_FLASH",
+ "TAB_PLAYER_FLASH",
+ "SHOP_WARNING",
+ "ITEM_EQUIPPED",
+ "CHAT",
+ "GM",
+ "PLAYER",
+ "WHISPER",
+ "WHISPER_OFFLINE",
+ "IS",
+ "SERVER",
+ "LOGGER",
+ "HYPERLINK",
+ "UNKNOWN_ITEM",
+ "GENERIC",
+ "HEAD",
+ "USABLE",
+ "TORSO",
+ "ONEHAND",
+ "LEGS",
+ "FEET",
+ "TWOHAND",
+ "SHIELD",
+ "RING",
+ "NECKLACE",
+ "ARMS",
+ "AMMO",
+ "SERVER_VERSION_NOT_SUPPORTED",
+ "WARNING"
+ };
+
+ if (type.empty())
+ return -1;
+
+ for (int i = 0; i < Theme::THEME_COLORS_END; i++)
+ {
+ if (compareStrI(type, colors[i]) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static gcn::Color readColor(const std::string &description)
+{
+ int size = static_cast<int>(description.length());
+ if (size < 7 || description[0] != '#')
+ {
+ logger->log("Error, invalid theme color palette: %s",
+ description.c_str());
+ return Palette::BLACK;
+ }
+
+ int v = 0;
+ for (int i = 1; i < 7; ++i)
+ {
+ char c = description[i];
+ int n;
+
+ if ('0' <= c && c <= '9')
+ {
+ n = c - '0';
+ }
+ else if ('A' <= c && c <= 'F')
+ {
+ n = c - 'A' + 10;
+ }
+ else if ('a' <= c && c <= 'f')
+ {
+ n = c - 'a' + 10;
+ }
+ else
+ {
+ logger->log("Error, invalid theme color palette: %s",
+ description.c_str());
+ return Palette::BLACK;
+ }
+
+ v = (v << 4) | n;
+ }
+
+ return gcn::Color(v);
+}
+
+static Palette::GradientType readColorGradient(const std::string &grad)
+{
+ static std::string grads[] =
+ {
+ "STATIC",
+ "PULSE",
+ "SPECTRUM",
+ "RAINBOW"
+ };
+
+ if (grad.empty())
+ return Palette::STATIC;
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (compareStrI(grad, grads[i]))
+ return static_cast<Palette::GradientType>(i);
+ }
+
+ return Palette::STATIC;
+}
+
+static int readProgressType(const std::string &type)
+{
+ static std::string colors[] =
+ {
+ "DEFAULT",
+ "HP",
+ "MP",
+ "NO_MP",
+ "EXP",
+ "INVY_SLOTS",
+ "WEIGHT",
+ "JOB"
+ };
+
+ if (type.empty())
+ return -1;
+
+ for (int i = 0; i < Theme::THEME_PROG_END; i++)
+ {
+ if (compareStrI(type, colors[i]) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+void Theme::loadColors(std::string file)
+{
+// if (file == mThemePath)
+// return; // No need to reload
+
+ if (file == "")
+ file = "colors.xml";
+ else
+ file += "/colors.xml";
+
+ XML::Document doc(resolveThemePath(file));
+ xmlNodePtr root = doc.rootNode();
+
+ if (!root || !xmlStrEqual(root->name, BAD_CAST "colors"))
+ {
+ logger->log("Error loading colors file: %s", file.c_str());
+ return;
+ }
+
+ logger->log("Loading colors file: %s", file.c_str());
+
+ int type;
+ std::string temp;
+ gcn::Color color;
+ GradientType grad;
+
+ for_each_xml_child_node(node, root)
+ {
+ if (xmlStrEqual(node->name, BAD_CAST "color"))
+ {
+ type = readColorType(XML::getProperty(node, "id", ""));
+ if (type < 0) // invalid or no type given
+ continue;
+
+ temp = XML::getProperty(node, "color", "");
+ if (temp.empty()) // no color set, so move on
+ continue;
+
+ color = readColor(temp);
+ grad = readColorGradient(XML::getProperty(node, "effect", ""));
+
+ mColors[type].set(type, color, grad, 10);
+ }
+ else if (xmlStrEqual(node->name, BAD_CAST "progressbar"))
+ {
+ type = readProgressType(XML::getProperty(node, "id", ""));
+ if (type < 0) // invalid or no type given
+ continue;
+
+ mProgressColors[type] = new DyePalette(XML::getProperty(node,
+ "color", ""));
+ }
+ }
+}
diff --git a/src/gui/theme.h b/src/gui/theme.h
new file mode 100644
index 000000000..bb7a66f85
--- /dev/null
+++ b/src/gui/theme.h
@@ -0,0 +1,273 @@
+/*
+ * Gui Skinning
+ * Copyright (C) 2008 The Legend of Mazzeroth Development Team
+ * Copyright (C) 2009 Aethyra Development Team
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SKIN_H
+#define SKIN_H
+
+#include "configlistener.h"
+#include "graphics.h"
+
+#include "gui/palette.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+class DyePalette;
+class Image;
+class ImageSet;
+class ProgressBar;
+
+class Skin
+{
+ public:
+ Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown,
+ const std::string &filePath,
+ const std::string &name = "");
+
+ ~Skin();
+
+ /**
+ * Returns the skin's name. Useful for giving a human friendly skin
+ * name if a dialog for skin selection for a specific window type is
+ * done.
+ */
+ const std::string &getName() const
+ { return mName; }
+
+ /**
+ * Returns the skin's xml file path.
+ */
+ const std::string &getFilePath() const
+ { return mFilePath; }
+
+ /**
+ * Returns the background skin.
+ */
+ const ImageRect &getBorder() const
+ { return mBorder; }
+
+ /**
+ * Returns the image used by a close button for this skin.
+ */
+ Image *getCloseImage() const
+ { return mCloseImage; }
+
+ /**
+ * Returns the image used by a sticky button for this skin.
+ */
+ Image *getStickyImage(bool state) const
+ { return state ? mStickyImageDown : mStickyImageUp; }
+
+ /**
+ * Returns the minimum width which can be used with this skin.
+ */
+ int getMinWidth() const;
+
+ /**
+ * Returns the minimum height which can be used with this skin.
+ */
+ int getMinHeight() const;
+
+ /**
+ * Updates the alpha value of the skin
+ */
+ void updateAlpha(float minimumOpacityAllowed = 0.0f);
+
+ int instances;
+
+ private:
+ std::string mFilePath; /**< File name path for the skin */
+ std::string mName; /**< Name of the skin to use */
+ ImageRect mBorder; /**< The window border and background */
+ Image *mCloseImage; /**< Close Button Image */
+ Image *mStickyImageUp; /**< Sticky Button Image */
+ Image *mStickyImageDown; /**< Sticky Button Image */
+};
+
+class Theme : public Palette, public ConfigListener
+{
+ public:
+ static Theme *instance();
+
+ static void deleteInstance();
+
+ static void prepareThemePath();
+
+ static void selectSkin();
+
+ static std::string getThemePath()
+ { return mThemePath; }
+
+ static std::string getThemeName()
+ { return mThemeName; }
+
+ static void fillSkinsList(std::vector<std::string> &list);
+
+ static void fillFontsList(std::vector<std::string> &list);
+
+ /**
+ * Returns the patch to the given gui resource relative to the theme
+ * or, if it isn't in the theme, relative to 'graphics/gui'.
+ */
+ static std::string resolveThemePath(const std::string &path);
+
+ static Image *getImageFromTheme(const std::string &path);
+
+ static ImageSet *getImageSetFromTheme(const std::string &path,
+ int w, int h);
+
+ enum ThemePalette
+ {
+ TEXT = 0,
+ SHADOW,
+ OUTLINE,
+ PROGRESS_BAR,
+ BUTTON,
+ BUTTON_DISABLED,
+ TAB,
+ PARTY_CHAT_TAB,
+ PARTY_SOCIAL_TAB,
+ GUILD_CHAT_TAB,
+ GUILD_SOCIAL_TAB,
+ BACKGROUND,
+ HIGHLIGHT,
+ TAB_FLASH,
+ TAB_PLAYER_FLASH,
+ SHOP_WARNING,
+ ITEM_EQUIPPED,
+ CHAT,
+ GM,
+ PLAYER,
+ WHISPER,
+ WHISPER_OFFLINE,
+ IS,
+ SERVER,
+ LOGGER,
+ HYPERLINK,
+ UNKNOWN_ITEM,
+ GENERIC,
+ HEAD,
+ USABLE,
+ TORSO,
+ ONEHAND,
+ LEGS,
+ FEET,
+ TWOHAND,
+ SHIELD,
+ RING,
+ NECKLACE,
+ ARMS,
+ AMMO,
+ SERVER_VERSION_NOT_SUPPORTED,
+ WARNING,
+ THEME_COLORS_END
+ };
+
+ enum ProgressPalette
+ {
+ PROG_DEFAULT = 0,
+ PROG_HP,
+ PROG_MP,
+ PROG_NO_MP,
+ PROG_EXP,
+ PROG_INVY_SLOTS,
+ PROG_WEIGHT,
+ PROG_JOB,
+ THEME_PROG_END
+ };
+
+ /**
+ * Gets the color associated with the type. Sets the alpha channel
+ * before returning.
+ *
+ * @param type the color type requested
+ * @param alpha alpha channel to use
+ *
+ * @return the requested color
+ */
+ inline static const gcn::Color &getThemeColor(int type,
+ int alpha = 255)
+ { return mInstance->getColor(type, alpha); }
+
+ const static gcn::Color &getThemeColor(char c, bool &valid)
+ { return mInstance->getColor(c, valid); }
+
+ static gcn::Color getProgressColor(int type, float progress);
+
+ /**
+ * Loads a skin.
+ */
+ Skin *load(const std::string &filename,
+ const std::string &defaultPath = getThemePath());
+
+ /**
+ * Updates the alpha values of all of the skins.
+ */
+ void updateAlpha();
+
+ /**
+ * Get the minimum opacity allowed to skins.
+ */
+ float getMinimumOpacity()
+ { return mMinimumOpacity; }
+
+ /**
+ * Set the minimum opacity allowed to skins.
+ * Set a negative value to free the minimum allowed.
+ */
+ void setMinimumOpacity(float minimumOpacity);
+
+ void optionChanged(const std::string &);
+
+ private:
+ Theme();
+ ~Theme();
+
+ Skin *readSkin(const std::string &filename0);
+
+ // Map containing all window skins
+ typedef std::map<std::string, Skin*> Skins;
+ typedef Skins::iterator SkinIterator;
+
+ Skins mSkins;
+
+ static std::string mThemePath;
+ static std::string mThemeName;
+ static Theme *mInstance;
+
+ static bool tryThemePath(std::string themePath);
+
+ void loadColors(std::string file = "");
+
+ /**
+ * Tells if the current skins opacity
+ * should not get less than the given value
+ */
+ float mMinimumOpacity;
+
+ typedef std::vector<DyePalette*> ProgressColors;
+ ProgressColors mProgressColors;
+};
+
+#endif
diff --git a/src/gui/trade.cpp b/src/gui/trade.cpp
new file mode 100644
index 000000000..ee67dc6da
--- /dev/null
+++ b/src/gui/trade.cpp
@@ -0,0 +1,420 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/trade.h"
+
+#include "inventory.h"
+#include "item.h"
+#include "localplayer.h"
+#include "playerinfo.h"
+#include "units.h"
+
+#include "gui/inventorywindow.h"
+#include "gui/itemamount.h"
+#include "gui/setup.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/itemcontainer.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/layout.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+#include "net/tradehandler.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+
+#include <sstream>
+
+#define CAPTION_PROPOSE _("Propose trade")
+#define CAPTION_CONFIRMED _("Confirmed. Waiting...")
+#define CAPTION_ACCEPT _("Agree trade")
+#define CAPTION_ACCEPTED _("Agreed. Waiting...")
+
+TradeWindow::TradeWindow():
+ Window(_("Trade: You")),
+ mMyInventory(new Inventory(Inventory::TRADE)),
+ mPartnerInventory(new Inventory(Inventory::TRADE)),
+ mStatus(PROPOSING),
+ mAutoAddItem(0),
+ mAutoAddToNick(""),
+ mGotMoney(0),
+ mAutoMoney(0)
+{
+ logger->log1("TradeWindow::TradeWindow nick");
+
+ setWindowName("Trade");
+ setResizable(true);
+ setCloseButton(true);
+ setDefaultSize(386, 180, ImageRect::CENTER);
+ setMinWidth(386);
+ setMinHeight(180);
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ std::string longestName = getFont()->getWidth(_("OK")) >
+ getFont()->getWidth(_("Trade")) ?
+ _("OK") : _("Trade");
+
+ mAddButton = new Button(_("Add"), "add", this);
+ mOkButton = new Button("", "", this); // Will be filled in later
+
+ int width = std::max(mOkButton->getFont()->getWidth(CAPTION_PROPOSE),
+ mOkButton->getFont()->getWidth(CAPTION_CONFIRMED));
+ width = std::max(width, mOkButton->getFont()->getWidth(CAPTION_ACCEPT));
+ width = std::max(width, mOkButton->getFont()->getWidth(CAPTION_ACCEPTED));
+
+ mOkButton->setWidth(8 + width);
+
+ mMyItemContainer = new ItemContainer(mMyInventory.get());
+ mMyItemContainer->addSelectionListener(this);
+
+ ScrollArea *myScroll = new ScrollArea(mMyItemContainer);
+ myScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mPartnerItemContainer = new ItemContainer(mPartnerInventory.get());
+ mPartnerItemContainer->addSelectionListener(this);
+
+ ScrollArea *partnerScroll = new ScrollArea(mPartnerItemContainer);
+ partnerScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mMoneyLabel = new Label(strprintf(_("You get %s"), ""));
+ gcn::Label *mMoneyLabel2 = new Label(_("You give:"));
+
+ mMoneyField = new TextField;
+ mMoneyField->setWidth(40);
+ mMoneyChangeButton = new Button(_("Change"), "money", this);
+
+ place(1, 0, mMoneyLabel);
+ place(0, 1, myScroll).setPadding(3);
+ place(1, 1, partnerScroll).setPadding(3);
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+ place(0, 0, mMoneyLabel2);
+ place(1, 0, mMoneyField, 2);
+ place(3, 0, mMoneyChangeButton).setHAlign(LayoutCell::LEFT);
+ place = getPlacer(0, 2);
+ place(0, 0, mAddButton);
+ place(1, 0, mOkButton);
+ Layout &layout = getLayout();
+ layout.extend(0, 2, 2, 1);
+ layout.setRowHeight(1, Layout::AUTO_SET);
+ layout.setRowHeight(2, 0);
+ layout.setColWidth(0, Layout::AUTO_SET);
+ layout.setColWidth(1, Layout::AUTO_SET);
+
+ loadWindowState();
+
+ reset();
+}
+
+TradeWindow::~TradeWindow()
+{
+}
+
+void TradeWindow::setMoney(int amount)
+{
+ if (amount < mGotMoney)
+ mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::WARNING));
+ mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT));
+
+ mGotMoney = amount;
+ mMoneyLabel->setCaption(strprintf(_("You get %s"),
+ Units::formatCurrency(amount).c_str()));
+ mMoneyLabel->adjustSize();
+}
+
+void TradeWindow::addItem(int id, bool own, int quantity, int refine)
+{
+ if (own)
+ mMyInventory->addItem(id, quantity, refine);
+ else
+ mPartnerInventory->addItem(id, quantity, refine);
+}
+
+void TradeWindow::addItem(int id, bool own, int quantity,
+ int refine, bool equipment)
+{
+ if (own)
+ mMyInventory->addItem(id, quantity, refine, equipment);
+ else
+ mPartnerInventory->addItem(id, quantity, refine, equipment);
+}
+
+void TradeWindow::changeQuantity(int index, bool own, int quantity)
+{
+ if (own)
+ {
+ if (mMyInventory->getItem(index))
+ mMyInventory->getItem(index)->setQuantity(quantity);
+ }
+ else
+ {
+ if (mPartnerInventory->getItem(index))
+ mPartnerInventory->getItem(index)->setQuantity(quantity);
+ }
+}
+
+void TradeWindow::increaseQuantity(int index, bool own, int quantity)
+{
+ if (own)
+ {
+ if (mMyInventory->getItem(index))
+ mMyInventory->getItem(index)->increaseQuantity(quantity);
+ }
+ else
+ {
+ if (mPartnerInventory->getItem(index))
+ mPartnerInventory->getItem(index)->increaseQuantity(quantity);
+ }
+}
+
+void TradeWindow::reset()
+{
+ mMyInventory->clear();
+ mPartnerInventory->clear();
+ mOkOther = false;
+ mOkMe = false;
+ setMoney(0);
+ mMoneyField->setEnabled(true);
+ mMoneyField->setText("");
+ mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT));
+ mAddButton->setEnabled(true);
+ mMoneyChangeButton->setEnabled(true);
+ mGotMoney = 0;
+ setStatus(PREPARING);
+}
+
+void TradeWindow::receivedOk(bool own)
+{
+ if (own)
+ mOkMe = true;
+ else
+ mOkOther = true;
+
+ if (mOkMe && mOkOther)
+ {
+ //mOkMe = false;
+ //mOkOther = false;
+ setStatus(ACCEPTING);
+ }
+}
+
+void TradeWindow::tradeItem(Item *item, int quantity)
+{
+ Net::getTradeHandler()->addItem(item, quantity);
+}
+
+void TradeWindow::valueChanged(const gcn::SelectionEvent &event)
+{
+ if (!mMyItemContainer || !mPartnerItemContainer)
+ return;
+
+ /* If an item is selected in one container, make sure no item is selected
+ * in the other container.
+ */
+ if (event.getSource() == mMyItemContainer &&
+ mMyItemContainer->getSelectedItem())
+ {
+ mPartnerItemContainer->selectNone();
+ }
+ else if (mPartnerItemContainer->getSelectedItem())
+ {
+ mMyItemContainer->selectNone();
+ }
+}
+
+void TradeWindow::setStatus(Status s)
+{
+ if (s == mStatus)
+ return;
+ mStatus = s;
+
+ switch (s)
+ {
+ case PREPARING:
+ mOkButton->setCaption(CAPTION_PROPOSE);
+ mOkButton->setActionEventId("ok");
+ break;
+ case PROPOSING:
+ mOkButton->setCaption(CAPTION_CONFIRMED);
+ mOkButton->setActionEventId("");
+ break;
+ case ACCEPTING:
+ mOkButton->setCaption(CAPTION_ACCEPT);
+ mOkButton->setActionEventId("trade");
+ break;
+ case ACCEPTED:
+ mOkButton->setCaption(CAPTION_ACCEPTED);
+ mOkButton->setActionEventId("");
+ break;
+ default:
+ break;
+ }
+
+ mOkButton->setEnabled((s != PROPOSING && s != ACCEPTED));
+}
+
+void TradeWindow::action(const gcn::ActionEvent &event)
+{
+ if (!inventoryWindow)
+ return;
+
+ Item *item = inventoryWindow->getSelectedItem();
+
+ if (event.getId() == "add")
+ {
+ if (mStatus != PREPARING)
+ return;
+
+ if (!inventoryWindow->isVisible())
+ {
+ inventoryWindow->setVisible(true);
+ return;
+ }
+
+ if (!item)
+ return;
+
+ if (mMyInventory->getFreeSlot() == -1)
+ return;
+
+ if (mMyInventory->contains(item))
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(_("Failed adding item. You can not "
+ "overlap one kind of item on the window."), BY_SERVER);
+ }
+ return;
+ }
+
+ // Choose amount of items to trade
+ ItemAmountWindow::showWindow(ItemAmountWindow::TradeAdd, this, item);
+
+ setStatus(PREPARING);
+ }
+ else if (event.getId() == "cancel")
+ {
+ setVisible(false);
+ reset();
+ PlayerInfo::setTrading(false);
+
+ Net::getTradeHandler()->cancel();
+ }
+ else if (event.getId() == "ok")
+ {
+ mMoneyField->setEnabled(false);
+ mAddButton->setEnabled(false);
+ mMoneyChangeButton->setEnabled(false);
+ receivedOk(true);
+ setStatus(PROPOSING);
+ Net::getTradeHandler()->confirm();
+ }
+ else if (event.getId() == "trade")
+ {
+ receivedOk(true);
+ setStatus(ACCEPTED);
+ Net::getTradeHandler()->finish();
+ }
+ else if (event.getId() == "money")
+ {
+ if (mStatus != PREPARING)
+ return;
+
+ int v = atoi(mMoneyField->getText().c_str());
+ int curMoney = PlayerInfo::getAttribute(MONEY);
+ if (v > curMoney)
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(_("You don't have enough money."),
+ BY_SERVER);
+ }
+ v = curMoney;
+ }
+ Net::getTradeHandler()->setMoney(v);
+ mMoneyField->setText(strprintf("%d", v));
+ }
+}
+
+void TradeWindow::close()
+{
+ Net::getTradeHandler()->cancel();
+ clear();
+}
+
+void TradeWindow::clear()
+{
+ mAutoAddItem = 0;
+ mAutoAddToNick = "";
+ mAutoMoney = 0;
+ mAutoAddAmount = 0;
+ mGotMoney = 0;
+ mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT));
+}
+
+void TradeWindow::addAutoItem(std::string nick, Item* item, int amount)
+{
+ mAutoAddToNick = nick;
+ mAutoAddItem = item;
+ mAutoAddAmount = amount;
+}
+
+void TradeWindow::addAutoMoney(std::string nick, int money)
+{
+ mAutoAddToNick = nick;
+ mAutoMoney = money;
+}
+
+void TradeWindow::initTrade(std::string nick)
+{
+ if (!player_node)
+ return;
+
+ if (!mAutoAddToNick.empty() && mAutoAddToNick == nick)
+ {
+ if (mAutoAddItem && mAutoAddItem->getQuantity())
+ {
+ Inventory *inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ Item *item = inv->findItem(mAutoAddItem->getId());
+ if (item)
+ tradeItem(item, mAutoAddItem->getQuantity());
+ }
+ }
+ if (mAutoMoney)
+ {
+ Net::getTradeHandler()->setMoney(mAutoMoney);
+ mMoneyField->setText(strprintf("%d", mAutoMoney));
+ }
+ }
+ clear();
+}
diff --git a/src/gui/trade.h b/src/gui/trade.h
new file mode 100644
index 000000000..1baa14fa8
--- /dev/null
+++ b/src/gui/trade.h
@@ -0,0 +1,170 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRADE_H
+#define TRADE_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#include <memory>
+
+class Inventory;
+class Item;
+class ItemContainer;
+class ScrollArea;
+
+/**
+ * Trade dialog.
+ *
+ * \ingroup Interface
+ */
+class TradeWindow : public Window, gcn::ActionListener, gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ TradeWindow();
+
+ /**
+ * Destructor.
+ */
+ ~TradeWindow();
+
+ /**
+ * Displays expected money in the trade window.
+ */
+ void setMoney(int quantity);
+
+ /**
+ * Add an item to the trade window.
+ */
+ void addItem(int id, bool own, int quantity, int refine);
+
+ /**
+ * Reset both item containers
+ */
+ void reset();
+
+ /**
+ * Add an item to the trade window.
+ */
+ void addItem(int id, bool own, int quantity,
+ int refine, bool equipment);
+
+ /**
+ * Change quantity of an item.
+ */
+ void changeQuantity(int index, bool own, int quantity);
+
+ /**
+ * Increase quantity of an item.
+ */
+ void increaseQuantity(int index, bool own, int quantity);
+
+ /**
+ * Player received ok message from server
+ */
+ void receivedOk(bool own);
+
+ /**
+ * Send trade packet.
+ */
+ void tradeItem(Item *item, int quantity);
+
+ /**
+ * Updates the labels and makes sure only one item is selected in
+ * either my inventory or partner inventory.
+ */
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ /**
+ * Closes the Trade Window, as well as telling the server that the
+ * window has been closed.
+ */
+ void close();
+
+ /**
+ * Clear auto trade items.
+ */
+ void clear();
+
+ /**
+ * Add item what will be added to trade.
+ */
+ void addAutoItem(std::string nick, Item* item, int amount);
+
+ void addAutoMoney(std::string nick, int money);
+
+ void initTrade(std::string nick);
+
+ std::string getAutoTradeNick()
+ { return mAutoAddToNick; }
+
+ private:
+ enum Status
+ {
+ PREPARING = 0, /**< Players are adding items. (1) */
+ PROPOSING, /**< Local player has confirmed the trade. (1) */
+ ACCEPTING, /**< Accepting the trade. (2) */
+ ACCEPTED /**< Local player has accepted the trade. */
+ };
+
+ /**
+ * Sets the current status of the trade.
+ */
+ void setStatus(Status s);
+
+ typedef const std::auto_ptr<Inventory> InventoryPtr;
+ InventoryPtr mMyInventory;
+ InventoryPtr mPartnerInventory;
+
+ ItemContainer *mMyItemContainer;
+ ItemContainer *mPartnerItemContainer;
+
+ gcn::Label *mMoneyLabel;
+ gcn::Button *mAddButton;
+ gcn::Button *mOkButton;
+ gcn::Button *mMoneyChangeButton;
+ gcn::TextField *mMoneyField;
+
+ Status mStatus;
+ bool mOkOther, mOkMe;
+ Item* mAutoAddItem;
+ std::string mAutoAddToNick;
+ int mGotMoney;
+ int mAutoMoney;
+ int mAutoAddAmount;
+};
+
+extern TradeWindow *tradeWindow;
+
+#endif
diff --git a/src/gui/truetypefont.cpp b/src/gui/truetypefont.cpp
new file mode 100644
index 000000000..8e77c1ded
--- /dev/null
+++ b/src/gui/truetypefont.cpp
@@ -0,0 +1,336 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/truetypefont.h"
+
+#include "client.h"
+#include "graphics.h"
+#include "log.h"
+#include "main.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/stringutils.h"
+
+#include <guichan/exception.hpp>
+
+const unsigned int CACHE_SIZE = 256;
+const unsigned int CACHE_SIZE_SMALL1 = 90;
+const unsigned int CACHE_SIZE_SMALL2 = 150;
+
+char *strBuf;
+
+class TextChunk
+{
+ public:
+ TextChunk(const std::string &text, const gcn::Color &color) :
+ img(0), text(text), color(color)
+ {
+ }
+
+ ~TextChunk()
+ {
+ delete img;
+ img = 0;
+ }
+
+ bool operator==(const TextChunk &chunk) const
+ {
+ return (chunk.text == text && chunk.color == color);
+ }
+
+ void generate(TTF_Font *font)
+ {
+ SDL_Color sdlCol;
+ sdlCol.b = static_cast<Uint8>(color.b);
+ sdlCol.r = static_cast<Uint8>(color.r);
+ sdlCol.g = static_cast<Uint8>(color.g);
+
+ getSafeUtf8String(text, strBuf);
+
+// SDL_Surface *surface = TTF_RenderUTF8_Solid(
+ SDL_Surface *surface = TTF_RenderUTF8_Blended(
+ font, strBuf, sdlCol);
+
+ if (!surface)
+ {
+ img = 0;
+ return;
+ }
+
+ img = Image::load(surface);
+
+ SDL_FreeSurface(surface);
+ }
+
+ Image *img;
+ std::string text;
+ gcn::Color color;
+};
+
+typedef std::list<TextChunk>::iterator CacheIterator;
+
+static int fontCounter;
+
+TrueTypeFont::TrueTypeFont(const std::string &filename, int size, int style) :
+ mCreateCounter(0),
+ mDeleteCounter(0)
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+
+ if (fontCounter == 0 && TTF_Init() == -1)
+ {
+ throw GCN_EXCEPTION("Unable to initialize SDL_ttf: " +
+ std::string(TTF_GetError()));
+ }
+
+ if (!fontCounter)
+ {
+ strBuf = new char[65535];
+ memset(strBuf, 65535, 0);
+ }
+
+ ++fontCounter;
+ mFont = TTF_OpenFont(resman->getPath(filename).c_str(), size);
+
+ if (!mFont)
+ {
+ logger->log("Error finding font " + filename);
+ mFont = TTF_OpenFont(resman->getPath(
+ "fonts/dejavusans.ttf").c_str(), size);
+ if (!mFont)
+ {
+ throw GCN_EXCEPTION("SDLTrueTypeFont::SDLTrueTypeFont: " +
+ std::string(TTF_GetError()));
+ }
+ }
+
+ TTF_SetFontStyle(mFont, style);
+ mCleanTime = cur_time + 120;
+}
+
+TrueTypeFont::~TrueTypeFont()
+{
+ TTF_CloseFont(mFont);
+ mFont = 0;
+ --fontCounter;
+
+ if (fontCounter == 0)
+ {
+ TTF_Quit();
+ delete []strBuf;
+ }
+}
+
+void TrueTypeFont::loadFont(const std::string &filename, int size, int style)
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+
+ if (fontCounter == 0 && TTF_Init() == -1)
+ {
+ logger->log("Unable to initialize SDL_ttf: " +
+ std::string(TTF_GetError()));
+ return;
+ }
+
+ TTF_Font *font = TTF_OpenFont(resman->getPath(filename).c_str(), size);
+
+ if (!font)
+ {
+ logger->log("SDLTrueTypeFont::SDLTrueTypeFont: " +
+ std::string(TTF_GetError()));
+ return;
+ }
+
+ if (mFont)
+ TTF_CloseFont(mFont);
+
+ mFont = font;
+ TTF_SetFontStyle(mFont, style);
+ clear();
+}
+
+void TrueTypeFont::clear()
+{
+ for (unsigned short f = 0; f < (unsigned short)CACHES_NUMBER; f ++)
+ mCache[static_cast<unsigned short>(f)].clear();
+}
+
+void TrueTypeFont::drawString(gcn::Graphics *graphics,
+ const std::string &text,
+ int x, int y)
+{
+ if (text.empty())
+ return;
+
+ Graphics *g = dynamic_cast<Graphics *>(graphics);
+
+ gcn::Color col = g->getColor();
+ const float alpha = static_cast<float>(col.a) / 255.0f;
+
+ /* The alpha value is ignored at string generation so avoid caching the
+ * same text with different alpha values.
+ */
+ col.a = 255;
+
+ TextChunk chunk(text, col);
+
+ unsigned char chr = text[0];
+ std::list<TextChunk> *cache = &mCache[chr];
+
+ bool found = false;
+
+#ifdef DEBUG_FONT
+ int cnt = 0;
+#endif
+
+ for (CacheIterator i = cache->begin(); i != cache->end(); ++i)
+ {
+ if (chunk == (*i))
+ {
+ // Raise priority: move it to front
+ cache->splice(cache->begin(), *cache, i);
+ found = true;
+ break;
+ }
+#ifdef DEBUG_FONT
+ cnt ++;
+#endif
+ }
+#ifdef DEBUG_FONT
+ logger->log("drawString: " + text + ", iterations: " + toString(cnt));
+#endif
+
+ // Surface not found
+ if (!found)
+ {
+ if (cache->size() >= CACHE_SIZE)
+ {
+#ifdef DEBUG_FONT_COUNTERS
+ mDeleteCounter ++;
+#endif
+ cache->pop_back();
+ }
+#ifdef DEBUG_FONT_COUNTERS
+ mCreateCounter ++;
+#endif
+ cache->push_front(chunk);
+ cache->front().generate(mFont);
+
+ if (!mCleanTime)
+ {
+ mCleanTime = cur_time + 120;
+ }
+ else if (mCleanTime < cur_time)
+ {
+ doClean();
+ mCleanTime = cur_time + 120;
+ }
+ }
+
+ if (cache->front().img)
+ {
+ cache->front().img->setAlpha(alpha);
+ g->drawImage(cache->front().img, x, y);
+ }
+
+}
+
+void TrueTypeFont::createTextChunk(TextChunk *chunk)
+{
+ if (!chunk || chunk->text.empty())
+ return;
+
+ const float alpha = static_cast<float>(chunk->color.a) / 255.0f;
+ chunk->color.a = 255;
+ chunk->generate(mFont);
+ if (chunk->img)
+ chunk->img->setAlpha(alpha);
+}
+
+int TrueTypeFont::getWidth(const std::string &text) const
+{
+ if (text.empty())
+ return 0;
+
+ unsigned char chr = text[0];
+ std::list<TextChunk> *cache = &mCache[chr];
+
+#ifdef DEBUG_FONT
+ int cnt = 0;
+#endif
+
+ for (CacheIterator i = cache->begin(); i != cache->end(); i++)
+ {
+ if (i->text == text)
+ {
+ // Raise priority: move it to front
+ // Assumption is that TTF::draw will be called next
+ cache->splice(cache->begin(), *cache, i);
+ if (i->img)
+ return i->img->getWidth();
+ else
+ return 0;
+ }
+#ifdef DEBUG_FONT
+ cnt ++;
+#endif
+ }
+
+#ifdef DEBUG_FONT
+ logger->log("getWidth: " + text + ", iterations: " + toString(cnt));
+#endif
+
+ int w, h;
+ getSafeUtf8String(text, strBuf);
+ TTF_SizeUTF8(mFont, strBuf, &w, &h);
+ return w;
+}
+
+int TrueTypeFont::getHeight() const
+{
+ return TTF_FontHeight(mFont);
+}
+
+void TrueTypeFont::doClean()
+{
+ for (int f = 0; f < CACHES_NUMBER; f ++)
+ {
+ std::list<TextChunk> *cache = &mCache[f];
+ if (cache->size() > CACHE_SIZE_SMALL2)
+ {
+#ifdef DEBUG_FONT_COUNTERS
+ mDeleteCounter += 10;
+#endif
+ for (int d = 0; d < 10; d ++)
+ cache->pop_back();
+ }
+ else if (cache->size() > CACHE_SIZE_SMALL1)
+ {
+#ifdef DEBUG_FONT_COUNTERS
+ mDeleteCounter ++;
+#endif
+ cache->pop_back();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/gui/truetypefont.h b/src/gui/truetypefont.h
new file mode 100644
index 000000000..7a28c3b97
--- /dev/null
+++ b/src/gui/truetypefont.h
@@ -0,0 +1,104 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRUETYPEFONT_H
+#define TRUETYPEFONT_H
+
+#include <guichan/font.hpp>
+
+#ifdef __APPLE__
+#include <SDL_ttf/SDL_ttf.h>
+#else
+#ifdef __WIN32__
+#include <SDL/SDL_ttf.h>
+#else
+#include <SDL_ttf.h>
+#endif
+#endif
+
+#include <list>
+#include <string>
+
+#define CACHES_NUMBER 256
+
+class TextChunk;
+
+/**
+ * A wrapper around SDL_ttf for allowing the use of TrueType fonts.
+ *
+ * <b>NOTE:</b> This class initializes SDL_ttf as necessary.
+ */
+class TrueTypeFont : public gcn::Font
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @param filename Font filename.
+ * @param size Font size.
+ */
+ TrueTypeFont(const std::string &filename, int size, int style = 0);
+
+ /**
+ * Destructor.
+ */
+ ~TrueTypeFont();
+
+ void loadFont(const std::string &filename, int size, int style = 0);
+
+ void createTextChunk(TextChunk *chunk);
+
+ virtual int getWidth(const std::string &text) const;
+
+ virtual int getHeight() const;
+
+ std::list<TextChunk> *getCache()
+ { return mCache; }
+
+ /**
+ * @see Font::drawString
+ */
+ void drawString(gcn::Graphics *graphics,
+ const std::string &text,
+ int x, int y);
+
+ void clear();
+
+ void doClean();
+
+ int getCreateCounter() const
+ { return mCreateCounter; }
+
+ int getDeleteCounter() const
+ { return mDeleteCounter; }
+
+ private:
+ TTF_Font *mFont;
+ unsigned mCreateCounter;
+ unsigned mDeleteCounter;
+
+ // Word surfaces cache
+ mutable std::list<TextChunk> mCache[CACHES_NUMBER];
+ int mCleanTime;
+};
+
+#endif
diff --git a/src/gui/unregisterdialog.cpp b/src/gui/unregisterdialog.cpp
new file mode 100644
index 000000000..a226f0d84
--- /dev/null
+++ b/src/gui/unregisterdialog.cpp
@@ -0,0 +1,145 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/unregisterdialog.h"
+
+#include "client.h"
+#include "log.h"
+
+#include "gui/okdialog.h"
+#include "gui/register.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/passwordfield.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <string>
+#include <sstream>
+
+UnRegisterDialog::UnRegisterDialog(LoginData *loginData):
+ Window(_("Unregister"), true),
+ mWrongDataNoticeListener(new WrongDataNoticeListener),
+ mLoginData(loginData)
+{
+ gcn::Label *userLabel = new Label(strprintf(_("Name: %s"), mLoginData->
+ username.c_str()));
+ gcn::Label *passwordLabel = new Label(_("Password:"));
+ mPasswordField = new PasswordField(mLoginData->password);
+ mUnRegisterButton = new Button(_("Unregister"), "unregister", this);
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+
+ const int width = 210;
+ const int height = 80;
+ setContentSize(width, height);
+
+ userLabel->setPosition(5, 5);
+ userLabel->setWidth(width - 5);
+ mPasswordField->setPosition(
+ 68, userLabel->getY() + userLabel->getHeight() + 7);
+ mPasswordField->setWidth(130);
+
+ passwordLabel->setPosition(5, mPasswordField->getY() + 1);
+
+ mCancelButton->setPosition(
+ width - 5 - mCancelButton->getWidth(),
+ height - 5 - mCancelButton->getHeight());
+ mUnRegisterButton->setPosition(
+ mCancelButton->getX() - 5 - mUnRegisterButton->getWidth(),
+ mCancelButton->getY());
+
+ add(userLabel);
+ add(passwordLabel);
+ add(mPasswordField);
+ add(mUnRegisterButton);
+ add(mCancelButton);
+
+ center();
+ setVisible(true);
+ mPasswordField->requestFocus();
+ mPasswordField->setActionEventId("cancel");
+}
+
+UnRegisterDialog::~UnRegisterDialog()
+{
+ delete mWrongDataNoticeListener;
+ mWrongDataNoticeListener = 0;
+}
+
+void UnRegisterDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "cancel")
+ {
+ Client::setState(STATE_CHAR_SELECT);
+ }
+ else if (event.getId() == "unregister")
+ {
+ const std::string username = mLoginData->username.c_str();
+ const std::string password = mPasswordField->getText();
+ logger->log("UnregisterDialog::unregistered, Username is %s",
+ username.c_str());
+
+ std::stringstream errorMessage;
+ bool error = false;
+
+ unsigned int min = Net::getLoginHandler()->getMinPasswordLength();
+ unsigned int max = Net::getLoginHandler()->getMaxPasswordLength();
+
+ // Check password
+ if (password.length() < min)
+ {
+ // Pass too short
+ errorMessage << strprintf(_("The password needs to be at least %d "
+ "characters long."), min);
+ error = true;
+ }
+ else if (password.length() > max - 1)
+ {
+ // Pass too long
+ errorMessage << strprintf(_("The password needs to be less than "
+ "%d characters long."), max);
+ error = true;
+ }
+
+ if (error)
+ {
+ mWrongDataNoticeListener->setTarget(this->mPasswordField);
+
+ OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str());
+ dlg->addActionListener(mWrongDataNoticeListener);
+ }
+ else
+ {
+ // No errors detected, unregister the new user.
+ mUnRegisterButton->setEnabled(false);
+ mLoginData->password = password;
+ Client::setState(STATE_UNREGISTER_ATTEMPT);
+ }
+ }
+}
diff --git a/src/gui/unregisterdialog.h b/src/gui/unregisterdialog.h
new file mode 100644
index 000000000..dd330afdc
--- /dev/null
+++ b/src/gui/unregisterdialog.h
@@ -0,0 +1,68 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UNREGISTERDIALOG_H
+#define UNREGISTERDIALOG_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class LoginData;
+class OkDialog;
+class WrongDataNoticeListener;
+
+/**
+ * The Unregister dialog.
+ *
+ * \ingroup Interface
+ */
+class UnRegisterDialog : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ UnRegisterDialog(LoginData *loginData);
+
+ ~UnRegisterDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ private:
+ gcn::TextField *mPasswordField;
+
+ gcn::Button *mUnRegisterButton;
+ gcn::Button *mCancelButton;
+
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+
+ LoginData *mLoginData;
+};
+
+#endif
diff --git a/src/gui/updatewindow.cpp b/src/gui/updatewindow.cpp
new file mode 100644
index 000000000..e199d7a72
--- /dev/null
+++ b/src/gui/updatewindow.cpp
@@ -0,0 +1,672 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/updatewindow.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "log.h"
+#include "main.h"
+
+#include "gui/sdlinput.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/progressbar.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "net/download.h"
+#include "net/logindata.h"
+
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+#include "utils/xml.h"
+
+#include <iostream>
+#include <fstream>
+
+#include <sys/stat.h>
+
+const std::string xmlUpdateFile = "resources.xml";
+const std::string txtUpdateFile = "resources2.txt";
+
+std::vector<updateFile> loadXMLFile(const std::string &fileName);
+std::vector<updateFile> loadTxtFile(const std::string &fileName);
+
+/**
+ * Load the given file into a vector of updateFiles.
+ */
+std::vector<updateFile> loadXMLFile(const std::string &fileName)
+{
+ std::vector<updateFile> files;
+ XML::Document doc(fileName, false);
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "updates"))
+ {
+ logger->log("Error loading update file: %s", fileName.c_str());
+ return files;
+ }
+
+ for_each_xml_child_node(fileNode, rootNode)
+ {
+ // Ignore all tags except for the "update" tags
+ if (!xmlStrEqual(fileNode->name, BAD_CAST "update"))
+ continue;
+
+ updateFile file;
+ file.name = XML::getProperty(fileNode, "file", "");
+ file.hash = XML::getProperty(fileNode, "hash", "");
+ file.type = XML::getProperty(fileNode, "type", "data");
+ file.desc = XML::getProperty(fileNode, "description", "");
+ if (XML::getProperty(fileNode, "required", "yes") == "yes")
+ file.required = true;
+ else
+ file.required = false;
+
+ files.push_back(file);
+ }
+
+ return files;
+}
+
+std::vector<updateFile> loadTxtFile(const std::string &fileName)
+{
+ std::vector<updateFile> files;
+ std::ifstream fileHandler;
+ fileHandler.open(fileName.c_str(), std::ios::in);
+
+ if (fileHandler.is_open())
+ {
+ while (fileHandler.good())
+ {
+ char name[256], hash[50];
+ fileHandler.getline(name, 256, ' ');
+ fileHandler.getline(hash, 50);
+
+ updateFile thisFile;
+ thisFile.name = name;
+ thisFile.hash = hash;
+ thisFile.type = "data";
+ thisFile.required = true;
+ thisFile.desc = "";
+
+ files.push_back(thisFile);
+ }
+ }
+ else
+ {
+ logger->log("Error loading update file: %s", fileName.c_str());
+ }
+ fileHandler.close();
+
+ return files;
+}
+
+UpdaterWindow::UpdaterWindow(const std::string &updateHost,
+ const std::string &updatesDir,
+ bool applyUpdates,
+ int updateType):
+ Window(_("Updating...")),
+ mDownloadStatus(UPDATE_NEWS),
+ mUpdateHost(updateHost),
+ mUpdatesDir(updatesDir),
+ mCurrentFile("news.txt"),
+ mDownloadProgress(0.0f),
+ mCurrentChecksum(0),
+ mStoreInMemory(true),
+ mDownloadComplete(true),
+ mUserCancel(false),
+ mDownloadedBytes(0),
+ mMemoryBuffer(NULL),
+ mDownload(NULL),
+ mUpdateIndex(0),
+ mLoadUpdates(applyUpdates),
+ mUpdateType(updateType)
+{
+ mBrowserBox = new BrowserBox;
+ mScrollArea = new ScrollArea(mBrowserBox);
+ mLabel = new Label(_("Connecting..."));
+ mProgressBar = new ProgressBar(0.0, 310, 20);
+ mCancelButton = new Button(_("Cancel"), "cancel", this);
+ mPlayButton = new Button(_("Play"), "play", this);
+
+ mProgressBar->setSmoothProgress(false);
+ mBrowserBox->setOpaque(false);
+ mPlayButton->setEnabled(false);
+
+ ContainerPlacer place;
+ place = getPlacer(0, 0);
+
+ place(0, 0, mScrollArea, 5, 3).setPadding(3);
+ place(0, 3, mLabel, 5);
+ place(0, 4, mProgressBar, 5);
+ place(3, 5, mCancelButton);
+ place(4, 5, mPlayButton);
+
+ reflowLayout(450, 400);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ addKeyListener(this);
+
+ center();
+ setVisible(true);
+ mCancelButton->requestFocus();
+
+ // Try to download the updates list
+ download();
+}
+
+UpdaterWindow::~UpdaterWindow()
+{
+ if (mLoadUpdates)
+ loadUpdates();
+
+ if (mDownload)
+ {
+ mDownload->cancel();
+
+ delete mDownload;
+ mDownload = 0;
+ }
+ free(mMemoryBuffer);
+}
+
+void UpdaterWindow::setProgress(float p)
+{
+ // Do delayed progress bar update, since Guichan isn't thread-safe
+ MutexLocker lock(&mDownloadMutex);
+ mDownloadProgress = p;
+}
+
+void UpdaterWindow::setLabel(const std::string &str)
+{
+ // Do delayed label text update, since Guichan isn't thread-safe
+ MutexLocker lock(&mDownloadMutex);
+ mNewLabelCaption = str;
+}
+
+void UpdaterWindow::enable()
+{
+ mCancelButton->setEnabled(false);
+ mPlayButton->setEnabled(true);
+ mPlayButton->requestFocus();
+
+ if (mUpdateType & LoginData::Upd_Close)
+ Client::setState(STATE_LOAD_DATA);
+}
+
+void UpdaterWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "cancel")
+ {
+ // Register the user cancel
+ mUserCancel = true;
+ // Skip the updating process
+ if (mDownloadStatus != UPDATE_COMPLETE)
+ {
+ mDownload->cancel();
+ mDownloadStatus = UPDATE_ERROR;
+ }
+ }
+ else if (event.getId() == "play")
+ {
+ Client::setState(STATE_LOAD_DATA);
+ }
+}
+
+void UpdaterWindow::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ESCAPE)
+ {
+ action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId()));
+ Client::setState(STATE_WORLD_SELECT);
+ }
+ else if (key.getValue() == Key::ENTER)
+ {
+ if (mDownloadStatus == UPDATE_COMPLETE ||
+ mDownloadStatus == UPDATE_ERROR)
+ {
+ action(gcn::ActionEvent(NULL, mPlayButton->getActionEventId()));
+ }
+ else
+ {
+ action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId()));
+ }
+ }
+}
+
+void UpdaterWindow::loadNews()
+{
+ if (!mMemoryBuffer)
+ {
+ logger->log1("Couldn't load news");
+ return;
+ }
+
+ // Reallocate and include terminating 0 character
+ mMemoryBuffer = static_cast<char*>(realloc(
+ mMemoryBuffer, mDownloadedBytes + 1));
+
+ mMemoryBuffer[mDownloadedBytes] = '\0';
+
+ mBrowserBox->clearRows();
+
+ // Tokenize and add each line separately
+ char *line = strtok(mMemoryBuffer, "\n");
+ while (line)
+ {
+ mBrowserBox->addRow(line);
+ line = strtok(NULL, "\n");
+ }
+
+ // Free the memory buffer now that we don't need it anymore
+ free(mMemoryBuffer);
+ mMemoryBuffer = NULL;
+ mDownloadedBytes = 0;
+
+ mScrollArea->setVerticalScrollAmount(0);
+}
+
+void UpdaterWindow::loadPatch()
+{
+ if (!mMemoryBuffer)
+ {
+ logger->log1("Couldn't load patch");
+ return;
+ }
+
+ // Reallocate and include terminating 0 character
+ mMemoryBuffer = static_cast<char*>(
+ realloc(mMemoryBuffer, mDownloadedBytes + 1));
+ mMemoryBuffer[mDownloadedBytes] = '\0';
+
+ std::string version;
+
+ // Tokenize and add each line separately
+ char *line = strtok(mMemoryBuffer, "\n");
+ if (line)
+ {
+ version = line;
+ if (version > CHECK_VERSION)
+ {
+ mBrowserBox->addRow("", true);
+ mBrowserBox->addRow(" ##1http://tmw.cetki.com/4144/", true);
+ mBrowserBox->addRow("##1You can download it from", true);
+ mBrowserBox->addRow("##1ManaPlus updated.", true);
+ }
+ else
+ {
+ mBrowserBox->addRow("You have latest client version.", true);
+ }
+ }
+
+ // Free the memory buffer now that we don't need it anymore
+ free(mMemoryBuffer);
+ mMemoryBuffer = NULL;
+ mDownloadedBytes = 0;
+
+ mScrollArea->setVerticalScrollAmount(0);
+}
+
+int UpdaterWindow::updateProgress(void *ptr, DownloadStatus status,
+ size_t dt, size_t dn)
+{
+ UpdaterWindow *uw = reinterpret_cast<UpdaterWindow *>(ptr);
+ if (!uw)
+ return -1;
+
+ if (status == DOWNLOAD_STATUS_COMPLETE)
+ {
+ uw->mDownloadComplete = true;
+ }
+ else if (status == DOWNLOAD_STATUS_ERROR ||
+ status == DOWNLOAD_STATUS_CANCELLED)
+ {
+ uw->mDownloadStatus = UPDATE_ERROR;
+ }
+
+ if (!dt)
+ dt = 1;
+
+ float progress = static_cast<float>(dn) /
+ static_cast<float>(dt);
+
+ if (progress != progress)
+ progress = 0.0f; // check for NaN
+ if (progress < 0.0f)
+ progress = 0.0f; // no idea how this could ever happen,
+ // but why not check for it anyway.
+ if (progress > 1.0f)
+ progress = 1.0f;
+
+ uw->setLabel(uw->mCurrentFile + " ("
+ + toString(static_cast<int>(progress * 100)) + "%)");
+
+ uw->setProgress(progress);
+
+ if (Client::getState() != STATE_UPDATE
+ || uw->mDownloadStatus == UPDATE_ERROR)
+ {
+ // If the action was canceled return an error code to stop the mThread
+ return -1;
+ }
+
+ return 0;
+}
+
+size_t UpdaterWindow::memoryWrite(void *ptr, size_t size,
+ size_t nmemb, void *stream)
+{
+ UpdaterWindow *uw = reinterpret_cast<UpdaterWindow *>(stream);
+ size_t totalMem = size * nmemb;
+ uw->mMemoryBuffer = static_cast<char*>(realloc(uw->mMemoryBuffer,
+ uw->mDownloadedBytes + totalMem));
+ if (uw->mMemoryBuffer)
+ {
+ memcpy(&(uw->mMemoryBuffer[uw->mDownloadedBytes]), ptr, totalMem);
+ uw->mDownloadedBytes += static_cast<int>(totalMem);
+ }
+
+ return totalMem;
+}
+
+void UpdaterWindow::download()
+{
+ if (mDownload)
+ {
+ mDownload->cancel();
+ delete mDownload;
+ }
+ if (mDownloadStatus == UPDATE_PATCH)
+ {
+ mDownload = new Net::Download(this, "http://tmw.cetki.com/update/"
+ + mCurrentFile, updateProgress);
+ }
+ else
+ {
+ mDownload = new Net::Download(this, mUpdateHost + "/" + mCurrentFile,
+ updateProgress);
+ }
+
+ if (mStoreInMemory)
+ {
+ mDownload->setWriteFunction(UpdaterWindow::memoryWrite);
+ }
+ else
+ {
+ if (mDownloadStatus == UPDATE_RESOURCES)
+ {
+ mDownload->setFile(mUpdatesDir + "/" + mCurrentFile,
+ mCurrentChecksum);
+ }
+ else
+ {
+ mDownload->setFile(mUpdatesDir + "/" + mCurrentFile);
+ }
+ }
+
+ if (mDownloadStatus != UPDATE_RESOURCES)
+ mDownload->noCache();
+
+ setLabel(mCurrentFile + " (0%)");
+ mDownloadComplete = false;
+
+ // TODO: check return
+ mDownload->start();
+}
+
+void UpdaterWindow::loadUpdates()
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+
+ if (mUpdateFiles.empty())
+ { // updates not downloaded
+ mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile);
+ if (!mUpdateFiles.size())
+ {
+ logger->log("Warning this server does not have a"
+ " %s file falling back to %s", xmlUpdateFile.c_str(),
+ txtUpdateFile.c_str());
+ mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile);
+ }
+ }
+
+ std::string fixPath = mUpdatesDir + "/fix";
+ for (mUpdateIndex = 0; mUpdateIndex < mUpdateFiles.size(); mUpdateIndex++)
+ {
+ UpdaterWindow::addUpdateFile(resman, mUpdatesDir, fixPath,
+ mUpdateFiles[mUpdateIndex].name, false);
+ }
+}
+
+void UpdaterWindow::loadLocalUpdates(std::string dir)
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+
+ std::vector<updateFile> updateFiles
+ = loadXMLFile(dir + "/" + xmlUpdateFile);
+
+ if (updateFiles.empty())
+ {
+ logger->log("Warning this server does not have a"
+ " %s file falling back to %s", xmlUpdateFile.c_str(),
+ txtUpdateFile.c_str());
+ updateFiles = loadTxtFile(dir + "/" + txtUpdateFile);
+ }
+
+ std::string fixPath = dir + "/fix";
+ for (unsigned int updateIndex = 0;
+ updateIndex < updateFiles.size(); updateIndex++)
+ {
+ UpdaterWindow::addUpdateFile(resman, dir, fixPath,
+ updateFiles[updateIndex].name, false);
+ }
+}
+
+void UpdaterWindow::addUpdateFile(ResourceManager *resman, std::string path,
+ std::string fixPath, std::string file,
+ bool append)
+{
+ if (!append)
+ resman->addToSearchPath(path + "/" + file, append);
+
+ const std::string fixFile = fixPath + "/" + file;
+ struct stat statbuf;
+ if (!stat(fixFile.c_str(), &statbuf))
+ resman->addToSearchPath(fixFile, append);
+
+ if (append)
+ resman->addToSearchPath(path + "/" + file, append);
+}
+
+void UpdaterWindow::logic()
+{
+ // Update Scroll logic
+ mScrollArea->logic();
+
+ // Synchronize label caption when necessary
+ {
+ MutexLocker lock(&mDownloadMutex);
+
+ if (mLabel->getCaption() != mNewLabelCaption)
+ {
+ mLabel->setCaption(mNewLabelCaption);
+ mLabel->adjustSize();
+ }
+
+ mProgressBar->setProgress(mDownloadProgress);
+ }
+
+ std::string filename = mUpdatesDir + "/" + mCurrentFile;
+
+ switch (mDownloadStatus)
+ {
+ case UPDATE_ERROR:
+ // TODO: Only send complete sentences to gettext
+ mBrowserBox->addRow("");
+ mBrowserBox->addRow(_("##1 The update process is incomplete."));
+ // TRANSLATORS: Continues "you try again later.".
+ mBrowserBox->addRow(_("##1 It is strongly recommended that"));
+ // TRANSLATORS: Begins "It is strongly recommended that".
+ mBrowserBox->addRow(_("##1 you try again later."));
+
+ mBrowserBox->addRow(mDownload->getError());
+ mScrollArea->setVerticalScrollAmount(
+ mScrollArea->getVerticalMaxScroll());
+ mDownloadStatus = UPDATE_COMPLETE;
+ break;
+ case UPDATE_NEWS:
+ if (mDownloadComplete)
+ {
+ // Parse current memory buffer as news and dispose of the data
+ loadNews();
+
+ mCurrentFile = xmlUpdateFile;
+ mStoreInMemory = false;
+ mDownloadStatus = UPDATE_LIST;
+ download(); // download() changes mDownloadComplete to false
+ }
+ break;
+ case UPDATE_PATCH:
+ if (mDownloadComplete)
+ {
+ // Parse current memory buffer as news and dispose of the data
+ loadPatch();
+
+/*
+ mCurrentFile = "news.txt";
+ mStoreInMemory = true;
+ mDownloadStatus = UPDATE_NEWS;
+ download(); // download() changes mDownloadComplete to false
+*/
+ mDownloadStatus = UPDATE_COMPLETE;
+ }
+ break;
+
+ case UPDATE_LIST:
+ if (mDownloadComplete)
+ {
+ if (mCurrentFile == xmlUpdateFile)
+ {
+ mUpdateFiles = loadXMLFile(
+ mUpdatesDir + "/" + xmlUpdateFile);
+
+ if (mUpdateFiles.size() == 0)
+ {
+ logger->log("Warning this server does not have a %s"
+ " file falling back to %s",
+ xmlUpdateFile.c_str(),
+ txtUpdateFile.c_str());
+
+ // If the resources.xml file fails,
+ // fall back onto a older version
+ mCurrentFile = txtUpdateFile;
+ mStoreInMemory = false;
+ mDownloadStatus = UPDATE_LIST;
+ download();
+ break;
+ }
+ }
+ else if (mCurrentFile == txtUpdateFile)
+ {
+ mUpdateFiles = loadTxtFile(
+ mUpdatesDir + "/" + txtUpdateFile);
+ }
+ mStoreInMemory = false;
+ mDownloadStatus = UPDATE_RESOURCES;
+ }
+ break;
+ case UPDATE_RESOURCES:
+ if (mDownloadComplete)
+ {
+ if (mUpdateIndex < mUpdateFiles.size())
+ {
+ updateFile thisFile = mUpdateFiles[mUpdateIndex];
+ if (!thisFile.required)
+ {
+ // This statement checks to see if the file type
+ // is music, and if download-music is true
+ // If it fails, this statement returns true,
+ // and results in not downloading the file
+ // Else it will ignore the break,
+ // and download the file.
+
+ if (!(thisFile.type == "music"
+ && config.getBoolValue("download-music")))
+ {
+ mUpdateIndex++;
+ break;
+ }
+ }
+ mCurrentFile = thisFile.name;
+ std::string checksum;
+ checksum = thisFile.hash;
+ std::stringstream ss(checksum);
+ ss >> std::hex >> mCurrentChecksum;
+
+ std::ifstream temp(
+ (mUpdatesDir + "/" + mCurrentFile).c_str());
+
+ if (!temp.is_open())
+ {
+ temp.close();
+ download();
+ }
+ else
+ {
+ temp.close();
+ logger->log("%s already here", mCurrentFile.c_str());
+ }
+ mUpdateIndex++;
+ }
+ else
+ {
+ // Download of updates completed
+// mDownloadStatus = UPDATE_COMPLETE;
+ mCurrentFile = "latest.txt";
+ mStoreInMemory = true;
+ mDownloadStatus = UPDATE_PATCH;
+ download(); // download() changes
+ // mDownloadComplete to false
+ }
+ }
+ break;
+ case UPDATE_COMPLETE:
+ enable();
+ setLabel(_("Completed"));
+ break;
+ case UPDATE_IDLE:
+ break;
+ default:
+ logger->log("UpdaterWindow::logic unknown status: "
+ + toString(static_cast<unsigned>(mDownloadStatus)));
+ break;
+ }
+}
diff --git a/src/gui/updatewindow.h b/src/gui/updatewindow.h
new file mode 100644
index 000000000..841af23a5
--- /dev/null
+++ b/src/gui/updatewindow.h
@@ -0,0 +1,210 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UPDATERWINDOW_H
+#define _UPDATERWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include "net/download.h"
+
+#include "utils/mutex.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+#include <string>
+#include <vector>
+
+class BrowserBox;
+class Button;
+class ProgressBar;
+class ResourceManager;
+class ScrollArea;
+
+struct updateFile
+{
+ public:
+ std::string name;
+ std::string hash;
+ std::string type;
+ bool required;
+ std::string desc;
+};
+
+/**
+ * Update progress window GUI
+ *
+ * \ingroup GUI
+ */
+class UpdaterWindow : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @param updateHost Host where to get the updated files.
+ * @param updatesDir Directory where to store updates (should be absolute
+ * and already created).
+ * @param applyUpdates If true, the update window will pass the updates to teh
+ * resource manager
+ */
+ UpdaterWindow(const std::string &updateHost,
+ const std::string &updatesDir,
+ bool applyUpdates, int updateType);
+
+ /**
+ * Destructor
+ */
+ ~UpdaterWindow();
+
+ /**
+ * Set's progress bar status
+ */
+ void setProgress(float p);
+
+ /**
+ * Set's label above progress
+ */
+ void setLabel(const std::string &);
+
+ /**
+ * Enables play button
+ */
+ void enable();
+
+ /**
+ * Loads and display news. Assumes the news file contents have been loaded
+ * into the memory buffer.
+ */
+ void loadNews();
+
+ void loadPatch();
+
+ void action(const gcn::ActionEvent &event);
+
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ void logic();
+
+ static void loadLocalUpdates(std::string dir);
+
+ static void addUpdateFile(ResourceManager *resman, std::string path,
+ std::string fixPath, std::string file,
+ bool append);
+
+ int updateState;
+
+private:
+ void download();
+
+ /**
+ * Loads the updates this window has gotten into the resource manager
+ */
+ void loadUpdates();
+
+
+ /**
+ * A download callback for progress updates.
+ */
+ static int updateProgress(void *ptr, DownloadStatus status,
+ size_t dt, size_t dn);
+
+ /**
+ * A libcurl callback for writing to memory.
+ */
+ static size_t memoryWrite(void *ptr, size_t size, size_t nmemb,
+ void *stream);
+
+ enum UpdateDownloadStatus
+ {
+ UPDATE_ERROR = 0,
+ UPDATE_IDLE,
+ UPDATE_LIST,
+ UPDATE_COMPLETE,
+ UPDATE_NEWS,
+ UPDATE_RESOURCES,
+ UPDATE_PATCH
+ };
+
+ /** Status of the current download. */
+ UpdateDownloadStatus mDownloadStatus;
+
+ /** Host where we get the updated files. */
+ std::string mUpdateHost;
+
+ /** Place where the updates are stored (absolute path). */
+ std::string mUpdatesDir;
+
+ /** The file currently downloading. */
+ std::string mCurrentFile;
+
+ /** The new label caption to be set in the logic method. */
+ std::string mNewLabelCaption;
+
+ /** The new progress value to be set in the logic method. */
+ float mDownloadProgress;
+
+ /** The mutex used to guard access to mNewLabelCaption and mDownloadProgress. */
+ Mutex mDownloadMutex;
+
+ /** The Adler32 checksum of the file currently downloading. */
+ unsigned long mCurrentChecksum;
+
+ /** A flag to indicate whether to use a memory buffer or a regular file. */
+ bool mStoreInMemory;
+
+ /** Flag that show if current download is complete. */
+ bool mDownloadComplete;
+
+ /** Flag that show if the user has canceled the update. */
+ bool mUserCancel;
+
+ /** Byte count currently downloaded in mMemoryBuffer. */
+ int mDownloadedBytes;
+
+ /** Buffer for files downloaded to memory. */
+ char *mMemoryBuffer;
+
+ /** Download handle. */
+ Net::Download *mDownload;
+
+ /** List of files to download. */
+ std::vector<updateFile> mUpdateFiles;
+
+ /** Index of the file to be downloaded. */
+ unsigned int mUpdateIndex;
+
+ /** Tells ~UpdaterWindow() if it should load updates */
+ bool mLoadUpdates;
+
+ int mUpdateType;
+
+ gcn::Label *mLabel; /**< Progress bar caption. */
+ Button *mCancelButton; /**< Button to stop the update process. */
+ Button *mPlayButton; /**< Button to start playing. */
+ ProgressBar *mProgressBar; /**< Update progress bar. */
+ BrowserBox *mBrowserBox; /**< Box to display news. */
+ ScrollArea *mScrollArea; /**< Used to scroll news box. */
+};
+
+#endif
diff --git a/src/gui/userpalette.cpp b/src/gui/userpalette.cpp
new file mode 100644
index 000000000..03e5c1eed
--- /dev/null
+++ b/src/gui/userpalette.cpp
@@ -0,0 +1,292 @@
+/*
+ * Configurable text colors
+ * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net>
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "userpalette.h"
+
+#include "configuration.h"
+#include "client.h"
+#include "log.h"
+
+#include "gui/gui.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <math.h>
+
+const std::string ColorTypeNames[] =
+{
+ "ColorBeing",
+ "ColorFriend",
+ "ColorDisregarded",
+ "ColorIgnored",
+ "ColorErased",
+ "ColorPlayer",
+ "ColorSelf",
+ "ColorGM",
+ "ColorNPC",
+ "ColorMonster",
+ "ColorMonsterHp",
+ "ColorMonsterHp2",
+ "ColorParty",
+ "ColorGuild",
+ "ColorParticle",
+ "ColorPickupInfo",
+ "ColorExpInfo",
+ "ColorHitPlayerMonster",
+ "ColorHitMonsterPlayer",
+ "ColorHitPlayerPlayer",
+ "ColorHitCritical",
+ "ColorHitLocalPlayerMonster",
+ "ColorHitLocalPlayerCritical",
+ "ColorHitLocalPlayerMiss",
+ "ColorMiss",
+ "ColorPortalHighlight",
+ "ColorCollisionHighlight",
+ "ColorWalkableTileHighlight",
+ "ColorAttackRange",
+ "ColorAttackRangeBorder",
+ "ColorMonsterAttackRange",
+ "ColorHomePlace",
+ "ColorHomePlaceBorder",
+ "ColorRoadPoint"
+};
+
+std::string UserPalette::getConfigName(const std::string &typeName)
+{
+ std::string res = "Color" + typeName;
+
+ int pos = 5;
+ for (size_t i = 0; i < typeName.length(); i++)
+ {
+ if (i == 0 || typeName[i] == '_')
+ {
+ if (i > 0)
+ i++;
+
+ res[pos] = typeName[i];
+ }
+ else
+ {
+ res[pos] = static_cast<char>(tolower(typeName[i]));
+ }
+ pos++;
+ }
+ res.erase(pos, res.length() - pos);
+
+ return res;
+}
+
+UserPalette::UserPalette():
+ Palette(USER_COLOR_LAST)
+{
+ mColors[BEING] = ColorElem();
+ mColors[PC] = ColorElem();
+ mColors[SELF] = ColorElem();
+ mColors[GM] = ColorElem();
+ mColors[NPC] = ColorElem();
+ mColors[MONSTER] = ColorElem();
+
+ addColor(BEING, 0xffffff, STATIC, _("Being"));
+ addColor(FRIEND, 0xb0ffb0, STATIC, _("Friend Names"));
+ addColor(DISREGARDED, 0xa00000, STATIC, _("Disregarded Names"));
+ addColor(IGNORED, 0xff0000, STATIC, _("Ignored Names"));
+ addColor(ERASED, 0xff0000, STATIC, _("Erased Names"));
+ addColor(PC, 0xffffff, STATIC, _("Other Players' Names"));
+ addColor(SELF, 0xff8040, STATIC, _("Own Name"));
+ addColor(GM, 0x00ff00, STATIC, _("GM Names"));
+ addColor(NPC, 0xc8c8ff, STATIC, _("NPCs"));
+ addColor(MONSTER, 0xff4040, STATIC, _("Monsters"));
+ addColor(MONSTER_HP, 0x00ff00, STATIC, _("Monster HP bar"), 50);
+ addColor(MONSTER_HP2, 0xff0000, STATIC,
+ _("Monster HP bar (second color)"), 50);
+ addColor(PARTY, 0xff00d8, STATIC, _("Party Members"));
+ addColor(GUILD, 0xff00d8, STATIC, _("Guild Members"));
+ addColor(PARTICLE, 0xffffff, STATIC, _("Particle Effects"));
+ addColor(PICKUP_INFO, 0x28dc28, STATIC, _("Pickup Notification"));
+ addColor(EXP_INFO, 0xffff00, STATIC, _("Exp Notification"));
+ addColor(HIT_PLAYER_MONSTER, 0x0064ff, STATIC, _("Player Hits Monster"));
+ addColor(HIT_MONSTER_PLAYER, 0xff3232, STATIC, _("Monster Hits Player"));
+ addColor(HIT_PLAYER_PLAYER, 0xff5050, STATIC,
+ _("Other Player Hits Local Player"));
+ addColor(HIT_CRITICAL, 0xff0000, RAINBOW, _("Critical Hit"));
+ addColor(HIT_LOCAL_PLAYER_MONSTER, 0x00ff00, STATIC,
+ _("Local Player Hits Monster"));
+ addColor(HIT_LOCAL_PLAYER_CRITICAL, 0xff0000, RAINBOW,
+ _("Local Player Critical Hit"));
+ addColor(HIT_LOCAL_PLAYER_MISS, 0x00ffa6, STATIC,
+ _("Local Player Miss"));
+ addColor(MISS, 0xffff00, STATIC, _("Misses"));
+ addColor(PORTAL_HIGHLIGHT, 0xC80000, STATIC, _("Portal Highlight"));
+ addColor(COLLISION_HIGHLIGHT, 0x0000C8, STATIC,
+ _("Collision Highlight"), 64);
+ addColor(WALKABLE_HIGHLIGHT, 0x00D000, STATIC,
+ _("Walkable Highlight"), 255);
+ addColor(ATTACK_RANGE, 0xffffff, STATIC,
+ _("Local Player Attack Range"), 5);
+ addColor(ATTACK_RANGE_BORDER, 0x0, STATIC,
+ _("Local Player Attack Range Border"), 76);
+ addColor(MONSTER_ATTACK_RANGE, 0xff0000, STATIC,
+ _("Monster Attack Range"), 20);
+ addColor(HOME_PLACE, 0xffffff, STATIC,
+ _("Home Place"), 20);
+ addColor(HOME_PLACE_BORDER, 0xffff00, STATIC,
+ _("Home Place Border"), 200);
+ addColor(ROAD_POINT, 0x000000, STATIC,
+ _("Road Point"), 100);
+ commit(true);
+}
+
+UserPalette::~UserPalette()
+{
+ for (Colors::iterator col = mColors.begin(),
+ colEnd = mColors.end(); col != colEnd; ++col)
+ {
+ const std::string &configName = ColorTypeNames[col->type];
+ config.setValue(configName + "Gradient",
+ static_cast<int>(col->committedGrad));
+ config.setValue(configName + "Delay", col->delay);
+
+ if (col->grad == STATIC || col->grad == PULSE)
+ {
+ char buffer[20];
+ sprintf(buffer, "0x%06x", col->getRGB());
+ config.setValue(configName, std::string(buffer));
+ }
+ }
+}
+
+void UserPalette::setColor(int type, int r, int g, int b)
+{
+ mColors[type].color.r = r;
+ mColors[type].color.g = g;
+ mColors[type].color.b = b;
+}
+
+void UserPalette::setGradient(int type, GradientType grad)
+{
+ ColorElem *elem = &mColors[type];
+ if (!elem)
+ return;
+
+ if (elem->grad != STATIC && grad == STATIC)
+ {
+ for (size_t i = 0; i < mGradVector.size(); i++)
+ {
+ if (mGradVector[i] == elem)
+ {
+ mGradVector.erase(mGradVector.begin() + i);
+ break;
+ }
+ }
+ }
+ else if (elem->grad == STATIC && grad != STATIC)
+ {
+ mGradVector.push_back(elem);
+ }
+
+ if (elem->grad != grad)
+ elem->grad = grad;
+}
+
+std::string UserPalette::getElementAt(int i)
+{
+ if (i < 0 || i >= getNumberOfElements())
+ return "";
+
+ return mColors[i].text;
+}
+
+void UserPalette::commit(bool commitNonStatic)
+{
+ for (Colors::iterator i = mColors.begin(), iEnd = mColors.end();
+ i != iEnd; ++i)
+ {
+ i->committedGrad = i->grad;
+ i->committedDelay = i->delay;
+ if (commitNonStatic || i->grad == STATIC)
+ i->committedColor = i->color;
+ else if (i->grad == PULSE)
+ i->committedColor = i->testColor;
+ }
+}
+
+void UserPalette::rollback()
+{
+ for (Colors::iterator i = mColors.begin(), iEnd = mColors.end();
+ i != iEnd; ++i)
+ {
+ if (i->grad != i->committedGrad)
+ setGradient(i->type, i->committedGrad);
+
+ setGradientDelay(i->type, i->committedDelay);
+ setColor(i->type, i->committedColor.r,
+ i->committedColor.g, i->committedColor.b);
+
+ if (i->grad == PULSE)
+ {
+ i->testColor.r = i->committedColor.r;
+ i->testColor.g = i->committedColor.g;
+ i->testColor.b = i->committedColor.b;
+ }
+ }
+}
+
+int UserPalette::getColorTypeAt(int i)
+{
+ if (i < 0 || i >= getNumberOfElements())
+ return BEING;
+
+ return mColors[i].type;
+}
+
+void UserPalette::addColor(unsigned type, unsigned rgb,
+ Palette::GradientType grad, const std::string &text,
+ int delay)
+{
+ const unsigned maxType = sizeof(ColorTypeNames)
+ / sizeof(ColorTypeNames[0]);
+
+ if (type >= maxType)
+ return;
+
+ const std::string &configName = ColorTypeNames[type];
+ char buffer[20];
+ sprintf(buffer, "0x%06x", rgb);
+
+ const std::string rgbString = config.getValue(configName,
+ std::string(buffer));
+ unsigned int rgbValue = 0;
+ if (rgbString.length() == 8 && rgbString[0] == '0' && rgbString[1] == 'x')
+ rgbValue = atox(rgbString);
+ else
+ rgbValue = atoi(rgbString.c_str());
+ gcn::Color trueCol = rgbValue;
+ grad = static_cast<GradientType>(config.getValue(configName + "Gradient",
+ static_cast<int>(grad)));
+ delay = config.getValueInt(configName + "Delay", delay);
+ mColors[type].set(type, trueCol, grad, delay);
+ mColors[type].text = text;
+
+ if (grad != STATIC)
+ mGradVector.push_back(&mColors[type]);
+}
diff --git a/src/gui/userpalette.h b/src/gui/userpalette.h
new file mode 100644
index 000000000..057d47113
--- /dev/null
+++ b/src/gui/userpalette.h
@@ -0,0 +1,222 @@
+/*
+ * Configurable text colors
+ * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net>
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef USER_PALETTE_H
+#define USER_PALETTE_H
+
+#include "gui/palette.h"
+
+#include <guichan/listmodel.hpp>
+
+/**
+ * Class controlling the game's color palette.
+ */
+class UserPalette : public Palette, public gcn::ListModel
+{
+ public:
+ /** List of all colors that are configurable. */
+ enum
+ {
+ BEING = 0,
+ FRIEND,
+ DISREGARDED,
+ IGNORED,
+ ERASED,
+ PC,
+ SELF,
+ GM,
+ NPC,
+ MONSTER,
+ MONSTER_HP,
+ MONSTER_HP2,
+ PARTY,
+ GUILD,
+ PARTICLE,
+ PICKUP_INFO,
+ EXP_INFO,
+ HIT_PLAYER_MONSTER,
+ HIT_MONSTER_PLAYER,
+ HIT_PLAYER_PLAYER,
+ HIT_CRITICAL,
+ HIT_LOCAL_PLAYER_MONSTER,
+ HIT_LOCAL_PLAYER_CRITICAL,
+ HIT_LOCAL_PLAYER_MISS,
+ MISS,
+ PORTAL_HIGHLIGHT,
+ COLLISION_HIGHLIGHT,
+ WALKABLE_HIGHLIGHT,
+ ATTACK_RANGE,
+ ATTACK_RANGE_BORDER,
+ MONSTER_ATTACK_RANGE,
+ HOME_PLACE,
+ HOME_PLACE_BORDER,
+ ROAD_POINT,
+ USER_COLOR_LAST
+ };
+
+ /**
+ * Constructor
+ */
+ UserPalette();
+
+ /**
+ * Destructor
+ */
+ ~UserPalette();
+
+ /**
+ * Gets the committed color associated with the specified type.
+ *
+ * @param type the color type requested
+ *
+ * @return the requested committed color
+ */
+ inline const gcn::Color &getCommittedColor(int type)
+ {
+ return mColors[type].committedColor;
+ }
+
+ /**
+ * Gets the test color associated with the specified type.
+ *
+ * @param type the color type requested
+ *
+ * @return the requested test color
+ */
+ inline const gcn::Color &getTestColor(int type)
+ { return mColors[type].testColor; }
+
+ /**
+ * Sets the test color associated with the specified type.
+ *
+ * @param type the color type requested
+ * @param color the color that should be tested
+ */
+ inline void setTestColor(int type, gcn::Color color)
+ { mColors[type].testColor = color; }
+
+ /**
+ * Sets the color for the specified type.
+ *
+ * @param type color to be set
+ * @param r red component
+ * @param g green component
+ * @param b blue component
+ */
+ void setColor(int type, int r, int g, int b);
+
+ /**
+ * Sets the gradient type for the specified color.
+ *
+ * @param grad gradient type to set
+ */
+ void setGradient(int type, Palette::GradientType grad);
+
+ /**
+ * Sets the gradient delay for the specified color.
+ *
+ * @param grad gradient type to set
+ */
+ void setGradientDelay(int type, int delay)
+ { mColors[type].delay = delay; }
+
+ /**
+ * Returns the number of colors known.
+ *
+ * @return the number of colors known
+ */
+ inline int getNumberOfElements()
+ { return static_cast<int>(mColors.size()); }
+
+ /**
+ * Returns the name of the ith color.
+ *
+ * @param i index of color interested in
+ *
+ * @return the name of the color
+ */
+ std::string getElementAt(int i);
+
+ /**
+ * Commit the colors
+ */
+ inline void commit()
+ { commit(false); }
+
+ /**
+ * Rollback the colors
+ */
+ void rollback();
+
+ /**
+ * Gets the ColorType used by the color for the element at index i in
+ * the current color model.
+ *
+ * @param i the index of the color
+ *
+ * @return the color type of the color with the given index
+ */
+ int getColorTypeAt(int i);
+
+ private:
+ /**
+ * Define a color replacement.
+ *
+ * @param i the index of the color to replace
+ * @param r red component
+ * @param g green component
+ * @param b blue component
+ */
+ void setColorAt(int i, int r, int g, int b);
+
+ /**
+ * Commit the colors. Commit the non-static color values, if
+ * commitNonStatic is true. Only needed in the constructor.
+ */
+ void commit(bool commitNonStatic);
+
+ /**
+ * Prefixes the given string with "Color", lowercases all letters but
+ * the first and all following a '_'. All '_'s will be removed.
+ *
+ * E.g.: HIT_PLAYER_MONSTER -> HitPlayerMonster
+ *
+ * @param typeName string to transform
+ *
+ * @return the transformed string
+ */
+ static std::string getConfigName(const std::string &typeName);
+
+ /**
+ * Initialise color
+ *
+ * @param c character that needs initialising
+ * @param rgb default color if not found in config
+ * @param text identifier of color
+ */
+ void addColor(unsigned type, unsigned rgb, GradientType grad,
+ const std::string &text, int delay = GRADIENT_DELAY);
+};
+
+extern UserPalette *userPalette;
+
+#endif // USER_PALETTE_H
diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp
new file mode 100644
index 000000000..e2bab0621
--- /dev/null
+++ b/src/gui/viewport.cpp
@@ -0,0 +1,763 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/viewport.h"
+
+#include "actorsprite.h"
+#include "actorspritemanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "itemshortcut.h"
+#include "keyboardconfig.h"
+#include "localplayer.h"
+#include "map.h"
+#include "textmanager.h"
+
+#include "gui/beingpopup.h"
+#include "gui/chat.h"
+#include "gui/gui.h"
+#include "gui/ministatus.h"
+#include "gui/popupmenu.h"
+#include "gui/statuspopup.h"
+#include "gui/textpopup.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/chattab.h"
+
+#include "net/net.h"
+
+#include "resources/resourcemanager.h"
+
+#include "utils/stringutils.h"
+
+extern volatile int tick_time;
+
+Viewport::Viewport():
+ mMap(0),
+ mMouseX(0),
+ mMouseY(0),
+ mPixelViewX(0.0f),
+ mPixelViewY(0.0f),
+// mTileViewX(0),
+// mTileViewY(0),
+ mShowDebugPath(false),
+ mCameraMode(0),
+ mPlayerFollowMouse(false),
+ mLocalWalkTime(-1),
+ mHoverBeing(0),
+ mHoverItem(0),
+ mHoverSign(0),
+ mCameraRelativeX(0),
+ mCameraRelativeY(0)
+{
+ setOpaque(false);
+ addMouseListener(this);
+
+ mScrollLaziness = config.getIntValue("ScrollLaziness");
+ mScrollRadius = config.getIntValue("ScrollRadius");
+ mScrollCenterOffsetX = config.getIntValue("ScrollCenterOffsetX");
+ mScrollCenterOffsetY = config.getIntValue("ScrollCenterOffsetY");
+
+ config.addListener("ScrollLaziness", this);
+ config.addListener("ScrollRadius", this);
+
+ mPopupMenu = new PopupMenu;
+ mBeingPopup = new BeingPopup;
+ mTextPopup = new TextPopup;
+
+ setFocusable(true);
+}
+
+Viewport::~Viewport()
+{
+ config.removeListener("ScrollLaziness", this);
+ config.removeListener("ScrollRadius", this);
+
+ delete mPopupMenu;
+ mPopupMenu = 0;
+ delete mBeingPopup;
+ mBeingPopup = 0;
+ delete mTextPopup;
+ mTextPopup = 0;
+}
+
+void Viewport::setMap(Map *map)
+{
+ if (mMap && map)
+ map->setDebugFlags(mMap->getDebugFlags());
+ mMap = map;
+}
+
+extern MiniStatusWindow *miniStatusWindow;
+
+void Viewport::draw(gcn::Graphics *gcnGraphics)
+{
+ static int lastTick = tick_time;
+
+ if (!mMap || !player_node)
+ {
+ gcnGraphics->setColor(gcn::Color(64, 64, 64));
+ gcnGraphics->fillRectangle(
+ gcn::Rectangle(0, 0, getWidth(), getHeight()));
+ return;
+ }
+
+ Graphics *graphics = static_cast<Graphics*>(gcnGraphics);
+
+ // Avoid freaking out when tick_time overflows
+ if (tick_time < lastTick)
+ lastTick = tick_time;
+
+ // Calculate viewpoint
+ int midTileX = (graphics->getWidth() + mScrollCenterOffsetX) / 2;
+ int midTileY = (graphics->getHeight() + mScrollCenterOffsetX) / 2;
+
+ const Vector &playerPos = player_node->getPosition();
+ const int player_x = static_cast<int>(playerPos.x)
+ - midTileX + mCameraRelativeX;
+ const int player_y = static_cast<int>(playerPos.y)
+ - midTileY + mCameraRelativeY;
+
+ if (mScrollLaziness < 1)
+ mScrollLaziness = 1; // Avoids division by zero
+
+ // Apply lazy scrolling
+ while (lastTick < tick_time)
+ {
+ if (player_x > static_cast<int>(mPixelViewX) + mScrollRadius)
+ {
+ mPixelViewX += static_cast<float>(player_x
+ - static_cast<int>(mPixelViewX) - mScrollRadius) /
+ static_cast<float>(mScrollLaziness);
+ }
+ if (player_x < static_cast<int>(mPixelViewX) - mScrollRadius)
+ {
+ mPixelViewX += static_cast<float>(player_x
+ - static_cast<int>(mPixelViewX) + mScrollRadius) /
+ static_cast<float>(mScrollLaziness);
+ }
+ if (player_y > static_cast<int>(mPixelViewY) + mScrollRadius)
+ {
+ mPixelViewY += static_cast<float>(player_y
+ - static_cast<int>(mPixelViewY) - mScrollRadius) /
+ static_cast<float>(mScrollLaziness);
+ }
+ if (player_y < static_cast<int>(mPixelViewY) - mScrollRadius)
+ {
+ mPixelViewY += static_cast<float>(player_y
+ - static_cast<int>(mPixelViewY) + mScrollRadius) /
+ static_cast<float>(mScrollLaziness);
+ }
+ lastTick++;
+ }
+
+ // Auto center when player is off screen
+ if (player_x - static_cast<int>(mPixelViewX) > graphics->getWidth() / 2
+ || static_cast<int>(mPixelViewX) - player_x > graphics->getWidth() / 2
+ || static_cast<int>(mPixelViewY) - player_y
+ > graphics->getHeight() / 2
+ || player_y - static_cast<int>(mPixelViewY)
+ > graphics->getHeight() / 2)
+ {
+ mPixelViewX = static_cast<float>(player_x);
+ mPixelViewY = static_cast<float>(player_y);
+ };
+
+ // Don't move camera so that the end of the map is on screen
+ const int viewXmax =
+ mMap->getWidth() * mMap->getTileWidth() - graphics->getWidth();
+ const int viewYmax =
+ mMap->getHeight() * mMap->getTileHeight() - graphics->getHeight();
+ if (mMap)
+ {
+ if (mPixelViewX < 0)
+ mPixelViewX = 0;
+ if (mPixelViewY < 0)
+ mPixelViewY = 0;
+ if (mPixelViewX > viewXmax)
+ mPixelViewX = static_cast<float>(viewXmax);
+ if (mPixelViewY > viewYmax)
+ mPixelViewY = static_cast<float>(viewYmax);
+ }
+
+ // Draw tiles and sprites
+ if (mMap)
+ {
+ mMap->draw(graphics, static_cast<int>(mPixelViewX),
+ static_cast<int>(mPixelViewY));
+
+ if (mShowDebugPath)
+ {
+ mMap->drawCollision(graphics,
+ static_cast<int>(mPixelViewX),
+ static_cast<int>(mPixelViewY),
+ mShowDebugPath);
+ if (mShowDebugPath == Map::MAP_DEBUG)
+ _drawDebugPath(graphics);
+ }
+ }
+
+ if (player_node->getCheckNameSetting())
+ {
+ player_node->setCheckNameSetting(false);
+ player_node->setName(player_node->getName());
+ }
+
+ // Draw text
+ if (textManager)
+ textManager->draw(graphics, static_cast<int>(mPixelViewX),
+ static_cast<int>(mPixelViewY));
+
+ // Draw player names, speech, and emotion sprite as needed
+ const ActorSprites &actors = actorSpriteManager->getAll();
+ for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end();
+ it != it_end; it++)
+ {
+ if ((*it)->getType() == ActorSprite::FLOOR_ITEM)
+ continue;
+ Being *b = static_cast<Being*>(*it);
+
+ b->drawSpeech(static_cast<int>(mPixelViewX),
+ static_cast<int>(mPixelViewY));
+ b->drawEmotion(graphics, static_cast<int>(mPixelViewX),
+ static_cast<int>(mPixelViewY));
+ }
+
+ if (miniStatusWindow)
+ miniStatusWindow->drawIcons(graphics);
+
+ // Draw contained widgets
+ WindowContainer::draw(gcnGraphics);
+}
+
+void Viewport::logic()
+{
+ WindowContainer::logic();
+
+ // Make the player follow the mouse position
+ // if the mouse is dragged elsewhere than in a window.
+ _followMouse();
+}
+
+void Viewport::_followMouse()
+{
+ Uint8 button = SDL_GetMouseState(&mMouseX, &mMouseY);
+ // If the left button is dragged
+ if (mPlayerFollowMouse && button & SDL_BUTTON(1))
+ {
+ // We create a mouse event and send it to mouseDragged.
+ Uint8 *keys = SDL_GetKeyState(NULL);
+ gcn::MouseEvent mouseEvent(NULL,
+ (keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT]),
+ false,
+ false,
+ false,
+ gcn::MouseEvent::DRAGGED,
+ gcn::MouseEvent::LEFT,
+ mMouseX,
+ mMouseY,
+ 0);
+
+ mouseDragged(mouseEvent);
+ }
+}
+
+void Viewport::_drawDebugPath(Graphics *graphics)
+{
+ // Get the current mouse position
+ SDL_GetMouseState(&mMouseX, &mMouseY);
+
+ Path debugPath;
+
+ if (Net::getNetworkType() == ServerInfo::TMWATHENA)
+ {
+ const int mouseTileX = (mMouseX + static_cast<int>(mPixelViewX)) / 32;
+ const int mouseTileY = (mMouseY + static_cast<int>(mPixelViewY)) / 32;
+ const Vector &playerPos = player_node->getPosition();
+
+ debugPath = mMap->findPath(
+ static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ mouseTileX, mouseTileY, 0, 500);
+
+ _drawPath(graphics, debugPath);
+ }
+ else if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ const Vector &playerPos = player_node->getPosition();
+ const int playerRadius = player_node->getCollisionRadius();
+ // Draw player collision rectangle
+ graphics->setColor(gcn::Color(128, 128, 0, 120));
+ graphics->fillRectangle(
+ gcn::Rectangle(static_cast<int>(playerPos.x)
+ - static_cast<int>(mPixelViewX) - playerRadius,
+ static_cast<int>(playerPos.y)
+ - static_cast<int>(mPixelViewY) - playerRadius,
+ playerRadius * 2, playerRadius * 2));
+
+ debugPath = mMap->findPixelPath(
+ static_cast<int>(playerPos.x),
+ static_cast<int>(playerPos.y),
+ mMouseX + static_cast<int>(mPixelViewX),
+ mMouseY + static_cast<int>(mPixelViewY),
+ playerRadius, 0xFF);
+
+ // We draw the path proposed by mouse
+ _drawPath(graphics, debugPath, gcn::Color(128, 0, 128));
+
+ // But also the one currently walked on.
+ _drawPath(graphics, player_node->getPath(), gcn::Color(0, 0, 255));
+ }
+}
+
+void Viewport::_drawPath(Graphics *graphics, const Path &path,
+ gcn::Color color)
+{
+ graphics->setColor(color);
+
+ if (Net::getNetworkType() == ServerInfo::TMWATHENA)
+ {
+ for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
+ {
+ int squareX = i->x * 32 - static_cast<int>(mPixelViewX) + 12;
+ int squareY = i->y * 32 - static_cast<int>(mPixelViewY) + 12;
+
+ graphics->fillRectangle(gcn::Rectangle(squareX, squareY, 8, 8));
+ if (mMap)
+ {
+ graphics->drawText(
+ toString(mMap->getMetaTile(i->x, i->y)->Gcost),
+ squareX + 4, squareY + 12, gcn::Graphics::CENTER);
+ }
+ }
+ }
+ else if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
+ {
+ int squareX = i->x - static_cast<int>(mPixelViewX);
+ int squareY = i->y - static_cast<int>(mPixelViewY);
+
+ graphics->fillRectangle(gcn::Rectangle(squareX - 4, squareY - 4,
+ 8, 8));
+ if (mMap)
+ {
+ graphics->drawText(
+ toString(mMap->getMetaTile(i->x / 32, i->y / 32)->Gcost),
+ squareX + 4, squareY + 12, gcn::Graphics::CENTER);
+ }
+ }
+
+ }
+}
+
+void Viewport::mousePressed(gcn::MouseEvent &event)
+{
+ if (event.getSource() != this)
+ return;
+
+ // Check if we are alive and kickin'
+// if (!mMap || !player_node || !player_node->isAlive())
+ if (!mMap || !player_node)
+ return;
+
+ // Check if we are busy
+ // if commented, allow context menu if npc dialog open
+ if (Being::isTalking())
+ return;
+
+ mPlayerFollowMouse = false;
+
+ const int pixelX = event.getX() + static_cast<int>(mPixelViewX);
+ const int pixelY = event.getY() + static_cast<int>(mPixelViewY);
+
+ // Right click might open a popup
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (mHoverBeing)
+ {
+ if (actorSpriteManager)
+ {
+ std::list<Being*> beings;
+ const int x = getMouseX() + static_cast<int>(mPixelViewX);
+ const int y = getMouseY() + static_cast<int>(mPixelViewY);
+ actorSpriteManager->findBeingsByPixel(beings, x, y, true);
+ if (beings.size() > 1)
+ {
+ mPopupMenu->showPopup(event.getX(), event.getY(), beings);
+ return;
+ }
+ else
+ {
+ mPopupMenu->showPopup(event.getX(), event.getY(),
+ mHoverBeing);
+ return;
+ }
+ }
+ }
+ else if (mHoverItem)
+ {
+ mPopupMenu->showPopup(event.getX(), event.getY(), mHoverItem);
+ return;
+ }
+ else if (mHoverSign)
+ {
+ mPopupMenu->showPopup(event.getX(), event.getY(), mHoverSign);
+ return;
+ }
+ }
+
+ // If a popup is active, just remove it
+ if (mPopupMenu->isVisible())
+ {
+ mPopupMenu->setVisible(false);
+ return;
+ }
+
+ // Left click can cause different actions
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ // Interact with some being
+ if (mHoverBeing)
+ {
+ if (!mHoverBeing->isAlive())
+ return;
+
+ if (mHoverBeing->canTalk())
+ {
+ mHoverBeing->talkTo();
+ }
+ else
+ {
+ if (mHoverBeing->getType() == ActorSprite::PLAYER)
+ {
+ if (actorSpriteManager)
+ actorSpriteManager->heal(player_node, mHoverBeing);
+ }
+ else if (player_node->withinAttackRange(mHoverBeing) ||
+ keyboard.isKeyActive(keyboard.KEY_ATTACK))
+ {
+ player_node->attack(mHoverBeing,
+ !keyboard.isKeyActive(keyboard.KEY_TARGET));
+ }
+ else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK))
+ {
+ player_node->setGotoTarget(mHoverBeing);
+ }
+ }
+ // Picks up a item if we clicked on one
+ }
+ else if (mHoverItem)
+ {
+ player_node->pickUp(mHoverItem);
+ }
+ // Just walk around
+ else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK))
+ {
+ player_node->stopAttack();
+ player_node->cancelFollow();
+ mPlayerFollowMouse = true;
+
+ // Make the player go to the mouse position
+ _followMouse();
+ }
+ }
+ else if (event.getButton() == gcn::MouseEvent::MIDDLE)
+ {
+ // Find the being nearest to the clicked position
+ if (actorSpriteManager)
+ {
+ Being *target = actorSpriteManager->findNearestLivingBeing(
+ pixelX, pixelY, 20, ActorSprite::MONSTER);
+
+ if (target)
+ player_node->setTarget(target);
+ }
+ }
+}
+
+void Viewport::mouseDragged(gcn::MouseEvent &event)
+{
+ if (!mMap || !player_node)
+ return;
+
+ if (mPlayerFollowMouse && !event.isShiftPressed())
+ {
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ if (get_elapsed_time(mLocalWalkTime) >= walkingMouseDelay)
+ {
+ mLocalWalkTime = tick_time;
+ player_node->setDestination(event.getX()
+ + static_cast<int>(mPixelViewX),
+ event.getY()
+ + static_cast<int>(mPixelViewY));
+ player_node->pathSetByMouse();
+ }
+ }
+ else
+ {
+ if (mLocalWalkTime != player_node->getActionTime())
+ {
+ mLocalWalkTime = player_node->getActionTime();
+ int destX = static_cast<int>((static_cast<float>(event.getX())
+ + mPixelViewX)
+ / static_cast<float>(mMap->getTileWidth()));
+ int destY = static_cast<int>((static_cast<float>(event.getY())
+ + mPixelViewY)
+ / static_cast<float>(mMap->getTileHeight()));
+ player_node->setDestination(destX, destY);
+ }
+ }
+ }
+}
+
+void Viewport::mouseReleased(gcn::MouseEvent &event _UNUSED_)
+{
+ mPlayerFollowMouse = false;
+
+ // Only useful for eAthena but doesn't hurt under ManaServ
+ mLocalWalkTime = -1;
+}
+
+void Viewport::showPopup(Window *parent, int x, int y, Item *item,
+ bool isInventory)
+{
+ mPopupMenu->showPopup(parent, x, y, item, isInventory);
+}
+
+void Viewport::showPopup(MapItem *item)
+{
+ mPopupMenu->showPopup(getMouseX(), getMouseY(), item);
+}
+
+void Viewport::showPopup(Window *parent, Item *item, bool isInventory)
+{
+ mPopupMenu->showPopup(parent, getMouseX(), getMouseY(), item, isInventory);
+}
+
+void Viewport::showItemPopup(Item *item)
+{
+ mPopupMenu->showItemPopup(getMouseX(), getMouseY(), item);
+}
+
+void Viewport::showItemPopup(int itemId)
+{
+ mPopupMenu->showItemPopup(getMouseX(), getMouseY(), itemId);
+}
+
+void Viewport::showDropPopup(Item *item)
+{
+ mPopupMenu->showDropPopup(getMouseX(), getMouseY(), item);
+}
+
+void Viewport::showOutfitsPopup(int x, int y)
+{
+ mPopupMenu->showOutfitsPopup(x, y);
+}
+
+void Viewport::showOutfitsPopup()
+{
+ mPopupMenu->showOutfitsPopup(getMouseX(), getMouseY());
+}
+
+void Viewport::showSpellPopup(TextCommand *cmd)
+{
+ mPopupMenu->showSpellPopup(getMouseX(), getMouseY(), cmd);
+}
+
+void Viewport::showChatPopup(int x, int y, ChatTab *tab)
+{
+ mPopupMenu->showChatPopup(x, y, tab);
+}
+
+void Viewport::showChatPopup(ChatTab *tab)
+{
+ mPopupMenu->showChatPopup(getMouseX(), getMouseY(), tab);
+}
+
+void Viewport::showPopup(int x, int y, Being *being)
+{
+ mPopupMenu->showPopup(x, y, being);
+}
+
+void Viewport::showPlayerPopup(std::string nick)
+{
+ mPopupMenu->showPlayerPopup(getMouseX(), getMouseY(), nick);
+}
+
+void Viewport::showPopup(int x, int y, Button *button)
+{
+ mPopupMenu->showPopup(x, y, button);
+}
+
+void Viewport::closePopupMenu()
+{
+ if (mPopupMenu)
+ mPopupMenu->handleLink("cancel", 0);
+}
+
+void Viewport::optionChanged(const std::string &name _UNUSED_)
+{
+ mScrollLaziness = config.getIntValue("ScrollLaziness");
+ mScrollRadius = config.getIntValue("ScrollRadius");
+}
+
+void Viewport::mouseMoved(gcn::MouseEvent &event _UNUSED_)
+{
+ // Check if we are on the map
+ if (!mMap || !player_node || !actorSpriteManager)
+ return;
+
+ const int x = getMouseX() + static_cast<int>(mPixelViewX);
+ const int y = getMouseY() + static_cast<int>(mPixelViewY);
+
+ mHoverBeing = actorSpriteManager->findBeingByPixel(x, y, true);
+ if (mHoverBeing && mHoverBeing->getType() == Being::PLAYER)
+ {
+ mTextPopup->setVisible(false);
+ mBeingPopup->show(getMouseX(), getMouseY(), mHoverBeing);
+ }
+ else
+ {
+ mBeingPopup->setVisible(false);
+ }
+
+ mHoverItem = 0;
+ if (!mHoverBeing && actorSpriteManager)
+ {
+ mHoverItem = actorSpriteManager->findItem(x / mMap->getTileWidth(),
+ y / mMap->getTileHeight());
+ }
+ if (!mHoverBeing && !mHoverItem)
+ {
+ SpecialLayer *specialLayer = mMap->getSpecialLayer();
+ if (specialLayer)
+ {
+ int mouseTileX = (getMouseX() + getCameraX())
+ / mMap->getTileWidth();
+ int mouseTileY = (getMouseY() + getCameraY())
+ / mMap->getTileHeight();
+
+ mHoverSign = specialLayer->getTile(mouseTileX, mouseTileY);
+ if (mHoverSign && mHoverSign->getType() != MapItem::EMPTY)
+ {
+ if (!mHoverSign->getComment().empty())
+ {
+ if (mBeingPopup)
+ mBeingPopup->setVisible(false);
+ mTextPopup->show(getMouseX(), getMouseY(),
+ mHoverSign->getComment());
+ }
+ else
+ {
+ if (mTextPopup->isVisible())
+ mTextPopup->setVisible(false);
+ }
+ return;
+ }
+ }
+ }
+ if (mTextPopup->isVisible())
+ mTextPopup->setVisible(false);
+
+ if (mHoverBeing)
+ {
+ switch (mHoverBeing->getType())
+ {
+ // NPCs
+ case ActorSprite::NPC:
+ gui->setCursorType(Gui::CURSOR_TALK);
+ break;
+
+ // Monsters
+ case ActorSprite::MONSTER:
+ gui->setCursorType(Gui::CURSOR_FIGHT);
+ break;
+ default:
+ gui->setCursorType(Gui::CURSOR_POINTER);
+ break;
+ }
+ }
+ // Item mouseover
+ else if (mHoverItem)
+ {
+ gui->setCursorType(Gui::CURSOR_PICKUP);
+ }
+ else
+ {
+ gui->setCursorType(Gui::CURSOR_POINTER);
+ }
+}
+
+void Viewport::toggleDebugPath()
+{
+ mShowDebugPath++;
+ if (mShowDebugPath > Map::MAP_BLACKWHITE)
+ mShowDebugPath = Map::MAP_NORMAL;
+ if (mMap)
+ mMap->setDebugFlags(mShowDebugPath);
+}
+
+void Viewport::toggleCameraMode()
+{
+ mCameraMode++;
+ if (mCameraMode > 1)
+ mCameraMode = 0;
+ if (!mCameraMode)
+ {
+ mCameraRelativeX = 0;
+ mCameraRelativeY = 0;
+ }
+ if (miniStatusWindow)
+ miniStatusWindow->updateStatus();
+}
+
+void Viewport::hideBeingPopup()
+{
+ if (mBeingPopup)
+ mBeingPopup->setVisible(false);
+ if (mTextPopup)
+ mTextPopup->setVisible(false);
+}
+
+void Viewport::clearHover(ActorSprite *actor)
+{
+ if (mHoverBeing == actor)
+ mHoverBeing = 0;
+
+ if (mHoverItem == actor)
+ mHoverItem = 0;
+}
+
+void Viewport::cleanHoverItems()
+{
+ mHoverBeing = 0;
+ mHoverItem = 0;
+ mHoverSign = 0;
+}
+
+void Viewport::moveCamera(int dx, int dy)
+{
+ mCameraRelativeX += dx;
+ mCameraRelativeY += dy;
+} \ No newline at end of file
diff --git a/src/gui/viewport.h b/src/gui/viewport.h
new file mode 100644
index 000000000..cf5e53c4d
--- /dev/null
+++ b/src/gui/viewport.h
@@ -0,0 +1,298 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VIEWPORT_H
+#define VIEWPORT_H
+
+#include "actorspritemanager.h"
+#include "configlistener.h"
+#include "position.h"
+
+#include "gui/widgets/windowcontainer.h"
+
+#include <guichan/mouselistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class ActorSprite;
+class Button;
+class Being;
+class BeingPopup;
+class ChatTab;
+class FloorItem;
+class Graphics;
+class ImageSet;
+class Item;
+class ItemShortcut;
+class Map;
+class PopupMenu;
+class TextCommand;
+class StatusPopup;
+class TextPopup;
+class Window;
+
+/** Delay between two mouse calls when dragging mouse and move the player */
+const int walkingMouseDelay = 500;
+
+/**
+ * The viewport on the map. Displays the current map and handles mouse input
+ * and the popup menu.
+ *
+ * TODO: This class is planned to be extended to allow floating widgets on top
+ * of it such as NPC messages, which are positioned using map pixel
+ * coordinates.
+ */
+class Viewport : public WindowContainer, public gcn::MouseListener,
+ public ConfigListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ Viewport();
+
+ /**
+ * Destructor.
+ */
+ ~Viewport();
+
+ /**
+ * Sets the map displayed by the viewport.
+ */
+ void setMap(Map *map);
+
+ /**
+ * Draws the viewport.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Implements player to keep following mouse.
+ */
+ void logic();
+
+ /**
+ * Toggles whether the path debug graphics are shown. normal, debug with all images and grid, debug with out big images and with out grid.
+ */
+ void toggleDebugPath();
+
+ void toggleCameraMode();
+
+ /**
+ * Handles mouse press on map.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse move on map
+ */
+ void mouseDragged(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse button release on map.
+ */
+ void mouseReleased(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse move on map.
+ */
+ void mouseMoved(gcn::MouseEvent &event);
+
+ /**
+ * Shows a popup for an item.
+ * TODO Find some way to get rid of Item here
+ */
+ void showPopup(Window *parent, int x, int y, Item *item,
+ bool isInventory = true);
+
+ /**
+ * Shows a popup for an item.
+ * TODO Find some way to get rid of Item here
+ */
+ void showPopup(Window *parent, Item *item, bool isInventory = true);
+
+ void showPopup(int x, int y, Button *button);
+
+ void showPopup(MapItem *item);
+
+ void showItemPopup(Item *item);
+
+ void showItemPopup(int itemId);
+
+ void showDropPopup(Item *item);
+
+ /**
+ * Shows a popup for being.
+ */
+ void showPopup(int x, int y, Being *being);
+
+ void showPlayerPopup(std::string nick);
+
+ void showOutfitsPopup(int x, int y);
+
+ void showOutfitsPopup();
+
+ void showSpellPopup(TextCommand *cmd);
+
+ /**
+ * Shows the related popup menu when right click on the chat
+ * at the specified mouse coordinates.
+ */
+ void showChatPopup(int x, int y, ChatTab *tab);
+
+ /**
+ * Shows the related popup menu when right click on the chat
+ */
+ void showChatPopup(ChatTab *tab);
+
+ /**
+ * Closes the popup menu. Needed for when the player dies or switching
+ * maps.
+ */
+ void closePopupMenu();
+
+ /**
+ * A relevant config option changed.
+ */
+ void optionChanged(const std::string &name);
+
+ /**
+ * Returns camera x offset in pixels.
+ */
+ int getCameraX() const
+ { return static_cast<int>(mPixelViewX); }
+
+ /**
+ * Returns camera y offset in pixels.
+ */
+ int getCameraY() const
+ { return static_cast<int>(mPixelViewY); }
+
+ /**
+ * Returns mouse x in pixels.
+ */
+ int getMouseX() const
+ { return mMouseX; }
+
+ /**
+ * Returns mouse y in pixels.
+ */
+ int getMouseY() const
+ { return mMouseY; }
+
+ /**
+ * Changes viewpoint by relative pixel coordinates.
+ */
+ void scrollBy(float x, float y)
+ { mPixelViewX += x; mPixelViewY += y; }
+
+ /**
+ * Returns the current map object.
+ */
+ Map *getCurrentMap() const
+ { return mMap; }
+
+ int getDebugPath()
+ { return mShowDebugPath; }
+
+ int getCameraMode()
+ { return mCameraMode; }
+
+ /**
+ * Hides the BeingPopup.
+ */
+ void hideBeingPopup();
+
+ /**
+ * Clear all hover item\being etc
+ */
+ void cleanHoverItems();
+
+ Map *getMap()
+ { return mMap; }
+
+ void moveCamera(int dx, int dy);
+
+ int getCameraRelativeX()
+ { return mCameraRelativeX; }
+
+ int getCameraRelativeY()
+ { return mCameraRelativeY; }
+
+ protected:
+ friend class ActorSpriteManager;
+
+ /// Clears any matching hovers
+ void clearHover(ActorSprite *actor);
+
+ private:
+ /**
+ * Finds a path from the player to the mouse, and draws it. This is for
+ * debug purposes.
+ */
+ void _drawDebugPath(Graphics *graphics);
+
+ /**
+ * Draws the given path.
+ */
+ void _drawPath(Graphics *graphics, const Path &path,
+ gcn::Color color = gcn::Color(255, 0, 0));
+
+ /**
+ * Make the player go to the mouse position.
+ */
+ void _followMouse();
+
+ Map *mMap; /**< The current map. */
+
+ int mScrollRadius;
+ int mScrollLaziness;
+ int mScrollCenterOffsetX;
+ int mScrollCenterOffsetY;
+ int mMouseX; /**< Current mouse position in pixels. */
+ int mMouseY; /**< Current mouse position in pixels. */
+ float mPixelViewX; /**< Current viewpoint in pixels. */
+ float mPixelViewY; /**< Current viewpoint in pixels. */
+ int mShowDebugPath; /**< Show a path from player to pointer. */
+ int mCameraMode; /**< Camera mode. */
+
+ bool mPlayerFollowMouse;
+
+ int mLocalWalkTime; /**< Timestamp before the next walk can be sent. */
+
+ PopupMenu *mPopupMenu; /**< Popup menu. */
+ Being *mHoverBeing; /**< Being mouse is currently over. */
+ FloorItem *mHoverItem; /**< FloorItem mouse is currently over. */
+ MapItem *mHoverSign; /**< Map sign mouse is currently over. */
+ BeingPopup *mBeingPopup; /**< Being information popup. */
+ TextPopup *mTextPopup; /**< Map Item information popup. */
+
+ int mCameraRelativeX;
+ int mCameraRelativeY;
+};
+
+extern Viewport *viewport; /**< The viewport. */
+
+#endif
diff --git a/src/gui/whoisonline.cpp b/src/gui/whoisonline.cpp
new file mode 100644
index 000000000..84485b995
--- /dev/null
+++ b/src/gui/whoisonline.cpp
@@ -0,0 +1,550 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "whoisonline.h"
+
+#include <SDL.h>
+#include <SDL_thread.h>
+#include <vector>
+#include <algorithm>
+
+#include "gui/viewport.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/chattab.h"
+
+#include "actorspritemanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "localplayer.h"
+#include "playerrelations.h"
+#include "main.h"
+
+#include "gui/chat.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+// Curl should be included after Guichan to avoid Windows redefinitions
+#include <curl/curl.h>
+
+bool stringCompare(const std::string &left, const std::string &right);
+
+bool stringCompare(const std::string &left, const std::string &right )
+{
+ for (std::string::const_iterator lit = left.begin(),
+ rit = right.begin();
+ lit != left.end() && rit != right.end(); ++lit, ++rit)
+ {
+ if (tolower(*lit) < tolower(*rit))
+ return true;
+ else if (tolower(*lit) > tolower(*rit))
+ return false;
+ }
+ if (left.size() < right.size())
+ return true;
+ return false;
+}
+
+WhoIsOnline::WhoIsOnline():
+ Window(_("Who Is Online - Updating")),
+ mThread(NULL),
+ mDownloadStatus(UPDATE_LIST),
+ mDownloadComplete(true),
+ mDownloadedBytes(0),
+ mMemoryBuffer(NULL),
+ mCurlError(new char[CURL_ERROR_SIZE]),
+ mAllowUpdate(true),
+ mShowLevel(false)
+{
+ mCurlError[0] = 0;
+ setWindowName("WhoIsOnline");
+
+ const int h = 350;
+ const int w = 200;
+ setDefaultSize(w, h, ImageRect::CENTER);
+// setContentSize(w, h);
+ setCloseButton(true);
+ setResizable(true);
+
+ mUpdateButton = new Button(_("Update"), "update", this);
+ mUpdateButton->setEnabled(false);
+ mUpdateButton->setDimension(gcn::Rectangle(5, 5, w - 10, 20 + 5));
+
+ mBrowserBox = new BrowserBox();
+ mScrollArea = new ScrollArea(mBrowserBox);
+ mScrollArea->setOpaque(false);
+ mBrowserBox->setOpaque(false);
+ mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
+ mScrollArea->setDimension(gcn::Rectangle(5, 20 + 10, w - 10, h - 10 - 30));
+ mScrollArea->setSize(w - 10, h - 10 - 30);
+ mBrowserBox->setLinkHandler(this);
+
+ add(mUpdateButton);
+ add(mScrollArea);
+
+ mUpdateTimer = 0;
+ setLocationRelativeTo(getParent());
+
+ loadWindowState();
+
+ download();
+
+ config.addListener("updateOnlineList", this);
+ mUpdateOnlineList = config.getBoolValue("updateOnlineList");
+}
+
+WhoIsOnline::~WhoIsOnline()
+{
+ config.removeListener("updateOnlineList", this);
+
+ if (mThread && SDL_GetThreadID(mThread))
+ SDL_WaitThread(mThread, NULL);
+
+ free(mMemoryBuffer);
+ mMemoryBuffer = 0;
+
+ // Remove possibly leftover temporary download
+ delete[] mCurlError;
+}
+
+void WhoIsOnline::handleLink(const std::string& link, gcn::MouseEvent *event)
+{
+ if (!event || event->getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (chatWindow)
+ {
+ if (config.getBoolValue("whispertab"))
+ chatWindow->localChatInput("/q " + link);
+ else
+ chatWindow->addInputText("/w \"" + link + "\" ");
+ }
+ }
+ else if (event->getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (player_node && link == player_node->getName())
+ return;
+
+ if (viewport)
+ {
+ if (actorSpriteManager)
+ {
+ Being* being = actorSpriteManager->findBeingByName(
+ link, Being::PLAYER);
+
+ if (being && viewport)
+ {
+ viewport->showPopup(event->getX(), event->getY(), being);
+ return;
+ }
+ }
+ viewport->showPlayerPopup(link);
+ }
+ }
+}
+
+void WhoIsOnline::loadList()
+{
+ if (!mMemoryBuffer)
+ return;
+
+ // Reallocate and include terminating 0 character
+ mMemoryBuffer = static_cast<char*>(
+ realloc(mMemoryBuffer, mDownloadedBytes + 1));
+ mMemoryBuffer[mDownloadedBytes] = '\0';
+
+ mBrowserBox->clearRows();
+ bool listStarted(false);
+ std::string lineStr;
+ int numOnline(0);
+ std::vector<std::string> friends;
+ std::vector<std::string> neutral;
+ std::vector<std::string> disregard;
+
+ // Tokenize and add each line separately
+ char *line = strtok(mMemoryBuffer, "\n");
+ const std::string gmText = "(GM)";
+ mOnlinePlayers.clear();
+
+ mShowLevel = config.getBoolValue("showlevel");
+
+ while (line != NULL)
+ {
+ std::string nick;
+ lineStr = line;
+ trim(lineStr);
+ if (listStarted == true)
+ {
+ size_t found;
+ found = lineStr.find(" users are online.");
+ if (found == std::string::npos)
+ {
+ int level = 0;
+
+ std::string::size_type pos = 0;
+ if (lineStr.length() > 24)
+ {
+ nick = lineStr.substr(0, 24);
+ lineStr = lineStr.substr(25);
+ }
+ else
+ {
+ nick = lineStr;
+ lineStr = "";
+ }
+ trim(nick);
+
+ pos = lineStr.find(gmText, 0);
+ if (pos != std::string::npos)
+ lineStr = lineStr.substr(pos + gmText.length());
+
+ trim(lineStr);
+ pos = lineStr.find("/", 0);
+
+ if (pos != std::string::npos)
+ lineStr = lineStr.substr(0, pos);
+
+ if (!lineStr.empty())
+ level = atoi(lineStr.c_str());
+
+ if (actorSpriteManager)
+ {
+ Being *being = actorSpriteManager->findBeingByName(
+ nick, Being::PLAYER);
+ if (being)
+ {
+ if (level > 0)
+ {
+ being->setLevel(level);
+ being->updateName();
+ }
+ else
+ {
+ if (being->getLevel() > 1)
+ level = being->getLevel();
+ }
+ }
+ }
+
+ mOnlinePlayers.insert(nick);
+
+ numOnline++;
+ switch (player_relations.getRelation(nick))
+ {
+ case PlayerRelation::NEUTRAL:
+ neutral.push_back(prepareNick(nick, level, "0"));
+ break;
+
+ case PlayerRelation::FRIEND:
+ friends.push_back(prepareNick(nick, level, "2"));
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ disregard.push_back(prepareNick(nick, level, "8"));
+ break;
+
+ case PlayerRelation::IGNORED:
+ case PlayerRelation::ERASED:
+ default:
+ //Ignore the ignored.
+ break;
+ }
+ }
+ }
+ else if (lineStr.find("------------------------------")
+ != std::string::npos)
+ {
+ listStarted = true;
+ }
+ line = strtok(NULL, "\n");
+ }
+
+ //Set window caption
+ setCaption(_("Who Is Online - ") + toString(numOnline));
+
+ //List the online people
+ sort(friends.begin(), friends.end(), stringCompare);
+ sort(neutral.begin(), neutral.end(), stringCompare);
+ sort(disregard.begin(), disregard.end(), stringCompare);
+ bool addedFromSection(false);
+ for (int i = 0; i < static_cast<int>(friends.size()); i++)
+ {
+ mBrowserBox->addRow(friends.at(i));
+ addedFromSection = true;
+ }
+ if (addedFromSection == true)
+ {
+ mBrowserBox->addRow("---");
+ addedFromSection = false;
+ }
+ for (int i = 0; i < static_cast<int>(neutral.size()); i++)
+ {
+ mBrowserBox->addRow(neutral.at(i));
+ addedFromSection = true;
+ }
+ if (addedFromSection == true && !disregard.empty())
+ {
+ mBrowserBox->addRow("---");
+ addedFromSection = false;
+ }
+ for (int i = 0; i < static_cast<int>(disregard.size()); i++)
+ {
+ mBrowserBox->addRow(disregard.at(i));
+ }
+
+ // Free the memory buffer now that we don't need it anymore
+ free(mMemoryBuffer);
+ mMemoryBuffer = 0;
+
+ if (mScrollArea->getVerticalMaxScroll() <
+ mScrollArea->getVerticalScrollAmount())
+ {
+ mScrollArea->setVerticalScrollAmount(
+ mScrollArea->getVerticalMaxScroll());
+ }
+}
+
+size_t WhoIsOnline::memoryWrite(void *ptr, size_t size,
+ size_t nmemb, FILE *stream)
+{
+ WhoIsOnline *wio = reinterpret_cast<WhoIsOnline *>(stream);
+ size_t totalMem = size * nmemb;
+ wio->mMemoryBuffer = static_cast<char*>(realloc(wio->mMemoryBuffer,
+ wio->mDownloadedBytes + totalMem));
+ if (wio->mMemoryBuffer)
+ {
+ memcpy(&(wio->mMemoryBuffer[wio->mDownloadedBytes]), ptr, totalMem);
+ wio->mDownloadedBytes += static_cast<int>(totalMem);
+ }
+
+ return totalMem;
+}
+
+int WhoIsOnline::downloadThread(void *ptr)
+{
+ int attempts = 0;
+ WhoIsOnline *wio = reinterpret_cast<WhoIsOnline *>(ptr);
+ CURL *curl;
+ CURLcode res;
+
+ std::string url(Client::getServerName() + "/online.txt");
+
+ while (attempts < 1 && !wio->mDownloadComplete)
+ {
+ curl = curl_easy_init();
+
+ if (curl)
+ {
+ if (!wio->mAllowUpdate)
+ {
+ curl_easy_cleanup(curl);
+ break;
+ }
+ wio->mDownloadedBytes = 0;
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+ WhoIsOnline::memoryWrite);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, ptr);
+
+ curl_easy_setopt(curl, CURLOPT_USERAGENT,
+ strprintf(PACKAGE_EXTENDED_VERSION, branding
+ .getValue("appShort", "mana").c_str()).c_str());
+
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, wio->mCurlError);
+ curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
+ curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, ptr);
+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7);
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
+
+ struct curl_slist *pHeaders = 0;
+ // Make sure the resources2.txt and news.txt aren't cached,
+ // in order to always get the latest version.
+ pHeaders = curl_slist_append(pHeaders, "pragma: no-cache");
+ pHeaders =
+ curl_slist_append(pHeaders, "Cache-Control: no-cache");
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders);
+
+ if ((res = curl_easy_perform(curl)) != 0)
+ {
+ wio->mDownloadStatus = UPDATE_ERROR;
+ switch (res)
+ {
+ case CURLE_COULDNT_CONNECT:
+ default:
+ std::cerr << "curl error "
+ << static_cast<unsigned>(res) << ": "
+ << wio->mCurlError << " host: "
+ << url.c_str() << std::endl;
+ break;
+ }
+ attempts++;
+ continue;
+ }
+
+ curl_easy_cleanup(curl);
+
+ curl_slist_free_all(pHeaders);
+
+ // It's stored in memory, we're done
+ wio->mDownloadComplete = true;
+ }
+ if (!wio->mAllowUpdate)
+ break;
+ attempts++;
+ }
+
+ if (!wio->mDownloadComplete)
+ wio->mDownloadStatus = UPDATE_ERROR;
+
+// wio->mThread = 0;
+ return 0;
+}
+
+void WhoIsOnline::download()
+{
+ mDownloadComplete = true;
+ if (mThread && SDL_GetThreadID(mThread))
+ SDL_WaitThread(mThread, NULL);
+
+ mDownloadComplete = false;
+ mThread = SDL_CreateThread(WhoIsOnline::downloadThread, this);
+
+ if (mThread == NULL)
+ mDownloadStatus = UPDATE_ERROR;
+}
+
+void WhoIsOnline::logic()
+{
+ // Update Scroll logic
+ mScrollArea->logic();
+
+ if (!mAllowUpdate)
+ return;
+
+ if (mUpdateTimer == 0)
+ mUpdateTimer = cur_time;
+
+ double timeDiff = difftime(cur_time, mUpdateTimer);
+ int timeLimit = isVisible() ? 20 : 120;
+
+ if (mUpdateOnlineList && timeDiff >= timeLimit
+ && mDownloadStatus != UPDATE_LIST)
+ {
+ if (mDownloadComplete == true)
+ {
+ setCaption(_("Who Is Online - Updating"));
+ mUpdateTimer = 0;
+ mDownloadStatus = UPDATE_LIST;
+ download();
+ }
+ }
+
+ switch (mDownloadStatus)
+ {
+ case UPDATE_ERROR:
+ mBrowserBox->clearRows();
+ mBrowserBox->addRow("##1Failed to fetch the online list!");
+ mBrowserBox->addRow(mCurlError);
+ mDownloadStatus = UPDATE_COMPLETE;
+ setCaption(_("Who Is Online - error"));
+ mUpdateButton->setEnabled(true);
+ mUpdateTimer = cur_time + 240;
+ break;
+ case UPDATE_LIST:
+ if (mDownloadComplete == true)
+ {
+ loadList();
+ mDownloadStatus = UPDATE_COMPLETE;
+ mUpdateButton->setEnabled(true);
+ mUpdateTimer = 0;
+ updateSize();
+ if (mOnlinePlayers.size() > 0 && chatWindow)
+ chatWindow->updateOnline(mOnlinePlayers);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void WhoIsOnline::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "update")
+ {
+ if (mDownloadStatus == UPDATE_COMPLETE)
+ {
+ mUpdateTimer = cur_time - 20;
+ if (mUpdateButton)
+ mUpdateButton->setEnabled(false);
+ setCaption(_("Who Is Online - Update"));
+ if (mThread && SDL_GetThreadID(mThread))
+ {
+ SDL_WaitThread(mThread, NULL);
+ mThread = NULL;
+ }
+ mDownloadComplete = true;
+ }
+ }
+}
+
+void WhoIsOnline::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+ updateSize();
+}
+
+void WhoIsOnline::updateSize()
+{
+ if (mDownloadStatus == UPDATE_COMPLETE)
+ {
+ const gcn::Rectangle area = getChildrenArea();
+ if (mUpdateButton)
+ mUpdateButton->setWidth(area.width - 10);
+
+ if (mScrollArea)
+ mScrollArea->setSize(area.width - 10, area.height - 10 - 30);
+ }
+}
+
+const std::string WhoIsOnline::prepareNick(std::string nick, int level,
+ std::string color) const
+{
+ if (mShowLevel && level > 1)
+ {
+ return strprintf("@@%s|##%s%s (%d)@@", nick.c_str(),
+ color.c_str(), nick.c_str(), level);
+ }
+ else
+ {
+ return strprintf("@@%s|##%s%s@@", nick.c_str(),
+ color.c_str(), nick.c_str());
+ }
+}
+
+void WhoIsOnline::optionChanged(const std::string &name)
+{
+ if (name == "updateOnlineList")
+ mUpdateOnlineList = config.getBoolValue("updateOnlineList");
+} \ No newline at end of file
diff --git a/src/gui/whoisonline.h b/src/gui/whoisonline.h
new file mode 100644
index 000000000..d6ac177d6
--- /dev/null
+++ b/src/gui/whoisonline.h
@@ -0,0 +1,139 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _WHOISONLINE_H
+#define _WHOISONLINE_H
+
+#include <string>
+#include <set>
+
+#include "configlistener.h"
+
+#include "gui/widgets/linkhandler.h"
+#include "gui/widgets/window.h"
+
+#include "../utils/mutex.h"
+
+#include <guichan/actionlistener.hpp>
+
+class BrowserBox;
+class ScrollArea;
+
+struct SDL_Thread;
+
+/**
+ * Update progress window GUI
+ *
+ * \ingroup GUI
+ */
+class WhoIsOnline : public Window,
+ public LinkHandler,
+ public gcn::ActionListener,
+ public ConfigListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ WhoIsOnline();
+
+ /**
+ * Destructor
+ */
+ ~WhoIsOnline();
+
+ /**
+ * Loads and display online list from the memory buffer.
+ */
+ void loadList();
+
+ void handleLink(const std::string& link, gcn::MouseEvent *event);
+
+ void logic();
+
+ void action(const gcn::ActionEvent &event);
+
+ void widgetResized(const gcn::Event &event);
+
+ std::set<std::string> &getOnlinePlayers()
+ { return mOnlinePlayers; }
+
+ void setAllowUpdate(bool n)
+ { mAllowUpdate = n; }
+
+ void optionChanged(const std::string &name);
+
+private:
+ void download();
+
+ void updateSize();
+
+ /**
+ * The thread function that download the files.
+ */
+ static int downloadThread(void *ptr);
+
+ /**
+ * A libcurl callback for writing to memory.
+ */
+ static size_t memoryWrite(void *ptr, size_t size, size_t nmemb,
+ FILE *stream);
+
+ const std::string prepareNick(std::string nick, int level,
+ std::string color) const;
+ enum DownloadStatus
+ {
+ UPDATE_ERROR = 0,
+ UPDATE_COMPLETE,
+ UPDATE_LIST
+ };
+
+ /** A thread that use libcurl to download updates. */
+ SDL_Thread *mThread;
+
+ /** Status of the current download. */
+ DownloadStatus mDownloadStatus;
+
+ /** Flag that show if current download is complete. */
+ bool mDownloadComplete;
+
+ /** Byte count currently downloaded in mMemoryBuffer. */
+ int mDownloadedBytes;
+
+ /** Buffer for files downloaded to memory. */
+ char *mMemoryBuffer;
+
+ /** Buffer to handler human readable error provided by curl. */
+ char *mCurlError;
+
+ BrowserBox *mBrowserBox;
+ ScrollArea *mScrollArea;
+ time_t mUpdateTimer;
+ std::set<std::string> mOnlinePlayers;
+
+ gcn::Button *mUpdateButton;
+ bool mAllowUpdate;
+ bool mShowLevel;
+ bool mUpdateOnlineList;
+};
+
+#endif
diff --git a/src/gui/widgets/avatarlistbox.cpp b/src/gui/widgets/avatarlistbox.cpp
new file mode 100644
index 000000000..d665c81ce
--- /dev/null
+++ b/src/gui/widgets/avatarlistbox.cpp
@@ -0,0 +1,346 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/avatarlistbox.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "guild.h"
+#include "localplayer.h"
+
+#include "gui/chat.h"
+#include "gui/gui.h"
+#include "gui/palette.h"
+#include "gui/viewport.h"
+#include "gui/theme.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/stringutils.h"
+
+#include <guichan/font.hpp>
+
+int AvatarListBox::instances = 0;
+Image *AvatarListBox::onlineIcon = 0;
+Image *AvatarListBox::offlineIcon = 0;
+
+AvatarListBox::AvatarListBox(AvatarListModel *model):
+ ListBox(model),
+ mShowGender(false),
+ mShowLevel(false)
+{
+ instances++;
+
+ if (instances == 1)
+ {
+ onlineIcon = Theme::getImageFromTheme("circle-green.png");
+ offlineIcon = Theme::getImageFromTheme("circle-gray.png");
+ }
+
+ setWidth(200);
+
+ mShowGender = config.getBoolValue("showgender");
+ mShowLevel = config.getBoolValue("showlevel");
+
+ config.addListener("showgender", this);
+ config.addListener("showlevel", this);
+}
+
+AvatarListBox::~AvatarListBox()
+{
+ config.removeListener("showgender", this);
+ config.removeListener("showlevel", this);
+
+ instances--;
+
+ if (instances == 0)
+ {
+ if (onlineIcon)
+ onlineIcon->decRef();
+ if (offlineIcon)
+ offlineIcon->decRef();
+ }
+}
+
+void AvatarListBox::draw(gcn::Graphics *gcnGraphics)
+{
+ if (!mListModel || !player_node)
+ return;
+
+ AvatarListModel *model = static_cast<AvatarListModel*>(mListModel);
+// Guild *guild = dynamic_cast<Guild*>(model);
+
+ updateAlpha();
+
+ Graphics *graphics = static_cast<Graphics*>(gcnGraphics);
+
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT,
+ static_cast<int>(mAlpha * 255.0f)));
+ graphics->setFont(getFont());
+
+ const int fontHeight = getFont()->getHeight();
+
+ Widget *parent = getParent();
+
+ const std::string name = player_node->getName();
+
+ // Draw the list elements
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ for (int i = 0, y = 0;
+ i < model->getNumberOfElements();
+ ++i, y += fontHeight)
+ {
+ Avatar *a = model->getAvatarAt(i);
+ if (!a)
+ continue;
+
+ // Draw online status
+ Image *icon = a->getOnline() ? onlineIcon : offlineIcon;
+ if (icon)
+ graphics->drawImage(icon, 2, y + 1);
+
+ if (a->getDisplayBold())
+ graphics->setFont(boldFont);
+
+ std::string text;
+
+ if (a->getMaxHp() > 0)
+ {
+ if (mShowLevel && a->getLevel() > 1)
+ {
+ text = strprintf("%s %d/%d (%d)", a->getComplexName().c_str(),
+ a->getHp(), a->getMaxHp(), a->getLevel());
+ }
+ else
+ {
+ text = strprintf("%s %d/%d", a->getComplexName().c_str(),
+ a->getHp(), a->getMaxHp());
+ }
+ if (parent && a->getMaxHp())
+ {
+ gcn::Color color = Theme::getProgressColor(
+ Theme::PROG_HP, static_cast<float>(a->getHp())
+ / static_cast<float>(a->getMaxHp()));
+ color.a = 80;
+ graphics->setColor(color);
+
+ graphics->fillRectangle(gcn::Rectangle(0, y,
+ parent->getWidth() * a->getHp() / a->getMaxHp(),
+ fontHeight));
+ }
+ }
+ else if (a->getDamageHp() != 0 && a->getName() != name)
+ {
+ if (mShowLevel && a->getLevel() > 1)
+ {
+ text = strprintf("%s -%d (%d)", a->getComplexName().c_str(),
+ a->getDamageHp(), a->getLevel());
+ }
+ else
+ {
+ text = strprintf("%s -%d", a->getComplexName().c_str(),
+ a->getDamageHp());
+ }
+
+ if (parent)
+ {
+// int diff;
+// if (a->getDamageHp() > 1024)
+// diff = 0;
+// else
+// diff = 1024 - a->getDamageHp();
+ gcn::Color color = Theme::getProgressColor(Theme::PROG_HP,
+ 1);
+// 0 / 1024);
+/*
+ if (a->getDamageHp() >= 400)
+ {
+ }
+ else
+ {
+// int intens = 1024/(400 - a->getDamageHp());
+ int intens = a->getDamageHp() / 1024;
+ if (intens > 1)
+ intens = 1;
+ color = Theme::getProgressColor(Theme::PROG_HP,
+ intens);
+ }
+*/
+
+ color.a = 80;
+ graphics->setColor(color);
+ graphics->fillRectangle(gcn::Rectangle(0, y,
+ parent->getWidth() * a->getDamageHp() / 1024,
+ fontHeight));
+
+ if (a->getLevel() > 1)
+ {
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ int minHp = 40 + ((a->getLevel() - 1) * 5);
+ if (minHp < 0)
+ minHp = 40;
+
+ graphics->drawLine(parent->getWidth()*minHp / 1024, y,
+ parent->getWidth() * minHp / 1024, y + fontHeight);
+ }
+ }
+ }
+ else
+ {
+ if (mShowLevel && a->getLevel() > 1)
+ {
+ text = strprintf("%s (%d)", a->getComplexName().c_str(),
+ a->getLevel());
+ }
+ else
+ {
+ text = a->getComplexName();
+ }
+ }
+
+ if (!a->getMap().empty())
+ {
+ if (a->getX() != -1)
+ {
+ text += strprintf(" [%d,%d %s]", a->getX(), a->getY(),
+ a->getMap().c_str());
+ }
+ else
+ {
+ text += strprintf(" [%s]", a->getMap().c_str());
+ }
+ }
+
+ if (mShowGender)
+ {
+ switch (a->getGender())
+ {
+ case GENDER_FEMALE:
+ text += strprintf(" \u2640 %s",
+ a->getAdditionString().c_str());
+ break;
+ case GENDER_MALE:
+ text += strprintf(" \u2642 %s",
+ a->getAdditionString().c_str());
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ text += a->getAdditionString();
+ }
+
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
+ // Draw Name
+ graphics->drawText(text, 15, y);
+
+ if (a->getDisplayBold())
+ graphics->setFont(getFont());
+ }
+
+ setWidth(parent->getWidth() - 10);
+}
+
+void AvatarListBox::mousePressed(gcn::MouseEvent &event)
+{
+ if (!actorSpriteManager || !player_node || !viewport
+ || !getFont()->getHeight())
+ {
+ return;
+ }
+
+ int y = event.getY() / getFont()->getHeight();
+ if (!mListModel || y > mListModel->getNumberOfElements())
+ return;
+
+ setSelected(y);
+ distributeActionEvent();
+ int selected = getSelected();
+ AvatarListModel *model = static_cast<AvatarListModel*>(mListModel);
+ if (!model)
+ return;
+ Avatar *ava = model->getAvatarAt(selected);
+ if (!ava)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (ava->getType() == AVATAR_PLAYER)
+ {
+ Being* being = actorSpriteManager->findBeingByName(ava->getName(),
+ Being::PLAYER);
+ if (being)
+ actorSpriteManager->heal(player_node, being);
+ }
+ else
+ {
+ player_node->navigateTo(ava->getX(), ava->getY());
+ }
+ }
+ else if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (ava->getType() == AVATAR_PLAYER)
+ {
+ Being* being = actorSpriteManager->findBeingByName(
+ model->getAvatarAt(selected)->getName(), Being::PLAYER);
+ if (being)
+ {
+ viewport->showPopup(event.getX(), event.getY(), being);
+ }
+ else
+ {
+ viewport->showPlayerPopup(
+ model->getAvatarAt(selected)->getName());
+ }
+ }
+ else
+ {
+ Map *map = viewport->getMap();
+ Avatar *ava = model->getAvatarAt(selected);
+ if (map && ava)
+ {
+ MapItem *mapItem = map->findPortalXY(ava->getX(), ava->getY());
+ viewport->showPopup(mapItem);
+ }
+ }
+ }
+
+ else if (event.getButton() == gcn::MouseEvent::MIDDLE)
+ {
+ if (ava->getType() == AVATAR_PLAYER && chatWindow)
+ {
+ chatWindow->addWhisperTab(model->getAvatarAt(selected)
+ ->getName(), true);
+ }
+ }
+}
+
+void AvatarListBox::optionChanged(const std::string &value)
+{
+ if (value == "showgender")
+ mShowGender = config.getBoolValue("showgender");
+ else if (value == "showlevel")
+ mShowLevel = config.getBoolValue("showlevel");
+} \ No newline at end of file
diff --git a/src/gui/widgets/avatarlistbox.h b/src/gui/widgets/avatarlistbox.h
new file mode 100644
index 000000000..c7bc11f7c
--- /dev/null
+++ b/src/gui/widgets/avatarlistbox.h
@@ -0,0 +1,70 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_GUILDLISTBOX_H
+#define GUI_GUILDLISTBOX_H
+
+#include "avatar.h"
+
+#include "configlistener.h"
+
+#include "gui/widgets/listbox.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+class Image;
+
+class AvatarListModel : public gcn::ListModel
+{
+public:
+ virtual Avatar *getAvatarAt(int i) = 0;
+
+ std::string getElementAt(int i)
+ { return getAvatarAt(i)->getName(); }
+};
+
+class AvatarListBox : public ListBox, public ConfigListener
+{
+public:
+ AvatarListBox(AvatarListModel *model);
+
+ ~AvatarListBox();
+
+ /**
+ * Draws the list box.
+ */
+ void draw(gcn::Graphics *gcnGraphics);
+
+ void mousePressed(gcn::MouseEvent &event);
+
+ void optionChanged(const std::string &value);
+
+private:
+ bool mShowGender;
+ bool mShowLevel;
+
+ static int instances;
+ static Image *onlineIcon;
+ static Image *offlineIcon;
+};
+
+#endif
diff --git a/src/gui/widgets/battletab.cpp b/src/gui/widgets/battletab.cpp
new file mode 100644
index 000000000..68f1a0453
--- /dev/null
+++ b/src/gui/widgets/battletab.cpp
@@ -0,0 +1,54 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/battletab.h"
+
+#include "chatlog.h"
+#include "commandhandler.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "gui/theme.h"
+
+#include "net/net.h"
+
+#include "resources/iteminfo.h"
+#include "resources/itemdb.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+BattleTab::BattleTab() :
+ ChatTab(_("Battle"))
+{
+ loadFromLogFile("#Battle");
+}
+
+BattleTab::~BattleTab()
+{
+}
+
+void BattleTab::saveToLogFile(std::string &msg)
+{
+ if (chatLogger)
+ chatLogger->log(std::string("#Battle"), std::string(msg));
+}
diff --git a/src/gui/widgets/battletab.h b/src/gui/widgets/battletab.h
new file mode 100644
index 000000000..fdfe626f0
--- /dev/null
+++ b/src/gui/widgets/battletab.h
@@ -0,0 +1,47 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BATTLETAB_H
+#define BATTLETAB_H
+
+#include "gui/widgets/chattab.h"
+
+/**
+ * A tab for a party chat channel.
+ */
+class BattleTab : public ChatTab
+{
+ public:
+ BattleTab();
+
+ ~BattleTab();
+
+ int getType() const
+ { return ChatTab::TAB_BATTLE; }
+
+ void saveToLogFile(std::string &msg);
+};
+
+extern BattleTab *battleChatTab;
+#endif
+
+
+
diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp
new file mode 100644
index 000000000..acb182c3c
--- /dev/null
+++ b/src/gui/widgets/browserbox.cpp
@@ -0,0 +1,534 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/browserbox.h"
+
+#include "client.h"
+#include "log.h"
+
+#include "utils/stringutils.h"
+
+#include "gui/palette.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/linkhandler.h"
+
+#include <guichan/graphics.hpp>
+#include <guichan/font.hpp>
+#include <guichan/cliprectangle.hpp>
+
+#include <algorithm>
+
+BrowserBox::BrowserBox(unsigned int mode, bool opaque):
+ gcn::Widget(),
+ mMode(mode), mHighMode(UNDERLINE | BACKGROUND),
+ mOpaque(opaque),
+ mUseLinksAndUserColors(true),
+ mSelectedLink(-1),
+ mMaxRows(0),
+ mHeight(0),
+ mWidth(0),
+ mYStart(0),
+ mUpdateTime(-1),
+ mAlwaysUpdate(true)
+{
+ setFocusable(true);
+ addMouseListener(this);
+}
+
+BrowserBox::~BrowserBox()
+{
+}
+
+void BrowserBox::setLinkHandler(LinkHandler* linkHandler)
+{
+ mLinkHandler = linkHandler;
+}
+
+void BrowserBox::setOpaque(bool opaque)
+{
+ mOpaque = opaque;
+}
+
+void BrowserBox::setHighlightMode(unsigned int highMode)
+{
+ mHighMode = highMode;
+}
+
+void BrowserBox::addRow(const std::string &row, bool atTop)
+{
+ std::string tmp = row;
+ std::string newRow;
+ std::string::size_type idx1, idx2, idx3;
+ gcn::Font *font = getFont();
+
+ // Use links and user defined colors
+ if (mUseLinksAndUserColors)
+ {
+ BROWSER_LINK bLink;
+
+ // Check for links in format "@@link|Caption@@"
+ idx1 = tmp.find("@@");
+ while (idx1 != std::string::npos)
+ {
+ idx2 = tmp.find("|", idx1);
+ idx3 = tmp.find("@@", idx2);
+
+ if (idx2 == std::string::npos || idx3 == std::string::npos)
+ break;
+ bLink.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2));
+ bLink.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1));
+ bLink.y1 = static_cast<int>(mTextRows.size()) * font->getHeight();
+ bLink.y2 = bLink.y1 + font->getHeight();
+
+ newRow += tmp.substr(0, idx1);
+
+ std::string tmp2 = newRow;
+ idx1 = tmp2.find("##");
+ while (idx1 != std::string::npos)
+ {
+ tmp2.erase(idx1, 3);
+ idx1 = tmp2.find("##");
+ }
+ bLink.x1 = font->getWidth(tmp2) - 1;
+ bLink.x2 = bLink.x1 + font->getWidth(bLink.caption) + 1;
+
+ mLinks.push_back(bLink);
+
+ newRow += "##<" + bLink.caption;
+
+ tmp.erase(0, idx3 + 2);
+ if (!tmp.empty())
+ newRow += "##>";
+
+ idx1 = tmp.find("@@");
+ }
+
+ newRow += tmp;
+ }
+ // Don't use links and user defined colors
+ else
+ {
+ newRow = row;
+ }
+
+ if (atTop)
+ mTextRows.push_front(newRow);
+ else
+ mTextRows.push_back(newRow);
+
+ //discard older rows when a row limit has been set
+ if (mMaxRows > 0)
+ {
+ while (mTextRows.size() > mMaxRows)
+ {
+ mTextRows.pop_front();
+ for (unsigned int i = 0; i < mLinks.size(); i++)
+ {
+ mLinks[i].y1 -= font->getHeight();
+ mLinks[i].y2 -= font->getHeight();
+
+ if (mLinks[i].y1 < 0)
+ mLinks.erase(mLinks.begin() + i);
+ }
+ }
+ }
+
+ // Auto size mode
+ if (mMode == AUTO_SIZE)
+ {
+ std::string plain = newRow;
+ for (idx1 = plain.find("##");
+ idx1 != std::string::npos;
+ idx1 = plain.find("##"))
+ {
+ plain.erase(idx1, 3);
+ }
+
+ // Adjust the BrowserBox size
+ int w = font->getWidth(plain);
+ if (w > getWidth())
+ setWidth(w);
+ }
+
+ if (mMode == AUTO_WRAP)
+ {
+ unsigned int y = 0;
+ unsigned int nextChar;
+ const char *hyphen = "~";
+ int hyphenWidth = font->getWidth(hyphen);
+ int x = 0;
+
+ for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++)
+ {
+ std::string row = *i;
+ for (unsigned int j = 0; j < row.size(); j++)
+ {
+ std::string character = row.substr(j, 1);
+ x += font->getWidth(character);
+ nextChar = j + 1;
+
+ // Wraping between words (at blank spaces)
+ if ((nextChar < row.size()) && (row.at(nextChar) == ' '))
+ {
+ int nextSpacePos = static_cast<int>(
+ row.find(" ", (nextChar + 1)));
+ if (nextSpacePos <= 0)
+ nextSpacePos = static_cast<int>(row.size()) - 1;
+
+ int nextWordWidth = font->getWidth(
+ row.substr(nextChar,
+ (nextSpacePos - nextChar)));
+
+ if ((x + nextWordWidth + 10) > getWidth())
+ {
+ x = 15; // Ident in new line
+ y += 1;
+ j++;
+ }
+ }
+ // Wrapping looong lines (brutal force)
+ else if ((x + 2 * hyphenWidth) > getWidth())
+ {
+ x = 15; // Ident in new line
+ y += 1;
+ }
+ }
+ }
+
+ setHeight(font->getHeight() * (static_cast<int>(
+ mTextRows.size()) + y));
+ }
+ else
+ {
+ setHeight(font->getHeight() * static_cast<int>(mTextRows.size()));
+ }
+ mUpdateTime = 0;
+ updateHeight();
+}
+
+void BrowserBox::clearRows()
+{
+ mTextRows.clear();
+ mLinks.clear();
+ setWidth(0);
+ setHeight(0);
+ mSelectedLink = -1;
+ mUpdateTime = 0;
+ updateHeight();
+}
+
+struct MouseOverLink
+{
+ MouseOverLink(int x, int y) : mX(x), mY(y)
+ { }
+
+ bool operator() (BROWSER_LINK &link)
+ {
+ return (mX >= link.x1 && mX < link.x2 &&
+ mY >= link.y1 && mY < link.y2);
+ }
+ int mX, mY;
+};
+
+void BrowserBox::mousePressed(gcn::MouseEvent &event)
+{
+ if (!mLinkHandler)
+ return;
+
+ LinkIterator i = find_if(mLinks.begin(), mLinks.end(),
+ MouseOverLink(event.getX(), event.getY()));
+
+ if (i != mLinks.end())
+ mLinkHandler->handleLink(i->link, &event);
+}
+
+void BrowserBox::mouseMoved(gcn::MouseEvent &event)
+{
+ LinkIterator i = find_if(mLinks.begin(), mLinks.end(),
+ MouseOverLink(event.getX(), event.getY()));
+
+ mSelectedLink = (i != mLinks.end())
+ ? static_cast<int>(i - mLinks.begin()) : -1;
+}
+
+void BrowserBox::draw(gcn::Graphics *graphics)
+{
+ gcn::ClipRectangle cr = graphics->getCurrentClipArea();
+ mYStart = cr.y - cr.yOffset;
+ int yEnd = mYStart + cr.height;
+ if (mYStart < 0)
+ mYStart = 0;
+
+ if (getWidth() != mWidth)
+ updateHeight();
+
+ if (mOpaque)
+ {
+ graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND));
+ graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
+ }
+
+ if (mSelectedLink >= 0 && mSelectedLink < (signed)mLinks.size())
+ {
+ if ((mHighMode & BACKGROUND))
+ {
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT));
+ graphics->fillRectangle(gcn::Rectangle(
+ mLinks[mSelectedLink].x1,
+ mLinks[mSelectedLink].y1,
+ mLinks[mSelectedLink].x2 - mLinks[mSelectedLink].x1,
+ mLinks[mSelectedLink].y2 - mLinks[mSelectedLink].y1
+ ));
+ }
+
+ if ((mHighMode & UNDERLINE))
+ {
+ graphics->setColor(Theme::getThemeColor(Theme::HYPERLINK));
+ graphics->drawLine(
+ mLinks[mSelectedLink].x1,
+ mLinks[mSelectedLink].y2,
+ mLinks[mSelectedLink].x2,
+ mLinks[mSelectedLink].y2);
+ }
+ }
+
+ gcn::Font *font = getFont();
+
+ for (LinePartIterator i = mLineParts.begin();
+ i != mLineParts.end();
+ i ++)
+ {
+ const LinePart &part = *i;
+ if (part.mY + 50 < mYStart)
+ continue;
+ if (part.mY > yEnd)
+ break;
+ graphics->setColor(part.mColor);
+ font->drawString(graphics, part.mText, part.mX, part.mY);
+ }
+
+ return;
+}
+
+int BrowserBox::calcHeight()
+{
+ int x = 0, y = 0;
+ int wrappedLines = 0;
+ int link = 0;
+ gcn::Font *font = getFont();
+
+ int fontHeight = font->getHeight();
+ int fontWidthMinus = font->getWidth("-");
+ char const *hyphen = "~";
+ int hyphenWidth = font->getWidth(hyphen);
+
+ gcn::Color selColor = Theme::getThemeColor(Theme::TEXT);
+ const gcn::Color textColor = Theme::getThemeColor(Theme::TEXT);
+
+ mLineParts.clear();
+
+ for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++)
+ {
+ const std::string row = *(i);
+ bool wrapped = false;
+ x = 0;
+
+ // Check for separator lines
+ if (row.find("---", 0) == 0)
+ {
+ const int dashWidth = fontWidthMinus;
+ for (x = 0; x < getWidth(); x++)
+ {
+ mLineParts.push_back(LinePart(x, y, selColor, "-"));
+ x += dashWidth - 2;
+ }
+
+ y += fontHeight;
+ continue;
+ }
+
+ gcn::Color prevColor = selColor;
+
+ // TODO: Check if we must take texture size limits into account here
+ // TODO: Check if some of the O(n) calls can be removed
+ for (std::string::size_type start = 0, end = std::string::npos;
+ start != std::string::npos;
+ start = end, end = std::string::npos)
+ {
+ // Wrapped line continuation shall be indented
+ if (wrapped)
+ {
+ y += fontHeight;
+ x = 15;
+ wrapped = false;
+ }
+
+ // "Tokenize" the string at control sequences
+ if (mUseLinksAndUserColors)
+ end = row.find("##", start + 1);
+
+ if (mUseLinksAndUserColors ||
+ (!mUseLinksAndUserColors && (start == 0)))
+ {
+ // Check for color change in format "##x", x = [L,P,0..9]
+ if (row.find("##", start) == start && row.size() > start + 2)
+ {
+ const char c = row.at(start + 2);
+
+ bool valid;
+ const gcn::Color col = Theme::getThemeColor(c, valid);
+
+ if (c == '>')
+ {
+ selColor = prevColor;
+ }
+ else if (c == '<')
+ {
+// link++;
+ prevColor = selColor;
+ selColor = col;
+ }
+ else if (valid)
+ {
+ selColor = col;
+ }
+ else
+ {
+
+ switch (c)
+ {
+ case '1': selColor = RED; break;
+ case '2': selColor = GREEN; break;
+ case '3': selColor = BLUE; break;
+ case '4': selColor = ORANGE; break;
+ case '5': selColor = YELLOW; break;
+ case '6': selColor = PINK; break;
+ case '7': selColor = PURPLE; break;
+ case '8': selColor = GRAY; break;
+ case '9': selColor = BROWN; break;
+ case '0':
+ default:
+ selColor = textColor;
+ }
+ }
+
+ if (c == '<' && link < (signed)mLinks.size())
+ {
+ const int size =
+ font->getWidth(mLinks[link].caption) + 1;
+
+ mLinks[link].x1 = x;
+ mLinks[link].y1 = y;
+ mLinks[link].x2 = mLinks[link].x1 + size;
+ mLinks[link].y2 = y + fontHeight - 1;
+ link++;
+ }
+ start += 3;
+
+ if (start == row.size())
+ break;
+ }
+ }
+
+ std::string::size_type len =
+ end == std::string::npos ? end : end - start;
+
+ if (start >= row.length())
+ break;
+
+ std::string part = row.substr(start, len);
+
+ // Auto wrap mode
+ if (mMode == AUTO_WRAP && getWidth() > 0
+ && font->getWidth(part) > 0
+ && (x + font->getWidth(part) + 10) > getWidth())
+ {
+ bool forced = false;
+
+ /* FIXME: This code layout makes it easy to crash remote
+ clients by talking garbage. Forged long utf-8 characters
+ will cause either a buffer underflow in substr or an
+ infinite loop in the main loop. */
+ do
+ {
+ if (!forced)
+ end = row.rfind(' ', end);
+
+ // Check if we have to (stupidly) force-wrap
+ if (end == std::string::npos || end <= start)
+ {
+ forced = true;
+ end = row.size();
+ x += hyphenWidth; // Account for the wrap-notifier
+ continue;
+ }
+
+ // Skip to the start of the current character
+ while ((row[end] & 192) == 128)
+ end--;
+ end--; // And then to the last byte of the previous one
+
+ part = row.substr(start, end - start + 1);
+ }
+ while (end > start && font->getWidth(part) > 0
+ && (x + font->getWidth(part) + 10) > getWidth());
+
+ if (forced)
+ {
+ x -= hyphenWidth; // Remove the wrap-notifier accounting
+ mLineParts.push_back(LinePart(getWidth() - hyphenWidth,
+ y, selColor, hyphen));
+ end++; // Skip to the next character
+ }
+ else
+ {
+ end += 2; // Skip to after the space
+ }
+
+ wrapped = true;
+ wrappedLines++;
+ }
+
+ mLineParts.push_back(LinePart(x, y, selColor, part.c_str()));
+
+ if (mMode == AUTO_WRAP && font->getWidth(part) == 0)
+ break;
+
+ x += font->getWidth(part);
+ }
+ y += fontHeight;
+ }
+ return (static_cast<int>(mTextRows.size()) + wrappedLines) * fontHeight;
+}
+
+void BrowserBox::updateHeight()
+{
+ if (mAlwaysUpdate || mUpdateTime != cur_time
+ || mTextRows.size() < 3 || !mUpdateTime)
+ {
+ mWidth = getWidth();
+ mHeight = calcHeight();
+ setHeight(mHeight);
+ mUpdateTime = cur_time;
+ }
+}
diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h
new file mode 100644
index 000000000..cd9cc92de
--- /dev/null
+++ b/src/gui/widgets/browserbox.h
@@ -0,0 +1,205 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BROWSERBOX_H
+#define BROWSERBOX_H
+
+#include <guichan/mouselistener.hpp>
+#include <guichan/widget.hpp>
+
+#include <list>
+#include <vector>
+
+class LinkHandler;
+
+struct BROWSER_LINK
+{
+ int x1, x2, y1, y2; /**< Where link is placed */
+ std::string link;
+ std::string caption;
+};
+
+class LinePart
+{
+ public:
+ LinePart(int x, int y, gcn::Color color, std::string text) :
+ mX(x), mY(y), mColor(color), mText(text)
+ {
+ }
+
+ int mX, mY;
+ gcn::Color mColor;
+ std::string mText;
+};
+
+/**
+ * A simple browser box able to handle links and forward events to the
+ * parent conteiner.
+ */
+class BrowserBox : public gcn::Widget,
+ public gcn::MouseListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ BrowserBox(unsigned int mode = AUTO_SIZE, bool opaque = true);
+
+ /**
+ * Destructor.
+ */
+ ~BrowserBox();
+
+ /**
+ * Sets the handler for links.
+ */
+ void setLinkHandler(LinkHandler *linkHandler);
+
+ /**
+ * Sets the BrowserBox opacity.
+ */
+ void setOpaque(bool opaque);
+
+ /**
+ * Sets the Highlight mode for links.
+ */
+ void setHighlightMode(unsigned int highMode);
+
+ /**
+ * Sets the maximum numbers of rows in the browser box. 0 = no limit.
+ */
+ void setMaxRow(unsigned max) {mMaxRows = max; };
+
+ /**
+ * Disable links & user defined colors to be used in chat input.
+ */
+/*
+ void disableLinksAndUserColors();
+*/
+ /**
+ * Adds a text row to the browser.
+ */
+ void addRow(const std::string &row, bool atTop = false);
+
+ /**
+ * Remove all rows.
+ */
+ void clearRows();
+
+// void setSize(int width, int height);
+
+// void widgetResized(const gcn::Event &event);
+
+ /**
+ * Handles mouse actions.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+ void mouseMoved(gcn::MouseEvent &event);
+
+ /**
+ * Draws the browser box.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ void updateHeight();
+
+// void widgetResized(const gcn::Event &event);
+
+ /**
+ * BrowserBox modes.
+ */
+ enum
+ {
+ AUTO_SIZE = 0,
+ AUTO_WRAP /**< Maybe it needs a fix or to be redone. */
+ };
+
+ /**
+ * BrowserBox colors.
+ *
+ * NOTES (by Javila):
+ * - color values is "0x" prefix followed by HTML color style.
+ * - we can add up to 10 different colors: [0..9].
+ * - not all colors will be fine with all backgrounds due transparent
+ * windows and widgets. So, I think it's better keep BrowserBox
+ * opaque (white background) by default.
+ */
+ enum
+ {
+ RED = 0xff0000, /**< Color 1 */
+ GREEN = 0x009000, /**< Color 2 */
+ BLUE = 0x0000ff, /**< Color 3 */
+ ORANGE = 0xe0980e, /**< Color 4 */
+ YELLOW = 0xf1dc27, /**< Color 5 */
+ PINK = 0xff00d8, /**< Color 6 */
+ PURPLE = 0x8415e2, /**< Color 7 */
+ GRAY = 0x919191, /**< Color 8 */
+ BROWN = 0x8e4c17 /**< Color 9 */
+ };
+
+ /**
+ * Highlight modes for links.
+ * This can be used for a bitmask.
+ */
+ enum
+ {
+ UNDERLINE = 1,
+ BACKGROUND = 2
+ };
+
+ typedef std::list<std::string> TextRows;
+
+ TextRows &getRows()
+ { return mTextRows; }
+
+ void setAlwaysUpdate(bool n)
+ { mAlwaysUpdate = n; }
+
+ private:
+ int calcHeight();
+
+ typedef TextRows::iterator TextRowIterator;
+ TextRows mTextRows;
+
+ typedef std::list<LinePart> LinePartList;
+ typedef LinePartList::iterator LinePartIterator;
+ LinePartList mLineParts;
+
+ typedef std::vector<BROWSER_LINK> Links;
+ typedef Links::iterator LinkIterator;
+ Links mLinks;
+
+ LinkHandler *mLinkHandler;
+ unsigned int mMode;
+ unsigned int mHighMode;
+ bool mOpaque;
+ bool mUseLinksAndUserColors;
+ int mSelectedLink;
+ unsigned int mMaxRows;
+ int mHeight;
+ int mWidth;
+ int mYStart;
+ int mUpdateTime;
+ bool mAlwaysUpdate;
+};
+
+#endif
diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp
new file mode 100644
index 000000000..3445928a1
--- /dev/null
+++ b/src/gui/widgets/button.cpp
@@ -0,0 +1,227 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/button.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+
+#include "gui/palette.h"
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+#include <guichan/exception.hpp>
+#include <guichan/font.hpp>
+
+int Button::mInstances = 0;
+float Button::mAlpha = 1.0;
+
+enum
+{
+ BUTTON_STANDARD = 0, // 0
+ BUTTON_HIGHLIGHTED, // 1
+ BUTTON_PRESSED, // 2
+ BUTTON_DISABLED, // 3
+ BUTTON_COUNT // 4 - Must be last.
+};
+
+struct ButtonData
+{
+ char const *file;
+ int gridX;
+ int gridY;
+};
+
+static ButtonData const data[BUTTON_COUNT] =
+{
+ { "button.png", 0, 0 },
+ { "buttonhi.png", 9, 4 },
+ { "buttonpress.png", 16, 19 },
+ { "button_disabled.png", 25, 23 }
+};
+
+ImageRect Button::button[BUTTON_COUNT];
+
+Button::Button():
+ mDescription(""), mClickCount(0)
+{
+ init();
+}
+
+Button::Button(const std::string &caption, const std::string &actionEventId,
+ gcn::ActionListener *listener):
+ gcn::Button(caption),
+ mDescription(""), mClickCount(0)
+{
+ init();
+ setActionEventId(actionEventId);
+
+ if (listener)
+ addActionListener(listener);
+}
+
+void Button::init()
+{
+ setFrameSize(0);
+
+ if (mInstances == 0)
+ {
+ // Load the skin
+ Image *btn[BUTTON_COUNT];
+
+ int a, x, y, mode;
+
+ for (mode = 0; mode < BUTTON_COUNT; mode++)
+ {
+ btn[mode] = Theme::getImageFromTheme(data[mode].file);
+ if (!btn[mode])
+ continue;
+
+ a = 0;
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ button[mode].grid[a] = btn[mode]->getSubImage(
+ data[x].gridX, data[y].gridY,
+ data[x + 1].gridX - data[x].gridX + 1,
+ data[y + 1].gridY - data[y].gridY + 1);
+ a++;
+ }
+ }
+ if (btn[mode])
+ btn[mode]->decRef();
+ }
+ updateAlpha();
+ }
+ mInstances++;
+}
+
+Button::~Button()
+{
+ mInstances--;
+
+ if (mInstances == 0)
+ {
+ for (int mode = 0; mode < BUTTON_COUNT; mode++)
+ {
+ if (button[mode].grid)
+ {
+ for_each(button[mode].grid,
+ button[mode].grid + 9, dtor<Image*>());
+ }
+ }
+ }
+}
+
+void Button::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (mAlpha != alpha)
+ {
+ mAlpha = alpha;
+ for (int a = 0; a < 9; a++)
+ {
+ if (button[BUTTON_DISABLED].grid[a])
+ button[BUTTON_DISABLED].grid[a]->setAlpha(mAlpha);
+ if (button[BUTTON_PRESSED].grid[a])
+ button[BUTTON_PRESSED].grid[a]->setAlpha(mAlpha);
+ if (button[BUTTON_HIGHLIGHTED].grid[a])
+ button[BUTTON_HIGHLIGHTED].grid[a]->setAlpha(mAlpha);
+ if (button[BUTTON_STANDARD].grid[a])
+ button[BUTTON_STANDARD].grid[a]->setAlpha(mAlpha);
+ }
+ }
+}
+
+void Button::draw(gcn::Graphics *graphics)
+{
+ int mode;
+
+ if (!isEnabled())
+ mode = BUTTON_DISABLED;
+ else if (isPressed())
+ mode = BUTTON_PRESSED;
+ else if (mHasMouse || isFocused())
+ mode = BUTTON_HIGHLIGHTED;
+ else
+ mode = BUTTON_STANDARD;
+
+ updateAlpha();
+
+ static_cast<Graphics*>(graphics)->
+ drawImageRect(0, 0, getWidth(), getHeight(), button[mode]);
+
+ if (mode == BUTTON_DISABLED)
+ graphics->setColor(Theme::getThemeColor(Theme::BUTTON_DISABLED));
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::BUTTON));
+
+ int textX;
+ int textY = getHeight() / 2 - getFont()->getHeight() / 2;
+
+ switch (getAlignment())
+ {
+ default:
+ case gcn::Graphics::LEFT:
+ textX = 4;
+ break;
+ case gcn::Graphics::CENTER:
+ textX = getWidth() / 2;
+ break;
+ case gcn::Graphics::RIGHT:
+ textX = getWidth() - 4;
+ break;
+// throw GCN_EXCEPTION("Button::draw. Unknown alignment.");
+ }
+
+ graphics->setFont(getFont());
+
+ if (isPressed())
+ graphics->drawText(getCaption(), textX + 1, textY + 1, getAlignment());
+ else
+ graphics->drawText(getCaption(), textX, textY, getAlignment());
+}
+
+void Button::mouseReleased(gcn::MouseEvent& mouseEvent)
+{
+ if (mouseEvent.getButton() == gcn::MouseEvent::LEFT
+ && mMousePressed && mHasMouse)
+ {
+ mMousePressed = false;
+ mClickCount = mouseEvent.getClickCount();
+ distributeActionEvent();
+ mouseEvent.consume();
+ }
+ else if (mouseEvent.getButton() == gcn::MouseEvent::LEFT)
+ {
+ mMousePressed = false;
+ mClickCount = 0;
+ mouseEvent.consume();
+ }
+}
diff --git a/src/gui/widgets/button.h b/src/gui/widgets/button.h
new file mode 100644
index 000000000..301d02fbe
--- /dev/null
+++ b/src/gui/widgets/button.h
@@ -0,0 +1,94 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include <guichan/widgets/button.hpp>
+#include <guichan/mouseevent.hpp>
+
+class ImageRect;
+
+/**
+ * Button widget. Same as the Guichan button but with custom look.
+ *
+ * \ingroup GUI
+ */
+class Button : public gcn::Button
+{
+ public:
+ /**
+ * Default constructor.
+ */
+ Button();
+
+ /**
+ * Constructor, sets the caption of the button to the given string and
+ * adds the given action listener.
+ */
+ Button(const std::string &caption, const std::string &actionEventId,
+ gcn::ActionListener *listener);
+
+ /**
+ * Destructor.
+ */
+ ~Button();
+
+ /**
+ * Draws the button.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Update the alpha value to the button components.
+ */
+ void updateAlpha();
+
+ virtual void mouseReleased(gcn::MouseEvent& mouseEvent);
+
+ void setDescription(std::string text)
+ { mDescription = text; }
+
+ std::string getDescription()
+ { return mDescription; }
+
+ unsigned getClickCount()
+ { return mClickCount; }
+
+ void setTag(int tag)
+ { mTag = tag; }
+
+ int getTag()
+ { return mTag; }
+
+ private:
+ void init();
+
+ static ImageRect button[4]; /**< Button state graphics */
+ static int mInstances; /**< Number of button instances */
+ static float mAlpha;
+
+ std::string mDescription;
+ unsigned mClickCount;
+ int mTag;
+};
+
+#endif
diff --git a/src/gui/widgets/channeltab.cpp b/src/gui/widgets/channeltab.cpp
new file mode 100644
index 000000000..a7370a4c5
--- /dev/null
+++ b/src/gui/widgets/channeltab.cpp
@@ -0,0 +1,132 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "channeltab.h"
+
+#include "channel.h"
+
+#include "net/chathandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+
+ChannelTab::ChannelTab(Channel *channel) :
+ ChatTab(channel->getName()),
+ mChannel(channel)
+{
+ channel->setTab(this);
+}
+
+ChannelTab::~ChannelTab()
+{
+}
+
+void ChannelTab::handleInput(const std::string &msg)
+{
+ Net::getChatHandler()->sendToChannel(getChannel()->getId(), msg);
+}
+
+void ChannelTab::showHelp()
+{
+ chatLog(_("/users > Lists the users in the current channel"));
+ chatLog(_("/topic > Set the topic of the current channel"));
+ chatLog(_("/quit > Leave a channel"));
+ chatLog(_("/op > Make a user a channel operator"));
+ chatLog(_("/kick > Kick a user from the channel"));
+}
+
+bool ChannelTab::handleCommand(const std::string &type,
+ const std::string &args)
+{
+ if (type == "help")
+ {
+ if (args == "users")
+ {
+ chatLog(_("Command: /users"));
+ chatLog(_("This command shows the users in this channel."));
+ }
+ else if (args == "topic")
+ {
+ chatLog(_("Command: /topic <message>"));
+ chatLog(_("This command sets the topic to <message>."));
+ }
+ else if (args == "quit")
+ {
+ chatLog(_("Command: /quit"));
+ chatLog(_("This command leaves the current channel."));
+ chatLog(_("If you're the last person in the channel, "
+ "it will be deleted."));
+ }
+ else if (args == "op")
+ {
+ chatLog(_("Command: /op <nick>"));
+ chatLog(_("This command makes <nick> a channel operator."));
+ chatLog(_("If the <nick> has spaces in it, enclose it in "
+ "double quotes (\")."));
+ chatLog(_("Channel operators can kick and op other users "
+ "from the channel."));
+ }
+ else if (args == "kick")
+ {
+ chatLog(_("Command: /kick <nick>"));
+ chatLog(_("This command makes <nick> leave the channel."));
+ chatLog(_("If the <nick> has spaces in it, enclose it in "
+ "double quotes (\")."));
+ }
+ else
+ return false;
+ }
+ else if (type == "users")
+ {
+ Net::getChatHandler()->userList(mChannel->getName());
+ }
+ else if (type == "topic")
+ {
+ Net::getChatHandler()->setChannelTopic(mChannel->getId(), args);
+ }
+ else if (type == "topic")
+ {
+ Net::getChatHandler()->setChannelTopic(mChannel->getId(), args);
+ }
+ else if (type == "quit")
+ {
+ Net::getChatHandler()->quitChannel(mChannel->getId());
+ }
+ else if (type == "op")
+ {
+ // set the user mode 'o' to op a user
+ if (args != "")
+ Net::getChatHandler()->setUserMode(mChannel->getId(), args, 'o');
+ else
+ chatLog(_("Need a user to op!"), BY_CHANNEL);
+ }
+ else if (type == "kick")
+ {
+ if (args != "")
+ Net::getChatHandler()->kickUser(mChannel->getId(), args);
+ else
+ chatLog(_("Need a user to kick!"), BY_CHANNEL);
+ }
+ else
+ return false;
+
+ return true;
+}
diff --git a/src/gui/widgets/channeltab.h b/src/gui/widgets/channeltab.h
new file mode 100644
index 000000000..842b80f7d
--- /dev/null
+++ b/src/gui/widgets/channeltab.h
@@ -0,0 +1,62 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHANNELTAB_H
+#define CHANNELTAB_H
+
+#include "chattab.h"
+
+class Channel;
+
+/**
+ * A tab for a chat channel.
+ */
+class ChannelTab : public ChatTab
+{
+ public:
+
+ Channel *getChannel() const { return mChannel; }
+
+ void showHelp();
+
+ bool handleCommand(const std::string &type,
+ const std::string &args);
+
+ protected:
+ friend class Channel;
+
+ /**
+ * Constructor.
+ */
+ ChannelTab(Channel *channel);
+
+ /**
+ * Destructor.
+ */
+ ~ChannelTab();
+
+ void handleInput(const std::string &msg);
+
+ private:
+ Channel *mChannel;
+};
+
+#endif // CHANNELTAB_H
diff --git a/src/gui/widgets/chattab.cpp b/src/gui/widgets/chattab.cpp
new file mode 100644
index 000000000..06ba3d3ed
--- /dev/null
+++ b/src/gui/widgets/chattab.cpp
@@ -0,0 +1,431 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/chattab.h"
+
+#include "actorspritemanager.h"
+#include "chatlog.h"
+#include "commandhandler.h"
+#include "configuration.h"
+#include "localplayer.h"
+#include "log.h"
+#include "sound.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/itemlinkhandler.h"
+#include "gui/widgets/tradetab.h"
+
+#include "net/chathandler.h"
+#include "net/net.h"
+
+#include "resources/iteminfo.h"
+#include "resources/itemdb.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include <guichan/widgets/tabbedarea.hpp>
+
+#define MAX_WORD_SIZE 50
+
+ChatTab::ChatTab(const std::string &name) :
+ Tab(),
+ mAllowHightlight(true)
+{
+ setCaption(name);
+
+ mTextOutput = new BrowserBox(BrowserBox::AUTO_WRAP);
+ mTextOutput->setOpaque(false);
+ mTextOutput->setMaxRow((int) config.getIntValue("ChatLogLength"));
+ if (chatWindow)
+ mTextOutput->setLinkHandler(chatWindow->mItemLinkHandler);
+ mTextOutput->setAlwaysUpdate(false);
+
+ mScrollArea = new ScrollArea(mTextOutput);
+ mScrollArea->setScrollPolicy(gcn::ScrollArea::SHOW_NEVER,
+ gcn::ScrollArea::SHOW_ALWAYS);
+ mScrollArea->setScrollAmount(0, 1);
+ mScrollArea->setOpaque(false);
+
+ if (chatWindow)
+ chatWindow->addTab(this);
+}
+
+ChatTab::~ChatTab()
+{
+ if (chatWindow)
+ chatWindow->removeTab(this);
+
+ delete mTextOutput;
+ mTextOutput = 0;
+ delete mScrollArea;
+ mScrollArea = 0;
+}
+
+void ChatTab::chatLog(std::string line, Own own,
+ bool ignoreRecord, bool tryRemoveColors)
+{
+ // Trim whitespace
+ trim(line);
+
+ if (line.empty())
+ return;
+
+ if (tryRemoveColors && own == BY_OTHER &&
+ config.getBoolValue("removeColors"))
+ {
+ line = removeColors(line);
+ if (line.empty())
+ return;
+ }
+
+ unsigned lineLim = config.getIntValue("chatMaxCharLimit");
+ if (lineLim > 0 && line.length() > lineLim)
+ line = line.substr(0, lineLim);
+
+ if (line.empty())
+ return;
+
+ CHATLOG tmp;
+ tmp.own = own;
+ tmp.nick = "";
+ tmp.text = line;
+
+ std::string::size_type pos = line.find(" : ");
+ if (pos != std::string::npos)
+ {
+ if (line.length() <= pos + 3)
+ return;
+
+ tmp.nick = line.substr(0, pos);
+ tmp.text = line.substr(pos + 3);
+ }
+ else
+ {
+ // Fix the owner of welcome message.
+ if (line.length() > 7 && line.substr(0, 7) == "Welcome")
+ own = BY_SERVER;
+ }
+
+ // *implements actions in a backwards compatible way*
+ if ((own == BY_PLAYER || own == BY_OTHER) &&
+ tmp.text.at(0) == '*' &&
+ tmp.text.at(tmp.text.length()-1) == '*')
+ {
+ tmp.text[0] = ' ';
+ tmp.text.erase(tmp.text.length() - 1);
+ own = ACT_IS;
+ }
+
+ std::string lineColor = "##C";
+ switch (own)
+ {
+ case BY_GM:
+ if (tmp.nick.empty())
+ {
+ tmp.nick = std::string(_("Global announcement:"));
+ tmp.nick += " ";
+ lineColor = "##G";
+ }
+ else
+ {
+ tmp.nick = strprintf(_("Global announcement from %s:"),
+ tmp.nick.c_str());
+ tmp.nick += " ";
+ lineColor = "##1"; // Equiv. to BrowserBox::RED
+ }
+ break;
+ case BY_PLAYER:
+ tmp.nick += ": ";
+ lineColor = "##Y";
+ break;
+ case BY_OTHER:
+ tmp.nick += ": ";
+ lineColor = "##C";
+ break;
+ case BY_SERVER:
+ tmp.nick = _("Server:");
+ tmp.nick += " ";
+ tmp.text = line;
+ lineColor = "##S";
+ break;
+ case BY_CHANNEL:
+ tmp.nick = "";
+ // TODO: Use a predefined color
+ lineColor = "##2"; // Equiv. to BrowserBox::GREEN
+ break;
+ case ACT_WHISPER:
+ tmp.nick = strprintf(_("%s whispers: %s"), tmp.nick.c_str(), "");
+ lineColor = "##W";
+ break;
+ case ACT_IS:
+ lineColor = "##I";
+ break;
+ case BY_LOGGER:
+ tmp.nick = "";
+ tmp.text = line;
+ lineColor = "##L";
+ break;
+ default:
+ logger->log1("ChatTab::chatLog incorrect value in switch");
+ break;
+ }
+
+ if (tmp.nick == ": ")
+ {
+ tmp.nick = "";
+ lineColor = "##S";
+ }
+
+ // if configured, move magic messages log to debug chat tab
+ if (localChatTab && this == localChatTab
+ && ((config.getBoolValue("showMagicInDebug")
+ && own == BY_PLAYER && tmp.text.length() > 1
+ && tmp.text.length() > 1 && tmp.text.at(0) == '#'
+ && tmp.text.at(1) != '#')
+ || (config.getBoolValue("serverMsgInDebug") && (own == BY_SERVER
+ || tmp.nick.empty()))))
+ {
+ if (debugChatTab)
+ debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+ return;
+ }
+
+ // Get the current system time
+ time_t t;
+ time(&t);
+
+ // Format the time string properly
+ std::stringstream timeStr;
+ timeStr << "[" << ((((t / 60) / 60) % 24 < 10) ? "0" : "")
+ << static_cast<int>(((t / 60) / 60) % 24)
+ << ":" << (((t / 60) % 60 < 10) ? "0" : "")
+ << static_cast<int>((t / 60) % 60)
+ << "] ";
+
+ line = lineColor + timeStr.str() + tmp.nick + tmp.text;
+
+ if (config.getBoolValue("enableChatLog"))
+ saveToLogFile(line);
+
+ mTextOutput->setMaxRow(config.getIntValue("chatMaxLinesLimit"));
+
+ // We look if the Vertical Scroll Bar is set at the max before
+ // adding a row, otherwise the max will always be a row higher
+ // at comparison.
+ if (mScrollArea->getVerticalScrollAmount() >=
+ mScrollArea->getVerticalMaxScroll())
+ {
+ addRow(line);
+ mScrollArea->setVerticalScrollAmount(
+ mScrollArea->getVerticalMaxScroll());
+ }
+ else
+ {
+ addRow(line);
+ }
+
+ mScrollArea->logic();
+ if (own != BY_PLAYER)
+ {
+ if (own == BY_SERVER && (getType() == TAB_PARTY
+ || getType() == TAB_GUILD))
+ {
+ return;
+ }
+
+ if (!getTabbedArea())
+ return;
+
+ if (this != getTabbedArea()->getSelectedTab())
+ {
+ if (getFlash() == 0)
+ {
+ if (player_node)
+ {
+ std::string::size_type pos
+ = tmp.text.find(player_node->getName());
+ if (pos != std::string::npos)
+ setFlash(2);
+ else
+ setFlash(1);
+ }
+ else
+ {
+ setFlash(1);
+ }
+ }
+ }
+
+ if (getAllowHighlight() && (this != getTabbedArea()->getSelectedTab()
+ || (Client::getIsMinimized() || (!Client::getMouseFocused()
+ && !Client::getInputFocused()))))
+ {
+ if (own != BY_SERVER)
+ sound.playGuiSfx("system/newmessage.ogg");
+ }
+ }
+}
+
+void ChatTab::chatLog(const std::string &nick, std::string msg)
+{
+ Own byWho = (nick == player_node->getName() ? BY_PLAYER : BY_OTHER);
+ if (byWho == BY_OTHER && config.getBoolValue("removeColors"))
+ msg = removeColors(msg);
+ chatLog(nick + " : " + msg, byWho, false, false);
+}
+
+void ChatTab::chatInput(const std::string &message)
+{
+ std::string msg = message;
+ trim(msg);
+
+ if (msg.empty())
+ return;
+
+ // Check for item link
+ std::string::size_type start = msg.find('[');
+ while (start + 1 < msg.size() && start != std::string::npos
+ && msg[start + 1] != '@')
+ {
+ std::string::size_type end = msg.find(']', start);
+ if (start + 1 != end && end != std::string::npos)
+ {
+ // Catch multiple embeds and ignore them
+ // so it doesn't crash the client.
+ while ((msg.find('[', start + 1) != std::string::npos) &&
+ (msg.find('[', start + 1) < end))
+ {
+ start = msg.find('[', start + 1);
+ }
+
+ std::string temp = "";
+ if (start + 1 < msg.length() && end < msg.length()
+ && end > start + 1)
+ {
+ temp = msg.substr(start + 1, end - start - 1);
+
+ const ItemInfo itemInfo = ItemDB::get(temp);
+ if (itemInfo.getId() != 0)
+ {
+ msg.insert(end, "@@");
+ msg.insert(start + 1, "|");
+ msg.insert(start + 1, toString(itemInfo.getId()));
+ msg.insert(start + 1, "@@");
+ }
+ }
+ }
+ start = msg.find('[', start + 1);
+ }
+
+ // Prepare ordinary message
+ if (msg[0] != '/')
+ handleInput(msg);
+ else
+ handleCommand(std::string(msg, 1));
+}
+
+void ChatTab::scroll(int amount)
+{
+ int range = mScrollArea->getHeight() / 8 * amount;
+ gcn::Rectangle scr;
+ scr.y = mScrollArea->getVerticalScrollAmount() + range;
+ scr.height = abs(range);
+ mTextOutput->showPart(scr);
+}
+
+void ChatTab::clearText()
+{
+ mTextOutput->clearRows();
+}
+
+void ChatTab::handleInput(const std::string &msg)
+{
+ if (chatWindow)
+ Net::getChatHandler()->talk(chatWindow->doReplace(msg));
+ else
+ Net::getChatHandler()->talk(msg);
+}
+
+void ChatTab::handleCommand(const std::string &msg)
+{
+ if (commandHandler)
+ commandHandler->handleCommands(msg, this);
+}
+
+bool ChatTab::handleCommands(const std::string &type, const std::string &args)
+{
+ // need split to commands and call each
+
+ return handleCommand(type, args);
+}
+
+void ChatTab::saveToLogFile(std::string &msg)
+{
+ if (getType() == TAB_INPUT && chatLogger)
+ chatLogger->log(msg);
+}
+
+int ChatTab::getType() const
+{
+ if (getCaption() == "General" || getCaption() == _("General"))
+ return TAB_INPUT;
+ else if (getCaption() == "Debug" || getCaption() == _("Debug"))
+ return TAB_DEBUG;
+ else
+ return TAB_UNKNOWN;
+}
+
+void ChatTab::addRow(std::string &line)
+{
+ std::string::size_type idx = 0;
+
+ for (unsigned int f = 0; f < line.length(); f++)
+ {
+ if (line.at(f) == ' ')
+ {
+ idx = f;
+ }
+ else if (f - idx > MAX_WORD_SIZE)
+ {
+ line.insert(f, " ");
+ idx = f;
+ }
+ }
+ mTextOutput->addRow(line);
+}
+
+void ChatTab::loadFromLogFile(std::string name)
+{
+ if (chatLogger)
+ {
+ std::list<std::string> list;
+ chatLogger->loadLast(name, list, 5);
+ std::list<std::string>::iterator i = list.begin();
+ while (i != list.end())
+ {
+ std::string line = "##9" + *i;
+ addRow(line);
+ ++i;
+ }
+ }
+}
diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h
new file mode 100644
index 000000000..ddc36d29c
--- /dev/null
+++ b/src/gui/widgets/chattab.h
@@ -0,0 +1,173 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHATTAB_H
+#define CHATTAB_H
+
+#include "gui/chat.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/tab.h"
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class ScrollArea;
+
+/**
+ * A tab for the chat window. This is special to ease chat handling.
+ */
+class ChatTab : public Tab
+{
+ public:
+ enum Type
+ {
+ TAB_UNKNOWN = 0,
+ TAB_INPUT,
+ TAB_WHISPER,
+ TAB_PARTY,
+ TAB_GUILD,
+ TAB_DEBUG,
+ TAB_TRADE,
+ TAB_BATTLE
+ };
+
+ /**
+ * Constructor.
+ */
+ ChatTab(const std::string &name);
+ ~ChatTab();
+
+ /**
+ * Adds a line of text to our message list. Parameters:
+ *
+ * @param line Text message.
+ * @param own Type of message (usually the owner-type).
+ * @param channelName which channel to send the message to.
+ * @param ignoreRecord should this not be recorded?
+ * @param removeColors try remove color if configured
+ */
+ void chatLog(std::string line, Own own = BY_SERVER,
+ bool ignoreRecord = false, bool tryRemoveColors = true);
+
+ /**
+ * Adds the text to the message list
+ *
+ * @param msg The message text which is to be sent.
+ */
+ void chatLog(const std::string &nick, std::string msg);
+
+ /**
+ * Determines whether the message is a command or message, then
+ * sends the given message to the game server to be said, or to the
+ * command handler
+ *
+ * @param msg The message text which is to be sent.
+ */
+ void chatInput(const std::string &msg);
+
+ /**
+ * Scrolls the chat window
+ *
+ * @param amount direction and amount to scroll. Negative numbers scroll
+ * up, positive numbers scroll down. The absolute amount indicates the
+ * amount of 1/8ths of chat window real estate that should be scrolled.
+ */
+ void scroll(int amount);
+
+ /**
+ * Clears the text from the tab
+ */
+ void clearText();
+
+ /**
+ * Add any extra help text to the output. Allows tabs to define help
+ * for commands defined by the tab itself.
+ */
+ virtual void showHelp() {}
+
+ /**
+ * Handle special commands. Allows a tab to handle commands it
+ * defines itself.
+ *
+ * @returns true if the command was handled
+ * false if the command was not handled
+ */
+ virtual bool handleCommand(const std::string &type _UNUSED_,
+ const std::string &args _UNUSED_)
+ { return false; }
+
+ /**
+ * Handle special commands. Allows a tab to handle commands it
+ * defines itself.
+ *
+ * @returns true if the command was handled
+ * false if the command was not handled
+ */
+ virtual bool handleCommands(const std::string &type,
+ const std::string &args);
+
+ /**
+ * Returns type of the being.
+ */
+ virtual int getType() const;
+
+ virtual void saveToLogFile(std::string &msg);
+
+ std::list<std::string> &getRows()
+ { return mTextOutput->getRows(); }
+
+ void loadFromLogFile(std::string name);
+
+ bool getAllowHighlight()
+ { return mAllowHightlight; }
+
+ void setAllowHighlight(bool n)
+ { mAllowHightlight = n; }
+
+ protected:
+ friend class ChatWindow;
+ friend class WhisperWindow;
+
+ virtual void setCurrent()
+ { setFlash(false); }
+
+ virtual void handleInput(const std::string &msg);
+
+ virtual void handleCommand(const std::string &msg);
+
+ virtual void getAutoCompleteList(std::vector<std::string>&) const
+ {}
+
+ void addRow(std::string &line);
+
+ ScrollArea *mScrollArea;
+ BrowserBox *mTextOutput;
+ bool mAllowHightlight;
+};
+
+extern ChatTab *localChatTab;
+extern ChatTab *debugChatTab;
+
+#endif // CHATTAB_H
diff --git a/src/gui/widgets/checkbox.cpp b/src/gui/widgets/checkbox.cpp
new file mode 100644
index 000000000..01331ddba
--- /dev/null
+++ b/src/gui/widgets/checkbox.cpp
@@ -0,0 +1,187 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/checkbox.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+
+#include "gui/palette.h"
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+#include <guichan/actionlistener.hpp>
+
+int CheckBox::instances = 0;
+float CheckBox::mAlpha = 1.0;
+Image *CheckBox::checkBoxNormal;
+Image *CheckBox::checkBoxChecked;
+Image *CheckBox::checkBoxDisabled;
+Image *CheckBox::checkBoxDisabledChecked;
+Image *CheckBox::checkBoxNormalHi;
+Image *CheckBox::checkBoxCheckedHi;
+
+CheckBox::CheckBox(const std::string &caption, bool selected,
+ gcn::ActionListener* listener, std::string eventId):
+ gcn::CheckBox(caption, selected),
+ mHasMouse(false)
+{
+ if (instances == 0)
+ {
+ Image *checkBox = Theme::getImageFromTheme("checkbox.png");
+ if (checkBox)
+ {
+ checkBoxNormal = checkBox->getSubImage(0, 0, 9, 10);
+ checkBoxChecked = checkBox->getSubImage(9, 0, 9, 10);
+ checkBoxDisabled = checkBox->getSubImage(18, 0, 9, 10);
+ checkBoxDisabledChecked = checkBox->getSubImage(27, 0, 9, 10);
+ checkBoxNormalHi = checkBox->getSubImage(36, 0, 9, 10);
+ checkBoxCheckedHi = checkBox->getSubImage(45, 0, 9, 10);
+ checkBoxNormal->setAlpha(mAlpha);
+ checkBoxChecked->setAlpha(mAlpha);
+ checkBoxDisabled->setAlpha(mAlpha);
+ checkBoxDisabledChecked->setAlpha(mAlpha);
+ checkBoxNormalHi->setAlpha(mAlpha);
+ checkBoxCheckedHi->setAlpha(mAlpha);
+ checkBox->decRef();
+ }
+ else
+ {
+ checkBoxNormal = 0;
+ checkBoxChecked = 0;
+ checkBoxDisabled = 0;
+ checkBoxDisabledChecked = 0;
+ checkBoxNormalHi = 0;
+ checkBoxCheckedHi = 0;
+ }
+ }
+
+ instances++;
+
+ if (!eventId.empty())
+ setActionEventId(eventId);
+
+ if (listener)
+ addActionListener(listener);
+}
+
+CheckBox::~CheckBox()
+{
+ instances--;
+
+ if (instances == 0)
+ {
+ delete checkBoxNormal;
+ checkBoxNormal = 0;
+ delete checkBoxChecked;
+ checkBoxChecked = 0;
+ delete checkBoxDisabled;
+ checkBoxDisabled = 0;
+ delete checkBoxDisabledChecked;
+ checkBoxDisabledChecked = 0;
+ delete checkBoxNormalHi;
+ checkBoxNormalHi = 0;
+ delete checkBoxCheckedHi;
+ checkBoxCheckedHi = 0;
+ }
+}
+
+void CheckBox::draw(gcn::Graphics* graphics)
+{
+ drawBox(graphics);
+
+ graphics->setFont(getFont());
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
+ const int h = getHeight() + getHeight() / 2;
+
+ graphics->drawText(getCaption(), h - 2, 0);
+}
+
+void CheckBox::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (mAlpha != alpha)
+ {
+ mAlpha = alpha;
+ if (checkBoxNormal)
+ checkBoxNormal->setAlpha(mAlpha);
+ if (checkBoxChecked)
+ checkBoxChecked->setAlpha(mAlpha);
+ if (checkBoxDisabled)
+ checkBoxDisabled->setAlpha(mAlpha);
+ if (checkBoxDisabledChecked)
+ checkBoxDisabledChecked->setAlpha(mAlpha);
+ if (checkBoxNormal)
+ checkBoxNormal->setAlpha(mAlpha);
+ if (checkBoxCheckedHi)
+ checkBoxCheckedHi->setAlpha(mAlpha);
+ }
+}
+
+void CheckBox::drawBox(gcn::Graphics* graphics)
+{
+ Image *box;
+
+ if (isEnabled())
+ {
+ if (isSelected())
+ {
+ if (mHasMouse)
+ box = checkBoxCheckedHi;
+ else
+ box = checkBoxChecked;
+ }
+ else
+ {
+ if (mHasMouse)
+ box = checkBoxNormalHi;
+ else
+ box = checkBoxNormal;
+ }
+ }
+ else
+ {
+ if (isSelected())
+ box = checkBoxDisabledChecked;
+ else
+ box = checkBoxDisabled;
+ }
+
+ updateAlpha();
+
+ if (box)
+ static_cast<Graphics*>(graphics)->drawImage(box, 2, 2);
+}
+
+void CheckBox::mouseEntered(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = true;
+}
+
+void CheckBox::mouseExited(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = false;
+}
diff --git a/src/gui/widgets/checkbox.h b/src/gui/widgets/checkbox.h
new file mode 100644
index 000000000..b885e8922
--- /dev/null
+++ b/src/gui/widgets/checkbox.h
@@ -0,0 +1,92 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHECKBOX_H
+#define CHECKBOX_H
+
+#include <guichan/widgets/checkbox.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+
+/**
+ * Check box widget. Same as the Guichan check box but with custom look.
+ *
+ * \ingroup GUI
+ */
+class CheckBox : public gcn::CheckBox
+{
+ public:
+ /**
+ * Constructor.
+ */
+ CheckBox(const std::string &caption, bool selected = false,
+ gcn::ActionListener* listener = NULL,
+ std::string eventId = "");
+
+ /**
+ * Destructor.
+ */
+ ~CheckBox();
+
+ /**
+ * Draws the caption, then calls drawBox to draw the check box.
+ */
+ void draw(gcn::Graphics* graphics);
+
+ /**
+ * Update the alpha value to the checkbox components.
+ */
+ void updateAlpha();
+
+ /**
+ * Draws the check box, not the caption.
+ */
+ void drawBox(gcn::Graphics* graphics);
+
+ /**
+ * Called when the mouse enteres the widget area.
+ */
+ void mouseEntered(gcn::MouseEvent& event);
+
+ /**
+ * Called when the mouse leaves the widget area.
+ */
+ void mouseExited(gcn::MouseEvent& event);
+
+ private:
+ static int instances;
+ static float mAlpha;
+ bool mHasMouse;
+ static Image *checkBoxNormal;
+ static Image *checkBoxChecked;
+ static Image *checkBoxDisabled;
+ static Image *checkBoxDisabledChecked;
+ static Image *checkBoxNormalHi;
+ static Image *checkBoxCheckedHi;
+};
+
+#endif
diff --git a/src/gui/widgets/container.cpp b/src/gui/widgets/container.cpp
new file mode 100644
index 000000000..b788b0610
--- /dev/null
+++ b/src/gui/widgets/container.cpp
@@ -0,0 +1,33 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/container.h"
+
+Container::Container()
+{
+ setOpaque(false);
+}
+
+Container::~Container()
+{
+ while (!mWidgets.empty())
+ delete mWidgets.front();
+}
diff --git a/src/gui/widgets/container.h b/src/gui/widgets/container.h
new file mode 100644
index 000000000..c2696a65f
--- /dev/null
+++ b/src/gui/widgets/container.h
@@ -0,0 +1,43 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_CONTAINER_H
+#define GUI_CONTAINER_H
+
+#include <guichan/widgets/container.hpp>
+
+/**
+ * A widget container.
+ *
+ * The main difference between the standard Guichan container and this one is
+ * that childs added to this container are automatically deleted when the
+ * container is deleted.
+ *
+ * This container is also non-opaque by default.
+ */
+class Container : public gcn::Container
+{
+ public:
+ Container();
+ ~Container();
+};
+
+#endif
diff --git a/src/gui/widgets/desktop.cpp b/src/gui/widgets/desktop.cpp
new file mode 100644
index 000000000..fa5b1698a
--- /dev/null
+++ b/src/gui/widgets/desktop.cpp
@@ -0,0 +1,157 @@
+/*
+ * Desktop widget
+ * Copyright (c) 2009-2010 The Mana World Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/desktop.h"
+
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+#include "main.h"
+
+#include "gui/palette.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/label.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+#include "resources/wallpaper.h"
+
+#include "utils/stringutils.h"
+
+Desktop::Desktop()
+ : mWallpaper(0)
+{
+ addWidgetListener(this);
+
+ Wallpaper::loadWallpapers();
+
+ std::string appName = branding.getValue("appName", std::string(""));
+
+ if (appName.empty())
+ mVersionLabel = new Label(FULL_VERSION);
+ else
+ mVersionLabel = new Label(strprintf("%s (Mana %s)", appName.c_str(),
+ FULL_VERSION));
+
+ mVersionLabel->setBackgroundColor(gcn::Color(255, 255, 255, 128));
+ add(mVersionLabel, 25, 2);
+}
+
+Desktop::~Desktop()
+{
+ if (mWallpaper)
+ mWallpaper->decRef();
+}
+
+void Desktop::reloadWallpaper()
+{
+ Wallpaper::loadWallpapers();
+ setBestFittingWallpaper();
+}
+
+void Desktop::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ setBestFittingWallpaper();
+}
+
+void Desktop::draw(gcn::Graphics *graphics)
+{
+ Graphics *g = static_cast<Graphics *>(graphics);
+
+ if (!mWallpaper || (getWidth() > mWallpaper->getWidth() ||
+ getHeight() > mWallpaper->getHeight()))
+ {
+ // TODO: Color from palette
+ g->setColor(gcn::Color(64, 64, 64));
+ g->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
+ }
+
+ if (mWallpaper)
+ {
+ if (!mWallpaper->useOpenGL())
+ {
+ g->drawImage(mWallpaper,
+ (getWidth() - mWallpaper->getWidth()) / 2,
+ (getHeight() - mWallpaper->getHeight()) / 2);
+ }
+ else
+ {
+ g->drawRescaledImage(mWallpaper, 0, 0, 0, 0,
+ mWallpaper->getWidth(), mWallpaper->getHeight(),
+ getWidth(), getHeight(), false);
+ }
+ }
+
+ // Draw a thin border under the application version...
+ g->setColor(gcn::Color(255, 255, 255, 128));
+ g->fillRectangle(gcn::Rectangle(mVersionLabel->getDimension()));
+
+ Container::draw(graphics);
+}
+
+void Desktop::setBestFittingWallpaper()
+{
+ if (!config.getBoolValue("showBackground"))
+ return;
+
+ const std::string wallpaperName =
+ Wallpaper::getWallpaper(getWidth(), getHeight());
+
+ Image *nWallPaper = Theme::getImageFromTheme(wallpaperName);
+
+ if (nWallPaper)
+ {
+ if (mWallpaper)
+ mWallpaper->decRef();
+
+ if (!nWallPaper->useOpenGL()
+ && (nWallPaper->getWidth() != getWidth()
+ || nWallPaper->getHeight() != getHeight()))
+ {
+ // We rescale to obtain a fullscreen wallpaper...
+ Image *newRsclWlPpr = nWallPaper->SDLgetScaledImage(
+ getWidth(), getHeight());
+ std::string idPath = nWallPaper->getIdPath();
+
+ // We replace the resource in the resource manager
+ nWallPaper->decRef();
+ if (newRsclWlPpr)
+ {
+ ResourceManager::getInstance()->addResource(
+ idPath, newRsclWlPpr);
+
+ mWallpaper = newRsclWlPpr;
+ }
+ else
+ {
+ mWallpaper = nWallPaper;
+ }
+ }
+ else
+ {
+ mWallpaper = nWallPaper;
+ }
+ }
+ else
+ {
+ logger->log("Couldn't load %s as wallpaper", wallpaperName.c_str());
+ }
+}
diff --git a/src/gui/widgets/desktop.h b/src/gui/widgets/desktop.h
new file mode 100644
index 000000000..83568c66f
--- /dev/null
+++ b/src/gui/widgets/desktop.h
@@ -0,0 +1,73 @@
+/*
+ * Desktop widget
+ * Copyright (c) 2009-2010 The Mana World Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DESKTOP_H
+#define DESKTOP_H
+
+#include "guichanfwd.h"
+
+#include "gui/widgets/container.h"
+
+#include <guichan/widgetlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+
+/**
+ * Desktop widget, for drawing a background image and color.
+ *
+ * It picks the best fitting background image. If the image doesn't fit, a
+ * background color is drawn and the image is centered.
+ *
+ * When the desktop widget is resized, the background image is automatically
+ * updated.
+ *
+ * The desktop also displays the client version in the top-right corner.
+ *
+ * \ingroup GUI
+ */
+class Desktop : public Container, gcn::WidgetListener
+{
+ public:
+ Desktop();
+ ~Desktop();
+
+ /**
+ * Has to be called after updates have been loaded.
+ */
+ void reloadWallpaper();
+
+ void widgetResized(const gcn::Event &event);
+
+ void draw(gcn::Graphics *graphics);
+
+ private:
+ void setBestFittingWallpaper();
+
+ Image *mWallpaper;
+ gcn::Label *mVersionLabel;
+};
+
+#endif // DESKTOP_H
diff --git a/src/gui/widgets/dropdown.cpp b/src/gui/widgets/dropdown.cpp
new file mode 100644
index 000000000..b8616643b
--- /dev/null
+++ b/src/gui/widgets/dropdown.cpp
@@ -0,0 +1,303 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2006-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/dropdown.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+
+#include "gui/palette.h"
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+#include <algorithm>
+
+int DropDown::instances = 0;
+Image *DropDown::buttons[2][2];
+ImageRect DropDown::skin;
+float DropDown::mAlpha = 1.0;
+
+DropDown::DropDown(gcn::ListModel *listModel):
+ gcn::DropDown::DropDown(listModel,
+ new ScrollArea,
+ new ListBox(listModel))
+{
+ setFrameSize(2);
+
+ // Initialize graphics
+ if (instances == 0)
+ {
+ // Load the background skin
+
+ // Get the button skin
+ buttons[1][0] = Theme::getImageFromTheme("vscroll_up_default.png");
+ buttons[0][0] = Theme::getImageFromTheme("vscroll_down_default.png");
+ buttons[1][1] = Theme::getImageFromTheme("vscroll_up_pressed.png");
+ buttons[0][1] = Theme::getImageFromTheme("vscroll_down_pressed.png");
+
+ if (buttons[0][0])
+ buttons[0][0]->setAlpha(mAlpha);
+ if (buttons[0][1])
+ buttons[0][1]->setAlpha(mAlpha);
+ if (buttons[1][0])
+ buttons[1][0]->setAlpha(mAlpha);
+ if (buttons[1][1])
+ buttons[1][1]->setAlpha(mAlpha);
+
+ // get the border skin
+ Image *boxBorder = Theme::getImageFromTheme("deepbox.png");
+ if (boxBorder)
+ {
+ int gridx[4] = {0, 3, 28, 31};
+ int gridy[4] = {0, 3, 28, 31};
+ int a = 0, x, y;
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ skin.grid[a] = boxBorder->getSubImage(gridx[x], gridy[y],
+ gridx[x + 1] -
+ gridx[x] + 1,
+ gridy[y + 1] -
+ gridy[y] + 1);
+ if (skin.grid[a])
+ skin.grid[a]->setAlpha(mAlpha);
+ a++;
+ }
+ }
+
+ boxBorder->decRef();
+ }
+ }
+
+ instances++;
+}
+
+DropDown::~DropDown()
+{
+ instances--;
+ // Free images memory
+ if (instances == 0)
+ {
+ if (buttons[0][0])
+ buttons[0][0]->decRef();
+ if (buttons[0][1])
+ buttons[0][1]->decRef();
+ if (buttons[1][0])
+ buttons[1][0]->decRef();
+ if (buttons[1][1])
+ buttons[1][1]->decRef();
+
+ for_each(skin.grid, skin.grid + 9, dtor<Image*>());
+ }
+
+ delete mScrollArea;
+ mScrollArea = 0;
+}
+
+void DropDown::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (mAlpha != alpha)
+ {
+ mAlpha = alpha;
+
+ if (buttons[0][0])
+ buttons[0][0]->setAlpha(mAlpha);
+ if (buttons[0][1])
+ buttons[0][1]->setAlpha(mAlpha);
+ if (buttons[1][0])
+ buttons[1][0]->setAlpha(mAlpha);
+ if (buttons[1][1])
+ buttons[1][1]->setAlpha(mAlpha);
+
+ for (int a = 0; a < 9; a++)
+ {
+ if (skin.grid[a])
+ skin.grid[a]->setAlpha(mAlpha);
+ }
+ }
+}
+
+void DropDown::draw(gcn::Graphics* graphics)
+{
+ int h;
+
+ if (mDroppedDown)
+ h = mFoldedUpHeight;
+ else
+ h = getHeight();
+
+ updateAlpha();
+
+ const int alpha = static_cast<int>(mAlpha * 255.0f);
+ gcn::Color faceColor = getBaseColor();
+ faceColor.a = alpha;
+ const gcn::Color *highlightColor = &Theme::getThemeColor(Theme::HIGHLIGHT,
+ alpha);
+ gcn::Color shadowColor = faceColor - 0x303030;
+ shadowColor.a = alpha;
+
+ if (mListBox->getListModel() && mListBox->getSelected() >= 0)
+ {
+ graphics->setFont(getFont());
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ graphics->drawText(mListBox->getListModel()->getElementAt(
+ mListBox->getSelected()), 1, 0);
+ }
+
+ if (isFocused())
+ {
+ if (highlightColor)
+ graphics->setColor(*highlightColor);
+ graphics->drawRectangle(gcn::Rectangle(0, 0, getWidth() - h, h));
+ }
+
+ drawButton(graphics);
+
+ if (mDroppedDown)
+ {
+ drawChildren(graphics);
+
+ // Draw two lines separating the ListBox with selected
+ // element view.
+ if (highlightColor)
+ graphics->setColor(*highlightColor);
+ graphics->drawLine(0, h, getWidth(), h);
+ graphics->setColor(shadowColor);
+ graphics->drawLine(0, h + 1, getWidth(), h + 1);
+ }
+}
+
+void DropDown::drawFrame(gcn::Graphics *graphics)
+{
+ const int bs = getFrameSize();
+ const int w = getWidth() + bs * 2;
+ const int h = getHeight() + bs * 2;
+
+ static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, skin);
+}
+
+void DropDown::drawButton(gcn::Graphics *graphics)
+{
+ int height = mDroppedDown ? mFoldedUpHeight : getHeight();
+
+ if (buttons[mDroppedDown][mPushed])
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImage(buttons[mDroppedDown][mPushed],
+ getWidth() - height + 2, 1);
+ }
+}
+
+// -- KeyListener notifications
+void DropDown::keyPressed(gcn::KeyEvent& keyEvent)
+{
+ if (keyEvent.isConsumed())
+ return;
+
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE)
+ dropDown();
+ else if (key.getValue() == Key::UP)
+ setSelected(getSelected() - 1);
+ else if (key.getValue() == Key::DOWN)
+ setSelected(getSelected() + 1);
+ else if (key.getValue() == Key::HOME)
+ setSelected(0);
+ else if (key.getValue() == Key::END && mListBox->getListModel())
+ setSelected(mListBox->getListModel()->getNumberOfElements() - 1);
+ else
+ return;
+
+ keyEvent.consume();
+}
+
+void DropDown::focusLost(const gcn::Event& event)
+{
+ gcn::DropDown::focusLost(event);
+ releaseModalMouseInputFocus();
+}
+
+void DropDown::mousePressed(gcn::MouseEvent& mouseEvent)
+{
+ gcn::DropDown::mousePressed(mouseEvent);
+
+ if (0 <= mouseEvent.getY() && mouseEvent.getY() < getHeight() &&
+ mouseEvent.getX() >= 0 && mouseEvent.getX() < getWidth() &&
+ mouseEvent.getButton() == gcn::MouseEvent::LEFT && mDroppedDown &&
+ mouseEvent.getSource() == mListBox)
+ {
+ mPushed = false;
+ foldUp();
+ releaseModalMouseInputFocus();
+ distributeActionEvent();
+ }
+}
+
+void DropDown::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent)
+{
+ setSelected(getSelected() - 1);
+ mouseEvent.consume();
+}
+
+void DropDown::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent)
+{
+ setSelected(getSelected() + 1);
+ mouseEvent.consume();
+}
+
+void DropDown::setSelectedString(std::string str)
+{
+ gcn::ListModel *listModel = mListBox->getListModel();
+ if (!listModel)
+ return;
+
+ for (int f = 0; f < listModel->getNumberOfElements(); f ++)
+ {
+ if (listModel->getElementAt(f) == str)
+ {
+ setSelected(f);
+ break;
+ }
+ }
+}
+
+std::string DropDown::getSelectedString() const
+{
+ gcn::ListModel *listModel = mListBox->getListModel();
+ if (!listModel)
+ return "";
+
+ return listModel->getElementAt(getSelected());
+}
diff --git a/src/gui/widgets/dropdown.h b/src/gui/widgets/dropdown.h
new file mode 100644
index 000000000..f6e347b2b
--- /dev/null
+++ b/src/gui/widgets/dropdown.h
@@ -0,0 +1,97 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2006-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DROPDOWN_H
+#define DROPDOWN_H
+
+#include <guichan/widgets/dropdown.hpp>
+
+class Image;
+class ImageRect;
+
+/**
+ * A drop down box from which you can select different values.
+ *
+ * A ListModel provides the contents of the drop down. To be able to use
+ * DropDown you must give DropDown an implemented ListModel which represents
+ * your list.
+ */
+class DropDown : public gcn::DropDown
+{
+ public:
+ /**
+ * Contructor.
+ *
+ * @param listModel the ListModel to use.
+ * @param scrollArea the ScrollArea to use.
+ * @param listBox the listBox to use.
+ * @see ListModel, ScrollArea, ListBox.
+ */
+ DropDown(gcn::ListModel *listModel = 0);
+
+ ~DropDown();
+
+ /**
+ * Update the alpha value to the graphic components.
+ */
+ void updateAlpha();
+
+ void draw(gcn::Graphics *graphics);
+
+ void drawFrame(gcn::Graphics *graphics);
+
+ // Inherited from FocusListener
+
+ void focusLost(const gcn::Event& event);
+
+ // Inherited from KeyListener
+
+ void keyPressed(gcn::KeyEvent& keyEvent);
+
+ // Inherited from MouseListener
+
+ void mousePressed(gcn::MouseEvent& mouseEvent);
+
+ void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent);
+
+ void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent);
+
+ void setSelectedString(std::string str);
+
+ std::string getSelectedString() const;
+
+ protected:
+ /**
+ * Draws the button with the little down arrow.
+ *
+ * @param graphics a Graphics object to draw with.
+ */
+ void drawButton(gcn::Graphics *graphics);
+
+ // Add own Images.
+ static int instances;
+ static Image *buttons[2][2];
+ static ImageRect skin;
+ static float mAlpha;
+};
+
+#endif // end DROPDOWN_H
+
diff --git a/src/gui/widgets/dropshortcutcontainer.cpp b/src/gui/widgets/dropshortcutcontainer.cpp
new file mode 100644
index 000000000..c3aaed829
--- /dev/null
+++ b/src/gui/widgets/dropshortcutcontainer.cpp
@@ -0,0 +1,303 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/widgets/dropshortcutcontainer.h"
+
+#include "gui/inventorywindow.h"
+#include "gui/itempopup.h"
+#include "gui/palette.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "configuration.h"
+#include "dropshortcut.h"
+#include "graphics.h"
+#include "inventory.h"
+#include "item.h"
+#include "keyboardconfig.h"
+#include "localplayer.h"
+#include "playerinfo.h"
+
+#include "resources/image.h"
+#include "resources/iteminfo.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/stringutils.h"
+
+DropShortcutContainer::DropShortcutContainer():
+ ShortcutContainer(),
+ mItemClicked(false),
+ mItemMoved(NULL)
+{
+ addMouseListener(this);
+ addWidgetListener(this);
+
+ mItemPopup = new ItemPopup;
+
+ mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png");
+ if (dropShortcut)
+ mMaxItems = dropShortcut->getItemCount();
+ else
+ mMaxItems = 0;
+
+ if (mBackgroundImg)
+ {
+ mBackgroundImg->setAlpha(Client::getGuiAlpha());
+ mBoxHeight = mBackgroundImg->getHeight();
+ mBoxWidth = mBackgroundImg->getWidth();
+ }
+ else
+ {
+ mBoxHeight = 1;
+ mBoxWidth = 1;
+ }
+}
+
+DropShortcutContainer::~DropShortcutContainer()
+{
+ if (mBackgroundImg)
+ mBackgroundImg->decRef();
+ delete mItemPopup;
+ mItemPopup = 0;
+}
+
+void DropShortcutContainer::draw(gcn::Graphics *graphics)
+{
+ if (!dropShortcut)
+ return;
+
+ if (Client::getGuiAlpha() != mAlpha)
+ {
+ mAlpha = Client::getGuiAlpha();
+ if (mBackgroundImg)
+ mBackgroundImg->setAlpha(mAlpha);
+ }
+
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ graphics->setFont(getFont());
+
+ for (unsigned i = 0; i < mMaxItems; i++)
+ {
+ const int itemX = (i % mGridWidth) * mBoxWidth;
+ const int itemY = (i / mGridWidth) * mBoxHeight;
+
+ if (mBackgroundImg)
+ g->drawImage(mBackgroundImg, itemX, itemY);
+
+/* // Draw item keyboard shortcut.
+ const char *key = SDL_GetKeyName(
+ (SDLKey) keyboard.getKeyValue(keyboard.KEY_SHORTCUT_1 + i));
+ graphics->setColor(guiPalette->getColor(Palette::TEXT));
+ g->drawText(key, itemX + 2, itemY + 2, gcn::Graphics::LEFT);
+*/
+ if (dropShortcut->getItem(i) < 0)
+ continue;
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ Item *item = inv->findItem(dropShortcut->getItem(i));
+
+ if (item)
+ {
+ // Draw item icon.
+ Image* image = item->getImage();
+
+ if (image)
+ {
+ std::string caption;
+ if (item->getQuantity() > 1)
+ caption = toString(item->getQuantity());
+ else if (item->isEquipped())
+ caption = "Eq.";
+
+ image->setAlpha(1.0f);
+ g->drawImage(image, itemX, itemY);
+ if (item->isEquipped())
+ g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED));
+ else
+ g->setColor(Theme::getThemeColor(Theme::TEXT));
+ g->drawText(caption, itemX + mBoxWidth / 2,
+ itemY + mBoxHeight - 14, gcn::Graphics::CENTER);
+ }
+ }
+ }
+
+ if (mItemMoved)
+ {
+ // Draw the item image being dragged by the cursor.
+ Image* image = mItemMoved->getImage();
+ if (image)
+ {
+ const int tPosX = mCursorPosX - (image->getWidth() / 2);
+ const int tPosY = mCursorPosY - (image->getHeight() / 2);
+
+ g->drawImage(image, tPosX, tPosY);
+ g->drawText(toString(mItemMoved->getQuantity()),
+ tPosX + mBoxWidth / 2, tPosY + mBoxHeight - 14,
+ gcn::Graphics::CENTER);
+ }
+ }
+}
+
+void DropShortcutContainer::mouseDragged(gcn::MouseEvent &event)
+{
+ if (!dropShortcut)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (!mItemMoved && mItemClicked)
+ {
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ const int itemId = dropShortcut->getItem(index);
+
+ if (itemId < 0)
+ return;
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ Item *item = inv->findItem(itemId);
+
+ if (item)
+ {
+ mItemMoved = item;
+ dropShortcut->removeItem(index);
+ }
+ }
+ if (mItemMoved)
+ {
+ mCursorPosX = event.getX();
+ mCursorPosY = event.getY();
+ }
+ }
+}
+
+void DropShortcutContainer::mousePressed(gcn::MouseEvent &event)
+{
+ if (!dropShortcut || !inventoryWindow)
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ // Stores the selected item if theirs one.
+ if (dropShortcut->isItemSelected() && inventoryWindow->isVisible())
+ {
+ dropShortcut->setItem(index);
+ dropShortcut->setItemSelected(-1);
+ }
+ else if (dropShortcut->getItem(index))
+ {
+ mItemClicked = true;
+ }
+ }
+ else if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ Item *item = inv->findItem(dropShortcut->getItem(index));
+
+ if (viewport)
+ viewport->showDropPopup(item);
+ }
+}
+
+void DropShortcutContainer::mouseReleased(gcn::MouseEvent &event)
+{
+ if (!dropShortcut)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (dropShortcut->isItemSelected())
+ dropShortcut->setItemSelected(-1);
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+ if (index == -1)
+ {
+ mItemMoved = NULL;
+ return;
+ }
+ if (mItemMoved)
+ {
+ dropShortcut->setItems(index, mItemMoved->getId());
+ mItemMoved = NULL;
+ }
+
+ if (mItemClicked)
+ mItemClicked = false;
+ }
+}
+
+// Show ItemTooltip
+void DropShortcutContainer::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!dropShortcut)
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ const int itemId = dropShortcut->getItem(index);
+
+ if (itemId < 0)
+ return;
+
+ Inventory *inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ Item *item = inv->findItem(itemId);
+
+ if (item && viewport)
+ {
+ mItemPopup->setItem(item);
+ mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
+ }
+ else
+ {
+ mItemPopup->setVisible(false);
+ }
+}
+
+// Hide ItemTooltip
+void DropShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ mItemPopup->setVisible(false);
+}
diff --git a/src/gui/widgets/dropshortcutcontainer.h b/src/gui/widgets/dropshortcutcontainer.h
new file mode 100644
index 000000000..c072f0613
--- /dev/null
+++ b/src/gui/widgets/dropshortcutcontainer.h
@@ -0,0 +1,88 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef DROPSHORTCUTCONTAINER_H
+#define DROPSHORTCUTCONTAINER_H
+
+#include <guichan/mouselistener.hpp>
+
+#include "gui/widgets/shortcutcontainer.h"
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+class Item;
+class ItemPopup;
+
+/**
+ * An item shortcut container. Used to quickly use items.
+ *
+ * \ingroup GUI
+ */
+class DropShortcutContainer : public ShortcutContainer
+{
+ public:
+ /**
+ * Constructor. Initializes the graphic.
+ */
+ DropShortcutContainer();
+
+ /**
+ * Destructor.
+ */
+ virtual ~DropShortcutContainer();
+
+ /**
+ * Draws the items.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Handles mouse when dragged.
+ */
+ void mouseDragged(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse when pressed.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse release.
+ */
+ void mouseReleased(gcn::MouseEvent &event);
+
+ private:
+ void mouseExited(gcn::MouseEvent &event);
+ void mouseMoved(gcn::MouseEvent &event);
+
+ bool mItemClicked;
+ Item *mItemMoved;
+
+ ItemPopup *mItemPopup;
+};
+
+#endif
diff --git a/src/gui/widgets/emoteshortcutcontainer.cpp b/src/gui/widgets/emoteshortcutcontainer.cpp
new file mode 100644
index 000000000..a9e435540
--- /dev/null
+++ b/src/gui/widgets/emoteshortcutcontainer.cpp
@@ -0,0 +1,259 @@
+/*
+ * Extended support for activating emotes
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/emoteshortcutcontainer.h"
+
+#include "animatedsprite.h"
+#include "configuration.h"
+#include "emoteshortcut.h"
+#include "graphics.h"
+#include "inventory.h"
+#include "item.h"
+#include "itemshortcut.h"
+#include "keyboardconfig.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "gui/palette.h"
+#include "gui/textpopup.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "resources/emotedb.h"
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+static const int MAX_ITEMS = 42;
+
+EmoteShortcutContainer::EmoteShortcutContainer():
+ ShortcutContainer(),
+ mEmoteClicked(false),
+ mEmoteMoved(0),
+ mEmotePopup(new TextPopup)
+{
+ addMouseListener(this);
+ addWidgetListener(this);
+
+ mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png");
+
+ if (mBackgroundImg)
+ mBackgroundImg->setAlpha(Client::getGuiAlpha());
+
+ // Setup emote sprites
+ for (int i = 0; i <= EmoteDB::getLast(); i++)
+ {
+ const EmoteSprite* sprite = EmoteDB::getSprite(i, true);
+ if (sprite && sprite->sprite)
+ mEmoteImg.push_back(sprite);
+ }
+
+// mMaxItems = EmoteDB::getLast() < MAX_ITEMS ? EmoteDB::getLast() : MAX_ITEMS;
+ mMaxItems = MAX_ITEMS;
+
+ if (mBackgroundImg)
+ {
+ mBoxHeight = mBackgroundImg->getHeight();
+ mBoxWidth = mBackgroundImg->getWidth();
+ }
+ else
+ {
+ mBoxHeight = 1;
+ mBoxWidth = 1;
+ }
+}
+
+EmoteShortcutContainer::~EmoteShortcutContainer()
+{
+ delete mEmotePopup;
+
+ if (mBackgroundImg)
+ mBackgroundImg->decRef();
+}
+
+void EmoteShortcutContainer::draw(gcn::Graphics *graphics)
+{
+ if (!emoteShortcut)
+ return;
+
+ mAlpha = Client::getGuiAlpha();
+ if (Client::getGuiAlpha() != mAlpha && mBackgroundImg)
+ mBackgroundImg->setAlpha(mAlpha);
+
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ graphics->setFont(getFont());
+
+ for (unsigned i = 0; i < mMaxItems; i++)
+ {
+ const int emoteX = (i % mGridWidth) * mBoxWidth;
+ const int emoteY = (i / mGridWidth) * mBoxHeight;
+
+ if (mBackgroundImg)
+ g->drawImage(mBackgroundImg, emoteX, emoteY);
+
+ // Draw emote keyboard shortcut.
+ std::string key = keyboard.getKeyValueString(
+ keyboard.KEY_EMOTE_1 + i);
+
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ g->drawText(key, emoteX + 2, emoteY + 2, gcn::Graphics::LEFT);
+
+/*
+ if (emoteShortcut->getEmote(i)
+ && static_cast<unsigned>(emoteShortcut->getEmote(i)) - 1
+ < mEmoteImg.size()
+ && mEmoteImg[emoteShortcut->getEmote(i) - 1])
+ {
+ mEmoteImg[emoteShortcut->getEmote(i) - 1]->draw(g, emoteX + 2,
+ emoteY + 10);
+ }
+*/
+
+ if (i < mEmoteImg.size() && mEmoteImg[i] && mEmoteImg[i]->sprite)
+ mEmoteImg[i]->sprite->draw(g, emoteX + 2, emoteY + 10);
+ }
+
+ if (mEmoteMoved && mEmoteMoved < (unsigned)mEmoteImg.size() + 1
+ && mEmoteMoved > 0)
+ {
+ // Draw the emote image being dragged by the cursor.
+ const EmoteSprite* sprite = mEmoteImg[mEmoteMoved - 1];
+ if (sprite && sprite->sprite)
+ {
+ const AnimatedSprite *spr = sprite->sprite;
+ const int tPosX = mCursorPosX - (spr->getWidth() / 2);
+ const int tPosY = mCursorPosY - (spr->getHeight() / 2);
+
+ spr->draw(g, tPosX, tPosY);
+ }
+ }
+}
+
+void EmoteShortcutContainer::mouseDragged(gcn::MouseEvent &event)
+{
+ if (!emoteShortcut)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (!mEmoteMoved && mEmoteClicked)
+ {
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+// const unsigned char emoteId = emoteShortcut->getEmote(index);
+ const unsigned char emoteId = index + 1;
+
+ if (emoteId)
+ {
+ mEmoteMoved = emoteId;
+ emoteShortcut->removeEmote(index);
+ }
+ }
+ if (mEmoteMoved)
+ {
+ mCursorPosX = event.getX();
+ mCursorPosY = event.getY();
+ }
+ }
+}
+
+void EmoteShortcutContainer::mousePressed(gcn::MouseEvent &event)
+{
+ if (!emoteShortcut)
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ // Stores the selected emote if there is one.
+ if (emoteShortcut->isEmoteSelected())
+ {
+ emoteShortcut->setEmote(index);
+ emoteShortcut->setEmoteSelected(0);
+ }
+ else if (emoteShortcut->getEmote(index))
+ {
+ mEmoteClicked = true;
+ }
+}
+
+void EmoteShortcutContainer::mouseReleased(gcn::MouseEvent &event)
+{
+ if (!emoteShortcut)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (emoteShortcut->isEmoteSelected())
+ emoteShortcut->setEmoteSelected(0);
+
+ if (index == -1)
+ {
+ mEmoteMoved = 0;
+ return;
+ }
+
+ if (mEmoteMoved)
+ {
+ emoteShortcut->setEmotes(index, mEmoteMoved);
+ mEmoteMoved = 0;
+ }
+ else if (emoteShortcut->getEmote(index) && mEmoteClicked)
+ {
+ emoteShortcut->useEmote(index + 1);
+ }
+
+ if (mEmoteClicked)
+ mEmoteClicked = false;
+ }
+}
+
+void EmoteShortcutContainer::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!emoteShortcut || !mEmotePopup)
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ mEmotePopup->setVisible(false);
+
+ if ((unsigned)index < mEmoteImg.size() && mEmoteImg[index])
+ {
+ mEmotePopup->show(viewport->getMouseX(), viewport->getMouseY(),
+ mEmoteImg[index]->name);
+ }
+}
+
+void EmoteShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ if (mEmotePopup)
+ mEmotePopup->setVisible(false);
+} \ No newline at end of file
diff --git a/src/gui/widgets/emoteshortcutcontainer.h b/src/gui/widgets/emoteshortcutcontainer.h
new file mode 100644
index 000000000..e841e6dfb
--- /dev/null
+++ b/src/gui/widgets/emoteshortcutcontainer.h
@@ -0,0 +1,84 @@
+/*
+ * Extended support for activating emotes
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EMOTESHORTCUTCONTAINER_H
+#define EMOTESHORTCUTCONTAINER_H
+
+#include "gui/widgets/shortcutcontainer.h"
+
+#include "resources/emotedb.h"
+
+#include <vector>
+
+class AnimatedSprite;
+class Image;
+class TextPopup;
+
+/**
+ * An emote shortcut container. Used to quickly use emoticons.
+ *
+ * \ingroup GUI
+ */
+class EmoteShortcutContainer : public ShortcutContainer
+{
+ public:
+ /**
+ * Constructor. Initializes the graphic.
+ */
+ EmoteShortcutContainer();
+
+ /**
+ * Destructor.
+ */
+ virtual ~EmoteShortcutContainer();
+
+ /**
+ * Draws the items.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Handles mouse when dragged.
+ */
+ void mouseDragged(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse when pressed.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse release.
+ */
+ void mouseReleased(gcn::MouseEvent &event);
+
+ void mouseMoved(gcn::MouseEvent &event);
+
+ void mouseExited(gcn::MouseEvent &event);
+
+ private:
+ std::vector<const EmoteSprite*> mEmoteImg;
+
+ bool mEmoteClicked;
+ unsigned char mEmoteMoved;
+ TextPopup *mEmotePopup;
+};
+
+#endif
diff --git a/src/gui/widgets/flowcontainer.cpp b/src/gui/widgets/flowcontainer.cpp
new file mode 100644
index 000000000..a93818abc
--- /dev/null
+++ b/src/gui/widgets/flowcontainer.cpp
@@ -0,0 +1,88 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "flowcontainer.h"
+
+FlowContainer::FlowContainer(int boxWidth, int boxHeight):
+ mBoxWidth(boxWidth), mBoxHeight(boxHeight),
+ mGridWidth(1), mGridHeight(1)
+{
+ addWidgetListener(this);
+ if (!mBoxWidth)
+ mBoxWidth = 1;
+ if (!mBoxHeight)
+ mBoxHeight = 1;
+}
+
+void FlowContainer::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ if (getWidth() < mBoxWidth)
+ {
+ setWidth(mBoxWidth);
+ return;
+ }
+
+ int itemCount = static_cast<int>(mWidgets.size());
+
+ if (!mBoxWidth)
+ mGridWidth = getWidth();
+ else
+ mGridWidth = getWidth() / mBoxWidth;
+
+ if (mGridWidth < 1)
+ mGridWidth = 1;
+
+ mGridHeight = itemCount / mGridWidth;
+
+ if (itemCount % mGridWidth != 0 || mGridHeight < 1)
+ ++mGridHeight;
+
+ int height = mGridHeight * mBoxHeight;
+
+ if (getHeight() != height)
+ {
+ setHeight(height);
+ return;
+ }
+
+ int i = 0;
+ height = 0;
+ for (WidgetList::iterator it = mWidgets.begin();
+ it != mWidgets.end(); it++)
+ {
+ int x = i % mGridWidth * mBoxWidth;
+ (*it)->setPosition(x, height);
+
+ i++;
+
+ if (i % mGridWidth == 0)
+ height += mBoxHeight;
+ }
+}
+
+void FlowContainer::add(gcn::Widget *widget)
+{
+ if (!widget)
+ return;
+
+ Container::add(widget);
+ widget->setSize(mBoxWidth, mBoxHeight);
+ widgetResized(NULL);
+}
diff --git a/src/gui/widgets/flowcontainer.h b/src/gui/widgets/flowcontainer.h
new file mode 100644
index 000000000..f738be209
--- /dev/null
+++ b/src/gui/widgets/flowcontainer.h
@@ -0,0 +1,73 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FLOWCONTAINER_H
+#define FLOWCONTAINER_H
+
+#include "container.h"
+
+#include <guichan/widgetlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+/**
+ * A container that arranges its contents like words on a page.
+ *
+ * \ingroup GUI
+ */
+class FlowContainer : public Container,
+ public gcn::WidgetListener
+{
+ public:
+ /**
+ * Constructor. Initializes the shortcut container.
+ */
+ FlowContainer(int boxWidth, int boxHeight);
+
+ /**
+ * Destructor.
+ */
+ ~FlowContainer() {}
+
+ /**
+ * Invoked when a widget changes its size. This is used to determine
+ * the new height of the container.
+ */
+ void widgetResized(const gcn::Event &event);
+
+ int getBoxWidth() const
+ { return mBoxWidth; }
+
+ int getBoxHeight() const
+ { return mBoxHeight; }
+
+ void add(gcn::Widget *widget);
+
+ private:
+ int mBoxWidth;
+ int mBoxHeight;
+ int mGridWidth, mGridHeight;
+};
+
+#endif
diff --git a/src/gui/widgets/icon.cpp b/src/gui/widgets/icon.cpp
new file mode 100644
index 000000000..4e5902128
--- /dev/null
+++ b/src/gui/widgets/icon.cpp
@@ -0,0 +1,60 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/icon.h"
+
+#include "graphics.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+Icon::Icon(const std::string &file)
+ : mImage(0)
+{
+ mImage = ResourceManager::getInstance()->getImage(file);
+ if (mImage)
+ setSize(mImage->getWidth(), mImage->getHeight());
+}
+
+Icon::Icon(Image *image)
+ : mImage(image)
+{
+ if (mImage)
+ setSize(mImage->getWidth(), mImage->getHeight());
+}
+
+void Icon::setImage(Image *image)
+{
+ mImage = image;
+ if (mImage)
+ setSize(mImage->getWidth(), mImage->getHeight());
+}
+
+void Icon::draw(gcn::Graphics *g)
+{
+ if (mImage)
+ {
+ Graphics *graphics = static_cast<Graphics*>(g);
+ const int x = (getWidth() - mImage->getWidth()) / 2;
+ const int y = (getHeight() - mImage->getHeight()) / 2;
+ graphics->drawImage(mImage, x, y);
+ }
+}
diff --git a/src/gui/widgets/icon.h b/src/gui/widgets/icon.h
new file mode 100644
index 000000000..27ed0db8e
--- /dev/null
+++ b/src/gui/widgets/icon.h
@@ -0,0 +1,66 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ICON_H
+#define ICON_H
+
+#include <guichan/widget.hpp>
+
+class Image;
+
+/**
+ * An icon.
+ *
+ * \ingroup GUI
+ */
+class Icon : public gcn::Widget
+{
+ public:
+ /**
+ * Constructor.
+ */
+ Icon(const std::string &filename);
+
+ /**
+ * Constructor, uses an existing Image.
+ */
+ Icon(Image *image);
+
+ /**
+ * Gets the current Image.
+ */
+ Image *getImage() const { return mImage; }
+
+ /**
+ * Sets the image to display.
+ */
+ void setImage(Image *image);
+
+ /**
+ * Draws the Icon.
+ */
+ void draw(gcn::Graphics *g);
+
+ private:
+ Image *mImage;
+};
+
+#endif // ICON_H
diff --git a/src/gui/widgets/inttextfield.cpp b/src/gui/widgets/inttextfield.cpp
new file mode 100644
index 000000000..d0ffaebc5
--- /dev/null
+++ b/src/gui/widgets/inttextfield.cpp
@@ -0,0 +1,112 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/inttextfield.h"
+
+#include "gui/sdlinput.h"
+
+#include "utils/stringutils.h"
+
+IntTextField::IntTextField(int def, int min, int max,
+ bool enabled, int width):
+ TextField(toString(def)),
+ mDefault(def),
+ mValue(def)
+{
+ if (min != 0 || max != 0)
+ setRange(min, max);
+
+ setEnabled(enabled);
+ if (width != 0)
+ setWidth(width);
+}
+
+void IntTextField::keyPressed(gcn::KeyEvent &event)
+{
+ const gcn::Key &key = event.getKey();
+
+ if (key.getValue() == Key::BACKSPACE ||
+ key.getValue() == Key::DELETE)
+ {
+ setText(std::string());
+ event.consume();
+ }
+
+ if (!key.isNumber())
+ return;
+
+ TextField::keyPressed(event);
+
+ std::istringstream s(getText());
+ int i;
+ s >> i;
+ setValue(i);
+}
+
+void IntTextField::setRange(int min, int max)
+{
+ mMin = min;
+ mMax = max;
+
+ if (mValue < mMin)
+ mValue = mMin;
+ else if (mValue > mMax)
+ mValue = mMax;
+
+ if (mDefault < mMin)
+ mDefault = mMin;
+ else if (mDefault > mMax)
+ mDefault = mMax;
+}
+
+int IntTextField::getValue()
+{
+ return getText().empty() ? mMin : mValue;
+}
+
+void IntTextField::setValue(int i)
+{
+ if (i < mMin)
+ mValue = mMin;
+ else if (i > mMax)
+ mValue = mMax;
+ else
+ mValue = i;
+
+ const std::string valStr = toString(mValue);
+ setText(valStr);
+ setCaretPosition(static_cast<unsigned>(valStr.length()) + 1);
+}
+
+void IntTextField::setDefaultValue(int value)
+{
+ if (value < mMin)
+ mDefault = mMin;
+ else if (value > mMax)
+ mDefault = mMax;
+ else
+ mDefault = value;
+}
+
+void IntTextField::reset()
+{
+ setValue(mDefault);
+}
diff --git a/src/gui/widgets/inttextfield.h b/src/gui/widgets/inttextfield.h
new file mode 100644
index 000000000..69c8a74d7
--- /dev/null
+++ b/src/gui/widgets/inttextfield.h
@@ -0,0 +1,76 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef INTTEXTFIELD_H
+#define INTTEXTFIELD_H
+
+#include "textfield.h"
+
+/**
+ * TextBox which only accepts numbers as input.
+ */
+class IntTextField : public TextField
+{
+ public:
+ /**
+ * Constructor, sets default value.
+ */
+ IntTextField(int def = 0, int min = 0, int max = 0,
+ bool enabled = true, int width = 0);
+
+ /**
+ * Sets the minimum and maximum values of the text box.
+ */
+ void setRange(int minimum, int maximum);
+
+ /**
+ * Returns the value in the text box.
+ */
+ int getValue();
+
+ /**
+ * Reset the field to the default value.
+ */
+ void reset();
+
+ /**
+ * Set the value of the text box to the specified value.
+ */
+ void setValue(int value);
+
+ /**
+ * Set the default value of the text box to the specified value.
+ */
+ void setDefaultValue(int value);
+
+ /**
+ * Responds to key presses.
+ */
+ void keyPressed(gcn::KeyEvent &event);
+
+ private:
+ int mMin; /**< Minimum value */
+ int mMax; /**< Maximum value */
+ int mDefault; /**< Default value */
+ int mValue; /**< Current value */
+};
+
+#endif
diff --git a/src/gui/widgets/itemcontainer.cpp b/src/gui/widgets/itemcontainer.cpp
new file mode 100644
index 000000000..8b5b1914a
--- /dev/null
+++ b/src/gui/widgets/itemcontainer.cpp
@@ -0,0 +1,475 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/itemcontainer.h"
+
+#include "graphics.h"
+#include "inventory.h"
+#include "item.h"
+#include "itemshortcut.h"
+#include "dropshortcut.h"
+#include "log.h"
+
+#include "gui/chat.h"
+#include "gui/itempopup.h"
+#include "gui/outfitwindow.h"
+#include "gui/palette.h"
+#include "gui/shopwindow.h"
+#include "gui/shortcutwindow.h"
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "net/net.h"
+#include "net/inventoryhandler.h"
+
+#include "resources/image.h"
+#include "resources/iteminfo.h"
+
+#include "utils/stringutils.h"
+
+#include <guichan/mouseinput.hpp>
+#include <guichan/selectionlistener.hpp>
+
+// TODO: Add support for adding items to the item shortcut window (global
+// itemShortcut).
+
+static const int BOX_WIDTH = 35;
+static const int BOX_HEIGHT = 43;
+
+ItemContainer::ItemContainer(Inventory *inventory, bool forceQuantity):
+ mInventory(inventory),
+ mGridColumns(1),
+ mGridRows(1),
+ mSelectedIndex(-1),
+ mHighlightedIndex(-1),
+ mLastUsedSlot(-1),
+ mSelectionStatus(SEL_NONE),
+ mForceQuantity(forceQuantity),
+ mSwapItems(false),
+ mDescItems(false)
+{
+ mItemPopup = new ItemPopup;
+ setFocusable(true);
+
+ mSelImg = Theme::getImageFromTheme("selection.png");
+ if (!mSelImg)
+ logger->log1("Error: Unable to load selection.png");
+
+ addKeyListener(this);
+ addMouseListener(this);
+ addWidgetListener(this);
+}
+
+ItemContainer::~ItemContainer()
+{
+ if (mSelImg)
+ {
+ mSelImg->decRef();
+ mSelImg = 0;
+ }
+ delete mItemPopup;
+ mItemPopup = 0;
+}
+
+void ItemContainer::logic()
+{
+ gcn::Widget::logic();
+
+ if (!mInventory)
+ return;
+
+ const int lastUsedSlot = mInventory->getLastUsedSlot();
+
+ if (lastUsedSlot != mLastUsedSlot)
+ {
+ mLastUsedSlot = lastUsedSlot;
+ adjustHeight();
+ }
+}
+
+void ItemContainer::draw(gcn::Graphics *graphics)
+{
+ if (!mInventory)
+ return;
+
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ g->setFont(getFont());
+
+ for (int i = 0; i < mGridColumns; i++)
+ {
+ for (int j = 0; j < mGridRows; j++)
+ {
+ int itemX = i * BOX_WIDTH;
+ int itemY = j * BOX_HEIGHT;
+ int itemIndex = (j * mGridColumns) + i;
+ Item *item = mInventory->getItem(itemIndex);
+
+ if (!item || item->getId() == 0)
+ continue;
+
+ Image *image = item->getImage();
+ if (image)
+ {
+ if (itemIndex == mSelectedIndex)
+ {
+ if (mSelectionStatus == SEL_DRAGGING)
+ {
+ // Reposition the coords to that of the cursor.
+ itemX = mDragPosX - (BOX_WIDTH / 2);
+ itemY = mDragPosY - (BOX_HEIGHT / 2);
+ }
+ else
+ {
+ // Draw selection border image.
+ if (mSelImg)
+ g->drawImage(mSelImg, itemX, itemY);
+ }
+ }
+ image->setAlpha(1.0f); // ensure the image if fully drawn...
+ g->drawImage(image, itemX, itemY);
+ }
+ // Draw item caption
+ std::string caption;
+ if (item->getQuantity() > 1 || mForceQuantity)
+ caption = toString(item->getQuantity());
+ else if (item->isEquipped())
+ caption = "Eq.";
+
+ if (item->isEquipped())
+ g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED));
+ else
+ g->setColor(gcn::Color(0, 0, 0));
+
+ g->drawText(caption, itemX + BOX_WIDTH / 2,
+ itemY + BOX_HEIGHT - 14, gcn::Graphics::CENTER);
+ }
+ }
+
+ // Draw an orange box around the selected item
+ if (isFocused() && mHighlightedIndex != -1 && mGridColumns)
+ {
+ const int itemX = (mHighlightedIndex % mGridColumns) * BOX_WIDTH;
+ const int itemY = (mHighlightedIndex / mGridColumns) * BOX_HEIGHT;
+ g->setColor(gcn::Color(255, 128, 0));
+ g->drawRectangle(gcn::Rectangle(itemX, itemY, BOX_WIDTH, BOX_HEIGHT));
+ }
+}
+
+void ItemContainer::selectNone()
+{
+ setSelectedIndex(-1);
+ mSelectionStatus = SEL_NONE;
+ if (outfitWindow)
+ outfitWindow->setItemSelected(-1);
+ if (shopWindow)
+ shopWindow->setItemSelected(-1);
+}
+
+void ItemContainer::setSelectedIndex(int newIndex)
+{
+ if (mSelectedIndex != newIndex)
+ {
+ mSelectedIndex = newIndex;
+ distributeValueChangedEvent();
+ }
+}
+
+Item *ItemContainer::getSelectedItem() const
+{
+ if (mInventory)
+ return mInventory->getItem(mSelectedIndex);
+ else
+ return 0;
+}
+
+void ItemContainer::distributeValueChangedEvent()
+{
+ SelectionListenerIterator i, i_end;
+
+ for (i = mSelectionListeners.begin(), i_end = mSelectionListeners.end();
+ i != i_end; ++i)
+ {
+ if (*i)
+ {
+ gcn::SelectionEvent event(this);
+ (*i)->valueChanged(event);
+ }
+ }
+}
+
+void ItemContainer::keyPressed(gcn::KeyEvent &event _UNUSED_)
+{
+ /*switch (event.getKey().getValue())
+ {
+ case Key::LEFT:
+ moveHighlight(Left);
+ break;
+ case Key::RIGHT:
+ moveHighlight(Right);
+ break;
+ case Key::UP:
+ moveHighlight(Up);
+ break;
+ case Key::DOWN:
+ moveHighlight(Down);
+ break;
+ case Key::SPACE:
+ keyAction();
+ break;
+ case Key::LEFT_ALT:
+ case Key::RIGHT_ALT:
+ mSwapItems = true;
+ break;
+ case Key::RIGHT_CONTROL:
+ mDescItems = true;
+ break;
+ }*/
+}
+
+void ItemContainer::keyReleased(gcn::KeyEvent &event _UNUSED_)
+{
+ /*switch (event.getKey().getValue())
+ {
+ case Key::LEFT_ALT:
+ case Key::RIGHT_ALT:
+ mSwapItems = false;
+ break;
+ case Key::RIGHT_CONTROL:
+ mDescItems = false;
+ break;
+ }*/
+}
+
+void ItemContainer::mousePressed(gcn::MouseEvent &event)
+{
+ if (!mInventory)
+ return;
+
+ const int button = event.getButton();
+ if (button == gcn::MouseEvent::LEFT || button == gcn::MouseEvent::RIGHT)
+ {
+ const int index = getSlotIndex(event.getX(), event.getY());
+ if (index == Inventory::NO_SLOT_INDEX)
+ return;
+
+ Item *item = mInventory->getItem(index);
+
+ // put item name into chat window
+ if (item && mDescItems && chatWindow)
+ chatWindow->addItemText(item->getInfo().getName());
+
+ if (mSelectedIndex == index)
+ {
+ mSelectionStatus = SEL_DESELECTING;
+ }
+ else if (item && item->getId())
+ {
+ setSelectedIndex(index);
+ mSelectionStatus = SEL_SELECTING;
+
+ int num = itemShortcutWindow->getTabIndex();
+ if (num >= 0 && num < SHORTCUT_TABS)
+ {
+ if (itemShortcut[num])
+ itemShortcut[num]->setItemSelected(item->getId());
+ }
+ if (dropShortcut)
+ dropShortcut->setItemSelected(item->getId());
+ if (item->isEquipment() && outfitWindow)
+ outfitWindow->setItemSelected(item->getId());
+ if (shopWindow)
+ shopWindow->setItemSelected(item->getId());
+ }
+ else
+ {
+ selectNone();
+ }
+ }
+}
+
+void ItemContainer::mouseDragged(gcn::MouseEvent &event)
+{
+ if (mSelectionStatus != SEL_NONE)
+ {
+ mSelectionStatus = SEL_DRAGGING;
+ mDragPosX = event.getX();
+ mDragPosY = event.getY();
+ }
+}
+
+void ItemContainer::mouseReleased(gcn::MouseEvent &event)
+{
+ switch (mSelectionStatus)
+ {
+ case SEL_SELECTING:
+ mSelectionStatus = SEL_SELECTED;
+ return;
+ case SEL_DESELECTING:
+ selectNone();
+ return;
+ case SEL_DRAGGING:
+ mSelectionStatus = SEL_SELECTED;
+ break;
+ default:
+ return;
+ };
+
+ int index = getSlotIndex(event.getX(), event.getY());
+ if (index == Inventory::NO_SLOT_INDEX)
+ return;
+ if (index == mSelectedIndex || mSelectedIndex == -1)
+ return;
+ Net::getInventoryHandler()->moveItem(mSelectedIndex, index);
+ selectNone();
+}
+
+
+// Show ItemTooltip
+void ItemContainer::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!mInventory)
+ return;
+
+ Item *item = mInventory->getItem(getSlotIndex(event.getX(), event.getY()));
+
+ if (item && viewport)
+ {
+ mItemPopup->setItem(item);
+ mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
+ }
+ else
+ {
+ mItemPopup->setVisible(false);
+ }
+}
+
+// Hide ItemTooltip
+void ItemContainer::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ mItemPopup->setVisible(false);
+}
+
+void ItemContainer::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ mGridColumns = std::max(1, getWidth() / BOX_WIDTH);
+ adjustHeight();
+}
+
+void ItemContainer::adjustHeight()
+{
+ if (!mGridColumns)
+ return;
+
+ mGridRows = (mLastUsedSlot + 1) / mGridColumns;
+ if (mGridRows == 0 || (mLastUsedSlot + 1) % mGridColumns > 0)
+ ++mGridRows;
+
+ setHeight(mGridRows * BOX_HEIGHT);
+}
+
+int ItemContainer::getSlotIndex(int x, int y) const
+{
+ if (x < getWidth() && y < getHeight())
+ return (y / BOX_HEIGHT) * mGridColumns + (x / BOX_WIDTH);
+
+ return Inventory::NO_SLOT_INDEX;
+}
+
+void ItemContainer::keyAction()
+{
+ // If there is no highlight then return.
+ if (mHighlightedIndex == -1)
+ return;
+
+ // If the highlight is on the selected item, then deselect it.
+ if (mHighlightedIndex == mSelectedIndex)
+ {
+ selectNone();
+ }
+ // Check and swap items if necessary.
+ else if (mSwapItems && mSelectedIndex != -1 && mHighlightedIndex != -1)
+ {
+ Net::getInventoryHandler()->moveItem(
+ mSelectedIndex, mHighlightedIndex);
+ setSelectedIndex(mHighlightedIndex);
+ }
+ // If the highlight is on an item then select it.
+ else if (mHighlightedIndex != -1)
+ {
+ setSelectedIndex(mHighlightedIndex);
+ mSelectionStatus = SEL_SELECTED;
+ }
+ // If the highlight is on a blank space then move it.
+ else if (mSelectedIndex != -1)
+ {
+ Net::getInventoryHandler()->moveItem(
+ mSelectedIndex, mHighlightedIndex);
+ selectNone();
+ }
+}
+
+void ItemContainer::moveHighlight(Direction direction)
+{
+ if (mHighlightedIndex == -1)
+ {
+ if (mSelectedIndex != -1)
+ mHighlightedIndex = mSelectedIndex;
+ else
+ mHighlightedIndex = 0;
+ return;
+ }
+
+ switch (direction)
+ {
+ case Left:
+ if (mHighlightedIndex % mGridColumns == 0)
+ mHighlightedIndex += mGridColumns;
+ mHighlightedIndex--;
+ break;
+ case Right:
+ if ((mHighlightedIndex % mGridColumns) ==
+ (mGridColumns - 1))
+ {
+ mHighlightedIndex -= mGridColumns;
+ }
+ mHighlightedIndex++;
+ break;
+ case Up:
+ if (mHighlightedIndex / mGridColumns == 0)
+ mHighlightedIndex += (mGridColumns * mGridRows);
+ mHighlightedIndex -= mGridColumns;
+ break;
+ case Down:
+ if ((mHighlightedIndex / mGridColumns) ==
+ (mGridRows - 1))
+ {
+ mHighlightedIndex -= (mGridColumns * mGridRows);
+ }
+ mHighlightedIndex += mGridColumns;
+ break;
+ default:
+ logger->log("warning moveHighlight unknown direction:"
+ + toString(static_cast<unsigned>(direction)));
+ break;
+ }
+}
diff --git a/src/gui/widgets/itemcontainer.h b/src/gui/widgets/itemcontainer.h
new file mode 100644
index 000000000..8aaa236b4
--- /dev/null
+++ b/src/gui/widgets/itemcontainer.h
@@ -0,0 +1,195 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ITEMCONTAINER_H
+#define ITEMCONTAINER_H
+
+#include <guichan/keylistener.hpp>
+#include <guichan/mouselistener.hpp>
+#include <guichan/widget.hpp>
+#include <guichan/widgetlistener.hpp>
+
+#include <list>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+class Inventory;
+class Item;
+class ItemPopup;
+
+namespace gcn
+{
+ class SelectionListener;
+}
+
+/**
+ * An item container. Used to show items in inventory and trade dialog.
+ *
+ * \ingroup GUI
+ */
+class ItemContainer : public gcn::Widget,
+ public gcn::KeyListener,
+ public gcn::MouseListener,
+ public gcn::WidgetListener
+{
+ public:
+ /**
+ * Constructor. Initializes the graphic.
+ *
+ * @param inventory
+ * @param gridColumns Amount of columns in grid.
+ * @param gridRows Amount of rows in grid.
+ * @param offset Index offset
+ */
+ ItemContainer(Inventory *inventory, bool forceQuantity = false);
+
+ /**
+ * Destructor.
+ */
+ virtual ~ItemContainer();
+
+ /**
+ * Necessary for checking how full the inventory is.
+ */
+ void logic();
+
+ /**
+ * Draws the items.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ // KeyListener
+ void keyPressed(gcn::KeyEvent &event);
+ void keyReleased(gcn::KeyEvent &event);
+
+ // MouseListener
+ void mousePressed(gcn::MouseEvent &event);
+ void mouseDragged(gcn::MouseEvent &event);
+ void mouseReleased(gcn::MouseEvent &event);
+ void mouseMoved(gcn::MouseEvent &event);
+ void mouseExited(gcn::MouseEvent &event);
+
+ // WidgetListener
+ void widgetResized(const gcn::Event &event);
+
+ /**
+ * Returns the selected item.
+ */
+ Item *getSelectedItem() const;
+
+ /**
+ * Sets selected item to NULL.
+ */
+ void selectNone();
+
+ /**
+ * Adds a listener to the list that's notified each time a change to
+ * the selection occurs.
+ */
+ void addSelectionListener(gcn::SelectionListener *listener)
+ { mSelectionListeners.push_back(listener); }
+
+ /**
+ * Removes a listener from the list that's notified each time a change
+ * to the selection occurs.
+ */
+ void removeSelectionListener(gcn::SelectionListener *listener)
+ { mSelectionListeners.remove(listener); }
+
+ private:
+ enum Direction
+ {
+ Left = 0,
+ Right,
+ Up,
+ Down
+ };
+
+ enum SelectionState
+ {
+ SEL_NONE = 0,
+ SEL_SELECTED,
+ SEL_SELECTING,
+ SEL_DESELECTING,
+ SEL_DRAGGING
+ };
+
+ /**
+ * Execute all the functionality associated with the action key.
+ */
+ void keyAction();
+
+ /**
+ * Moves the highlight in the direction specified.
+ *
+ * @param direction The move direction of the highlighter.
+ */
+ void moveHighlight(Direction direction);
+
+ /**
+ * Sets the currently selected item.
+ */
+ void setSelectedIndex(int index);
+
+ /**
+ * Determine and set the height of the container.
+ */
+ void adjustHeight();
+
+ /**
+ * Sends out selection events to the list of selection listeners.
+ */
+ void distributeValueChangedEvent();
+
+ /**
+ * Gets the slot index based on the cursor position.
+ *
+ * @param x The X coordinate position.
+ * @param y The Y coordinate position.
+ * @return The slot index on success, -1 on failure.
+ */
+ int getSlotIndex(int x, int y) const;
+
+ Inventory *mInventory;
+ int mGridColumns, mGridRows;
+ Image *mSelImg;
+ int mSelectedIndex, mHighlightedIndex;
+ int mLastUsedSlot;
+ SelectionState mSelectionStatus;
+ bool mForceQuantity;
+ bool mSwapItems;
+ bool mDescItems;
+ int mDragPosX, mDragPosY;
+
+ ItemPopup *mItemPopup;
+
+ typedef std::list<gcn::SelectionListener*> SelectionListenerList;
+ typedef SelectionListenerList::iterator SelectionListenerIterator;
+
+ SelectionListenerList mSelectionListeners;
+};
+
+#endif
diff --git a/src/gui/widgets/itemlinkhandler.cpp b/src/gui/widgets/itemlinkhandler.cpp
new file mode 100644
index 000000000..7c0a65bcb
--- /dev/null
+++ b/src/gui/widgets/itemlinkhandler.cpp
@@ -0,0 +1,66 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sstream>
+#include <string>
+
+#include "item.h"
+
+#include "gui/itempopup.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/itemlinkhandler.h"
+
+#include "resources/itemdb.h"
+
+ItemLinkHandler::ItemLinkHandler()
+{
+ mItemPopup = new ItemPopup;
+}
+
+ItemLinkHandler::~ItemLinkHandler()
+{
+ delete mItemPopup;
+ mItemPopup = 0;
+}
+
+void ItemLinkHandler::handleLink(const std::string &link,
+ gcn::MouseEvent *event _UNUSED_)
+{
+ if (!mItemPopup)
+ return;
+
+ int id = 0;
+ std::stringstream stream;
+ stream << link;
+ stream >> id;
+
+ if (id > 0)
+ {
+ const ItemInfo &itemInfo = ItemDB::get(id);
+ mItemPopup->setItem(itemInfo, true);
+
+ if (mItemPopup->isVisible())
+ mItemPopup->setVisible(false);
+ else if (viewport)
+ mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
+ }
+}
diff --git a/src/gui/widgets/itemlinkhandler.h b/src/gui/widgets/itemlinkhandler.h
new file mode 100644
index 000000000..57e135fed
--- /dev/null
+++ b/src/gui/widgets/itemlinkhandler.h
@@ -0,0 +1,47 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ITEM_LINK_HANDLER_H
+#define ITEM_LINK_HANDLER_H
+
+#include "gui/widgets/linkhandler.h"
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class ItemPopup;
+
+class ItemLinkHandler : public LinkHandler
+{
+ public:
+ ItemLinkHandler();
+ ~ItemLinkHandler();
+ void handleLink(const std::string &link,
+ gcn::MouseEvent *event _UNUSED_);
+
+ private:
+ ItemPopup *mItemPopup;
+};
+
+#endif
diff --git a/src/gui/widgets/itemshortcutcontainer.cpp b/src/gui/widgets/itemshortcutcontainer.cpp
new file mode 100644
index 000000000..2ab4a7e19
--- /dev/null
+++ b/src/gui/widgets/itemshortcutcontainer.cpp
@@ -0,0 +1,375 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/itemshortcutcontainer.h"
+
+#include "configuration.h"
+#include "graphics.h"
+#include "inventory.h"
+#include "item.h"
+#include "itemshortcut.h"
+#include "spellshortcut.h"
+#include "keyboardconfig.h"
+#include "localplayer.h"
+#include "playerinfo.h"
+#include "spellmanager.h"
+#include "textcommand.h"
+
+#include "gui/inventorywindow.h"
+#include "gui/itempopup.h"
+#include "gui/palette.h"
+#include "gui/spellpopup.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "resources/image.h"
+#include "resources/iteminfo.h"
+
+#include "utils/stringutils.h"
+
+ItemShortcutContainer::ItemShortcutContainer(unsigned number):
+ ShortcutContainer(),
+ mItemClicked(false),
+ mItemMoved(NULL),
+ mNumber(number)
+{
+ addMouseListener(this);
+ addWidgetListener(this);
+
+ mItemPopup = new ItemPopup;
+ mSpellPopup = new SpellPopup;
+
+ mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png");
+ if (itemShortcut[mNumber])
+ mMaxItems = itemShortcut[mNumber]->getItemCount();
+ else
+ mMaxItems = 0;
+
+ if (mBackgroundImg)
+ {
+ mBackgroundImg->setAlpha(Client::getGuiAlpha());
+ mBoxHeight = mBackgroundImg->getHeight();
+ mBoxWidth = mBackgroundImg->getWidth();
+ }
+ else
+ {
+ mBoxHeight = 1;
+ mBoxWidth = 1;
+ }
+}
+
+ItemShortcutContainer::~ItemShortcutContainer()
+{
+ if (mBackgroundImg)
+ {
+ mBackgroundImg->decRef();
+ mBackgroundImg = 0;
+ }
+ delete mItemPopup;
+ mItemPopup = 0;
+ delete mSpellPopup;
+ mSpellPopup = 0;
+}
+
+void ItemShortcutContainer::draw(gcn::Graphics *graphics)
+{
+ if (!itemShortcut[mNumber])
+ return;
+
+ mAlpha = Client::getGuiAlpha();
+ if (Client::getGuiAlpha() != mAlpha)
+ {
+ if (mBackgroundImg)
+ mBackgroundImg->setAlpha(mAlpha);
+ }
+
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ graphics->setFont(getFont());
+
+ for (unsigned i = 0; i < mMaxItems; i++)
+ {
+ const int itemX = (i % mGridWidth) * mBoxWidth;
+ const int itemY = (i / mGridWidth) * mBoxHeight;
+
+ if (mBackgroundImg)
+ g->drawImage(mBackgroundImg, itemX, itemY);
+
+ // Draw item keyboard shortcut.
+ std::string key = keyboard.getKeyValueString(
+ keyboard.KEY_SHORTCUT_1 + i);
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
+ g->drawText(key, itemX + 2, itemY + 2, gcn::Graphics::LEFT);
+
+ int itemId = itemShortcut[mNumber]->getItem(i);
+
+ if (itemId < 0)
+ continue;
+
+ // this is item
+ if (itemId < SPELL_MIN_ID)
+ {
+ if (!PlayerInfo::getInventory())
+ continue;
+
+ Item *item = PlayerInfo::getInventory()->findItem(itemId);
+
+ if (item)
+ {
+ // Draw item icon.
+ Image* image = item->getImage();
+
+ if (image)
+ {
+ std::string caption;
+ if (item->getQuantity() > 1)
+ caption = toString(item->getQuantity());
+ else if (item->isEquipped())
+ caption = "Eq.";
+
+ image->setAlpha(1.0f);
+ g->drawImage(image, itemX, itemY);
+ if (item->isEquipped())
+ {
+ g->setColor(Theme::getThemeColor(
+ Theme::ITEM_EQUIPPED));
+ }
+ else
+ {
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ }
+ g->drawText(caption, itemX + mBoxWidth / 2,
+ itemY + mBoxHeight - 14, gcn::Graphics::CENTER);
+ }
+ }
+ }
+ else if (spellManager) // this is magic shortcut
+ {
+ TextCommand *spell = spellManager->getSpellByItem(itemId);
+ if (spell)
+ {
+ if (!spell->isEmpty())
+ {
+ Image* image = spell->getImage();
+
+ if (image)
+ {
+ image->setAlpha(1.0f);
+ g->drawImage(image, itemX, itemY);
+ }
+ }
+
+ g->drawText(spell->getSymbol(), itemX + 2,
+ itemY + mBoxHeight / 2, gcn::Graphics::LEFT);
+ }
+ }
+
+ }
+
+ if (mItemMoved)
+ {
+ // Draw the item image being dragged by the cursor.
+ Image* image = mItemMoved->getImage();
+ if (image)
+ {
+ const int tPosX = mCursorPosX - (image->getWidth() / 2);
+ const int tPosY = mCursorPosY - (image->getHeight() / 2);
+
+ g->drawImage(image, tPosX, tPosY);
+ g->drawText(toString(mItemMoved->getQuantity()),
+ tPosX + mBoxWidth / 2, tPosY + mBoxHeight - 14,
+ gcn::Graphics::CENTER);
+ }
+ }
+}
+
+void ItemShortcutContainer::mouseDragged(gcn::MouseEvent &event)
+{
+ if (!itemShortcut[mNumber])
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (!mItemMoved && mItemClicked)
+ {
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ const int itemId = itemShortcut[mNumber]->getItem(index);
+
+ if (itemId < 0)
+ return;
+
+ if (itemId < SPELL_MIN_ID)
+ {
+ if (!PlayerInfo::getInventory())
+ return;
+
+ Item *item = PlayerInfo::getInventory()->findItem(itemId);
+
+ if (item)
+ {
+ mItemMoved = item;
+ itemShortcut[mNumber]->removeItem(index);
+ }
+ }
+ else if (spellManager)
+ {
+ TextCommand *spell = spellManager->getSpellByItem(itemId);
+ if (spell)
+ itemShortcut[mNumber]->removeItem(index);
+ }
+ }
+ if (mItemMoved)
+ {
+ mCursorPosX = event.getX();
+ mCursorPosY = event.getY();
+ }
+ }
+}
+
+void ItemShortcutContainer::mousePressed(gcn::MouseEvent &event)
+{
+ if (!itemShortcut[mNumber])
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ // Stores the selected item if theirs one.
+ if (itemShortcut[mNumber]->isItemSelected() &&
+ (inventoryWindow && (inventoryWindow->isVisible() ||
+ itemShortcut[mNumber]->getSelectedItem() >= SPELL_MIN_ID)))
+ {
+ itemShortcut[mNumber]->setItem(index);
+ itemShortcut[mNumber]->setItemSelected(-1);
+ if (spellShortcut)
+ spellShortcut->setItemSelected(-1);
+ }
+ else if (itemShortcut[mNumber]->getItem(index))
+ {
+ mItemClicked = true;
+ }
+ }
+ else if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+// Item *item = PlayerInfo::getInventory()->findItem(id);
+
+ if (viewport && itemShortcut[mNumber])
+ viewport->showItemPopup(itemShortcut[mNumber]->getItem(index));
+ }
+}
+
+void ItemShortcutContainer::mouseReleased(gcn::MouseEvent &event)
+{
+ if (!itemShortcut[mNumber])
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (itemShortcut[mNumber]->isItemSelected())
+ itemShortcut[mNumber]->setItemSelected(-1);
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+ if (index == -1)
+ {
+ mItemMoved = NULL;
+ return;
+ }
+ if (mItemMoved)
+ {
+ itemShortcut[mNumber]->setItems(index, mItemMoved->getId());
+ mItemMoved = NULL;
+ }
+ else if (itemShortcut[mNumber]->getItem(index) && mItemClicked)
+ {
+ itemShortcut[mNumber]->useItem(index);
+ }
+
+ if (mItemClicked)
+ mItemClicked = false;
+ }
+}
+
+// Show ItemTooltip
+void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!itemShortcut[mNumber])
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ const int itemId = itemShortcut[mNumber]->getItem(index);
+
+ if (itemId < 0)
+ return;
+
+ if (itemId < SPELL_MIN_ID)
+ {
+ mSpellPopup->setVisible(false);
+
+ if (!PlayerInfo::getInventory())
+ return;
+
+ Item *item = PlayerInfo::getInventory()->findItem(itemId);
+
+ if (item && viewport)
+ {
+ mItemPopup->setItem(item);
+ mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
+ }
+ else
+ {
+ mItemPopup->setVisible(false);
+ }
+ }
+ else if (spellManager)
+ {
+ mItemPopup->setVisible(false);
+ TextCommand *spell = spellManager->getSpellByItem(itemId);
+ if (spell && viewport)
+ {
+ mSpellPopup->setItem(spell);
+ mSpellPopup->view(viewport->getMouseX(), viewport->getMouseY());
+ }
+ else
+ {
+ mSpellPopup->setVisible(false);
+ }
+ }
+}
+
+// Hide ItemTooltip
+void ItemShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ mItemPopup->setVisible(false);
+ mSpellPopup->setVisible(false);
+}
diff --git a/src/gui/widgets/itemshortcutcontainer.h b/src/gui/widgets/itemshortcutcontainer.h
new file mode 100644
index 000000000..0f7067e38
--- /dev/null
+++ b/src/gui/widgets/itemshortcutcontainer.h
@@ -0,0 +1,93 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ITEMSHORTCUTCONTAINER_H
+#define ITEMSHORTCUTCONTAINER_H
+
+#include "spellmanager.h"
+
+#include "gui/widgets/shortcutcontainer.h"
+
+#include <guichan/mouselistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+class Item;
+class ItemPopup;
+class SpellPopup;
+
+/**
+ * An item shortcut container. Used to quickly use items.
+ *
+ * \ingroup GUI
+ */
+class ItemShortcutContainer : public ShortcutContainer
+{
+ public:
+ /**
+ * Constructor. Initializes the graphic.
+ */
+ ItemShortcutContainer(unsigned number);
+
+ /**
+ * Destructor.
+ */
+ virtual ~ItemShortcutContainer();
+
+ /**
+ * Draws the items.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Handles mouse when dragged.
+ */
+ void mouseDragged(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse when pressed.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse release.
+ */
+ void mouseReleased(gcn::MouseEvent &event);
+
+ private:
+ void mouseExited(gcn::MouseEvent &event);
+ void mouseMoved(gcn::MouseEvent &event);
+
+ bool mItemClicked;
+ Item *mItemMoved;
+ unsigned mNumber;
+
+ ItemPopup *mItemPopup;
+ SpellPopup *mSpellPopup;
+};
+
+//extern SpellManager *spellManager;
+#endif
diff --git a/src/gui/widgets/label.cpp b/src/gui/widgets/label.cpp
new file mode 100644
index 000000000..e9b4cb0f6
--- /dev/null
+++ b/src/gui/widgets/label.cpp
@@ -0,0 +1,38 @@
+/*
+ * The Mana Client
+ * Copyright (c) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/label.h"
+
+#include "gui/theme.h"
+
+Label::Label()
+{
+}
+
+Label::Label(const std::string &caption) :
+ gcn::Label(caption)
+{
+ setForegroundColor(Theme::getThemeColor(Theme::TEXT));
+}
+
+void Label::draw(gcn::Graphics *graphics)
+{
+ gcn::Label::draw(static_cast<gcn::Graphics*>(graphics));
+}
diff --git a/src/gui/widgets/label.h b/src/gui/widgets/label.h
new file mode 100644
index 000000000..2dfa254c4
--- /dev/null
+++ b/src/gui/widgets/label.h
@@ -0,0 +1,52 @@
+/*
+ * The Mana Client
+ * Copyright (c) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LABEL_H
+#define LABEL_H
+
+#include <guichan/widgets/label.hpp>
+
+/**
+ * Label widget. Same as the Guichan label but modified to use the palette
+ * system.
+ *
+ * \ingroup GUI
+ */
+class Label : public gcn::Label
+{
+ public:
+ /**
+ * Constructor.
+ */
+ Label();
+
+ /**
+ * Constructor. This version of the constructor sets the label with an
+ * inintialization string.
+ */
+ Label(const std::string &caption);
+
+ /**
+ * Draws the label.
+ */
+ void draw(gcn::Graphics *graphics);
+};
+
+#endif
diff --git a/src/gui/widgets/layout.cpp b/src/gui/widgets/layout.cpp
new file mode 100644
index 000000000..38c6bb471
--- /dev/null
+++ b/src/gui/widgets/layout.cpp
@@ -0,0 +1,362 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/layout.h"
+
+#include "log.h"
+
+#include <cassert>
+
+ContainerPlacer ContainerPlacer::at(int x, int y)
+{
+ return ContainerPlacer(mContainer, &mCell->at(x, y));
+}
+
+LayoutCell &ContainerPlacer::operator()
+ (int x, int y, gcn::Widget *wg, int w, int h)
+{
+ mContainer->add(wg);
+ return mCell->place(wg, x, y, w, h);
+}
+
+LayoutCell::~LayoutCell()
+{
+ if (mType == ARRAY)
+ {
+ delete mArray;
+ mArray = 0;
+ }
+}
+
+LayoutArray &LayoutCell::getArray()
+{
+ assert(mType != WIDGET);
+ if (mType == ARRAY)
+ return *mArray;
+
+ mArray = new LayoutArray;
+ mType = ARRAY;
+ mExtent[0] = 1;
+ mExtent[1] = 1;
+ mPadding = 0;
+ mAlign[0] = FILL;
+ mAlign[1] = FILL;
+ return *mArray;
+}
+
+void LayoutCell::reflow(int nx, int ny, int nw, int nh)
+{
+ assert(mType != NONE);
+ nx += mPadding;
+ ny += mPadding;
+ nw -= 2 * mPadding;
+ nh -= 2 * mPadding;
+ if (mType == ARRAY)
+ mArray->reflow(nx, ny, nw, nh);
+ else
+ mWidget->setDimension(gcn::Rectangle(nx, ny, nw, nh));
+}
+
+void LayoutCell::computeSizes()
+{
+ assert(mType == ARRAY);
+
+ std::vector< std::vector< LayoutCell * > >::iterator
+ i = mArray->mCells.begin();
+
+ while (i != mArray->mCells.end())
+ {
+ std::vector< LayoutCell * >::iterator j = i->begin();
+ while (j != i->end())
+ {
+ LayoutCell *cell = *j;
+ if (cell && cell->mType == ARRAY)
+ cell->computeSizes();
+
+ ++j;
+ }
+ ++i;
+ }
+
+ mSize[0] = mArray->getSize(0);
+ mSize[1] = mArray->getSize(1);
+}
+
+LayoutArray::LayoutArray(): mSpacing(4)
+{
+}
+
+LayoutArray::~LayoutArray()
+{
+ std::vector< std::vector< LayoutCell * > >::iterator i = mCells.begin();
+ while (i != mCells.end())
+ {
+ std::vector< LayoutCell * >::iterator j = i->begin();
+ while (j != i->end())
+ {
+ delete *j;
+ ++j;
+ }
+ ++i;
+ }
+}
+
+LayoutCell &LayoutArray::at(int x, int y, int w, int h)
+{
+ resizeGrid(x + w, y + h);
+ LayoutCell *&cell = mCells[y][x];
+ if (!cell)
+ cell = new LayoutCell;
+ return *cell;
+}
+
+void LayoutArray::resizeGrid(int w, int h)
+{
+ bool extW = w && w > static_cast<int>(mSizes[0].size()),
+ extH = h && h > static_cast<int>(mSizes[1].size());
+
+ if (!extW && !extH)
+ return;
+
+ if (extH)
+ {
+ mSizes[1].resize(h, Layout::AUTO_DEF);
+ mCells.resize(h);
+ if (!extW)
+ w = static_cast<int>(mSizes[0].size());
+ }
+
+ if (extW)
+ mSizes[0].resize(w, Layout::AUTO_DEF);
+
+ std::vector< std::vector< LayoutCell * > >::iterator i = mCells.begin();
+ while (i != mCells.end())
+ {
+ i->resize(w, 0);
+ ++i;
+ }
+}
+
+void LayoutArray::setColWidth(int n, int w)
+{
+ resizeGrid(n + 1, 0);
+ mSizes[0][n] = w;
+}
+
+void LayoutArray::setRowHeight(int n, int h)
+{
+ resizeGrid(0, n + 1);
+ mSizes[1][n] = h;
+}
+
+void LayoutArray::matchColWidth(int n1, int n2)
+{
+ resizeGrid(std::max(n1, n2) + 1, 0);
+ std::vector<int> widths = getSizes(0, Layout::AUTO_DEF);
+ int s = std::max(widths[n1], widths[n2]);
+ mSizes[0][n1] = s;
+ mSizes[0][n2] = s;
+}
+
+void LayoutArray::extend(int x, int y, int w, int h)
+{
+ LayoutCell &cell = at(x, y, w, h);
+ cell.mExtent[0] = w;
+ cell.mExtent[1] = h;
+}
+
+LayoutCell &LayoutArray::place(gcn::Widget *widget, int x, int y, int w, int h)
+{
+ LayoutCell &cell = at(x, y, w, h);
+ assert(cell.mType == LayoutCell::NONE);
+ cell.mType = LayoutCell::WIDGET;
+ cell.mWidget = widget;
+ if (widget)
+ {
+ cell.mSize[0] = w == 1 ? widget->getWidth() : 0;
+ cell.mSize[1] = h == 1 ? widget->getHeight() : 0;
+ }
+ else
+ {
+ cell.mSize[0] = 1;
+ cell.mSize[1] = 1;
+ }
+ cell.mExtent[0] = w;
+ cell.mExtent[1] = h;
+ cell.mPadding = 0;
+ cell.mAlign[0] = LayoutCell::FILL;
+ cell.mAlign[1] = LayoutCell::FILL;
+ int &cs = mSizes[0][x], &rs = mSizes[1][y];
+ if (cs == Layout::AUTO_DEF && w == 1)
+ cs = 0;
+ if (rs == Layout::AUTO_DEF && h == 1)
+ rs = 0;
+ return cell;
+}
+
+void LayoutArray::align(int &pos, int &size, int dim,
+ LayoutCell const &cell, int *sizes) const
+{
+ int size_max = sizes[0];
+ for (int i = 1; i < cell.mExtent[dim]; ++i)
+ size_max += sizes[i] + mSpacing;
+ size = std::min<int>(cell.mSize[dim], size_max);
+
+ switch (cell.mAlign[dim])
+ {
+ case LayoutCell::LEFT:
+ return;
+ case LayoutCell::RIGHT:
+ pos += size_max - size;
+ return;
+ case LayoutCell::CENTER:
+ pos += (size_max - size) / 2;
+ return;
+ case LayoutCell::FILL:
+ size = size_max;
+ return;
+ default:
+ logger->log1("LayoutArray::align unknown layout");
+ return;
+ }
+}
+
+std::vector<int> LayoutArray::getSizes(int dim, int upp) const
+{
+ int gridW = static_cast<int>(mSizes[0].size()),
+ gridH = static_cast<int>(mSizes[1].size());
+ std::vector<int> sizes = mSizes[dim];
+
+ // Compute minimum sizes.
+ for (int gridY = 0; gridY < gridH; ++gridY)
+ {
+ for (int gridX = 0; gridX < gridW; ++gridX)
+ {
+ LayoutCell const *cell = mCells[gridY][gridX];
+ if (!cell || cell->mType == LayoutCell::NONE)
+ continue;
+
+ if (cell->mExtent[dim] == 1)
+ {
+ int n = (dim == 0 ? gridX : gridY);
+ int s = cell->mSize[dim] + cell->mPadding * 2;
+ if (s > sizes[n]) sizes[n] = s;
+ }
+ }
+ }
+
+ if (upp == Layout::AUTO_DEF) return sizes;
+
+ // Compute the FILL sizes.
+ int nb = static_cast<int>(sizes.size());
+ int nbFill = 0;
+ for (int i = 0; i < nb; ++i)
+ {
+ if (mSizes[dim][i] <= Layout::AUTO_DEF)
+ {
+ ++nbFill;
+ if (mSizes[dim][i] == Layout::AUTO_SET ||
+ sizes[i] <= Layout::AUTO_DEF)
+ {
+ sizes[i] = 0;
+ }
+ }
+ upp -= sizes[i] + mSpacing;
+ }
+ upp = upp + mSpacing;
+
+ if (nbFill == 0)
+ return sizes;
+
+ for (int i = 0; i < nb; ++i)
+ {
+ if (mSizes[dim][i] > Layout::AUTO_DEF)
+ continue;
+
+ int s = upp / nbFill;
+ sizes[i] += s;
+ upp -= s;
+ --nbFill;
+ }
+
+ return sizes;
+}
+
+int LayoutArray::getSize(int dim) const
+{
+ std::vector<int> sizes = getSizes(dim, Layout::AUTO_DEF);
+ int size = 0;
+ int nb = static_cast<int>(sizes.size());
+ for (int i = 0; i < nb; ++i)
+ {
+ if (sizes[i] > Layout::AUTO_DEF)
+ size += sizes[i];
+ size += mSpacing;
+ }
+ return size - mSpacing;
+}
+
+void LayoutArray::reflow(int nx, int ny, int nw, int nh)
+{
+ int gridW = static_cast<int>(mSizes[0].size()),
+ gridH = static_cast<int>(mSizes[1].size());
+
+ std::vector<int> widths = getSizes(0, nw);
+ std::vector<int> heights = getSizes(1, nh);
+
+ int y = ny;
+ for (int gridY = 0; gridY < gridH; ++gridY)
+ {
+ int x = nx;
+ for (int gridX = 0; gridX < gridW; ++gridX)
+ {
+ LayoutCell *cell = mCells[gridY][gridX];
+ if (cell && cell->mType != LayoutCell::NONE)
+ {
+ int dx = x, dy = y, dw, dh;
+ align(dx, dw, 0, *cell, &widths[gridX]);
+ align(dy, dh, 1, *cell, &heights[gridY]);
+ cell->reflow(dx, dy, dw, dh);
+ }
+ x += widths[gridX] + mSpacing;
+ }
+ y += heights[gridY] + mSpacing;
+ }
+}
+
+Layout::Layout(): mComputed(false)
+{
+ getArray();
+ setPadding(6);
+}
+
+void Layout::reflow(int &nw, int &nh)
+{
+ if (!mComputed)
+ {
+ computeSizes();
+ mComputed = true;
+ }
+
+ nw = (nw == 0 ? mSize[0] + 2 * mPadding : nw);
+ nh = (nh == 0 ? mSize[1] + 2 * mPadding : nh);
+ LayoutCell::reflow(0, 0, nw, nh);
+}
diff --git a/src/gui/widgets/layout.h b/src/gui/widgets/layout.h
new file mode 100644
index 000000000..4c1e40bb9
--- /dev/null
+++ b/src/gui/widgets/layout.h
@@ -0,0 +1,319 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WIDGET_LAYOUT_H
+#define WIDGET_LAYOUT_H
+
+#include <guichan/widgets/container.hpp>
+
+#include <vector>
+
+class LayoutCell;
+
+/**
+ * This class is a helper for adding widgets to nested tables in a window.
+ */
+class ContainerPlacer
+{
+ public:
+ ContainerPlacer(gcn::Container *c = NULL, LayoutCell *l = NULL):
+ mContainer(c), mCell(l)
+ {}
+
+ /**
+ * Gets the pointed cell.
+ */
+ LayoutCell &getCell()
+ { return *mCell; }
+
+ /**
+ * Returns a placer for the same container but to an inner cell.
+ */
+ ContainerPlacer at(int x, int y);
+
+ /**
+ * Adds the given widget to the container and places it in the layout.
+ * @see LayoutArray::place
+ */
+ LayoutCell &operator()
+ (int x, int y, gcn::Widget *, int w = 1, int h = 1);
+
+ private:
+ gcn::Container *mContainer;
+ LayoutCell *mCell;
+};
+
+/**
+ * This class contains a rectangular array of cells.
+ */
+class LayoutArray
+{
+ friend class LayoutCell;
+
+ public:
+
+ LayoutArray();
+
+ ~LayoutArray();
+
+ /**
+ * Returns a reference on the cell at given position.
+ */
+ LayoutCell &at(int x, int y, int w = 1, int h = 1);
+
+ /**
+ * Places a widget in a given cell.
+ * @param w number of columns the widget spawns.
+ * @param h number of rows the widget spawns.
+ * @note When @a w is 1, the width of column @a x is reset to zero if
+ * it was AUTO_DEF. Similarly for @a h.
+ */
+ LayoutCell &place(gcn::Widget *, int x, int y, int w = 1, int h = 1);
+
+ /**
+ * Sets the minimum width of a column.
+ */
+ void setColWidth(int n, int w);
+
+ /**
+ * Sets the minimum height of a row.
+ */
+ void setRowHeight(int n, int h);
+
+ /**
+ * Sets the widths of two columns to the maximum of their widths.
+ */
+ void matchColWidth(int n1, int n2);
+
+ /**
+ * Spawns a cell over several columns/rows.
+ */
+ void extend(int x, int y, int w, int h);
+
+ /**
+ * Computes and sets the positions of all the widgets.
+ * @param nW width of the array, used to resize the AUTO_ columns.
+ * @param nH height of the array, used to resize the AUTO_ rows.
+ */
+ void reflow(int nX, int nY, int nW, int nH);
+
+ private:
+
+ // Copy not allowed, as the array owns all its cells.
+ LayoutArray(LayoutArray const &);
+ LayoutArray &operator=(LayoutArray const &);
+
+ /**
+ * Gets the position and size of a widget along a given axis
+ */
+ void align(int &pos, int &size, int dim,
+ LayoutCell const &cell, int *sizes) const;
+
+ /**
+ * Ensures the private vectors are large enough.
+ */
+ void resizeGrid(int w, int h);
+
+ /**
+ * Gets the column/row sizes along a given axis.
+ * @param upp target size for the array. Ignored if AUTO_DEF.
+ */
+ std::vector<int> getSizes(int dim, int upp) const;
+
+ /**
+ * Gets the total size along a given axis.
+ */
+ int getSize(int dim) const;
+
+ std::vector<int> mSizes[2];
+ std::vector< std::vector < LayoutCell * > > mCells;
+
+ int mSpacing;
+};
+
+/**
+ * This class describes the formatting of a widget in the cell of a layout
+ * table. Horizontally, a widget can either fill the width of the cell (minus
+ * the cell padding), or it can retain its size and be flushed left, or flush
+ * right, or centered in the cell. The process is similar for the vertical
+ * alignment, except that top is represented by LEFT and bottom by RIGHT.
+ */
+class LayoutCell
+{
+ friend class Layout;
+ friend class LayoutArray;
+
+ public:
+ enum Alignment
+ {
+ LEFT = 0,
+ RIGHT,
+ CENTER,
+ FILL
+ };
+
+ LayoutCell(): mType(NONE) {}
+
+ ~LayoutCell();
+
+ /**
+ * Sets the padding around the cell content.
+ */
+ LayoutCell &setPadding(int p)
+ { mPadding = p; return *this; }
+
+ /**
+ * Sets the horizontal alignment of the cell content.
+ */
+ LayoutCell &setHAlign(Alignment a)
+ { mAlign[0] = a; return *this; }
+
+ /**
+ * Sets the vertical alignment of the cell content.
+ */
+ LayoutCell &setVAlign(Alignment a)
+ { mAlign[1] = a; return *this; }
+
+ /**
+ * @see LayoutArray::at
+ */
+ LayoutCell &at(int x, int y)
+ { return getArray().at(x, y); }
+
+ /**
+ * @see LayoutArray::place
+ */
+ LayoutCell &place(gcn::Widget *wg, int x, int y, int w = 1, int h = 1)
+ { return getArray().place(wg, x, y, w, h); }
+
+ /**
+ * @see LayoutArray::matchColWidth
+ */
+ void matchColWidth(int n1, int n2)
+ { getArray().matchColWidth(n1, n2); }
+
+ /**
+ * @see LayoutArray::setColWidth
+ */
+ void setColWidth(int n, int w)
+ { getArray().setColWidth(n, w); }
+
+ /**
+ * @see LayoutArray::setRowHeight
+ */
+ void setRowHeight(int n, int h)
+ { getArray().setRowHeight(n, h); }
+
+ /**
+ * @see LayoutArray::extend.
+ */
+ void extend(int x, int y, int w, int h)
+ { getArray().extend(x, y, w, h); }
+
+ /**
+ * Sets the minimum widths and heights of this cell and of all the
+ * inner cells.
+ */
+ void computeSizes();
+
+ private:
+ // Copy not allowed, as the cell may own an array.
+ LayoutCell(LayoutCell const &);
+ LayoutCell &operator=(LayoutCell const &);
+
+ union
+ {
+ gcn::Widget *mWidget;
+ LayoutArray *mArray;
+ };
+
+ enum
+ {
+ NONE = 0,
+ WIDGET,
+ ARRAY
+ };
+
+ /**
+ * Returns the embedded array. Creates it if the cell does not contain
+ * anything yet. Aborts if it contains a widget.
+ */
+ LayoutArray &getArray();
+
+ /**
+ * @see LayoutArray::reflow
+ */
+ void reflow(int nx, int ny, int nw, int nh);
+
+ int mSize[2];
+ int mPadding;
+ int mExtent[2];
+ int mAlign[2];
+ int mNbFill[2];
+ int mType;
+};
+
+/**
+ * This class is an helper for setting the position of widgets. They are
+ * positioned along the cells of some rectangular tables. The layout may either
+ * be a single table or a tree of nested tables.
+ *
+ * The size of a given table column can either be set manually or be chosen
+ * from the widest widget of the column. An empty column has a AUTO_DEF width,
+ * which means it will be extended so that the layout fits its minimum width.
+ *
+ * The process is similar for table rows. By default, there is a spacing of 4
+ * pixels between rows and between columns, and a margin of 6 pixels around the
+ * whole layout.
+ */
+class Layout : public LayoutCell
+{
+ public:
+ Layout();
+
+ /**
+ * Sets the margin around the layout.
+ */
+ void setMargin(int m)
+ { setPadding(m); }
+
+ /**
+ * Sets the positions of all the widgets.
+ * @see LayoutArray::reflow
+ */
+ void reflow(int &nW, int &nH);
+
+ /**
+ * When the minimum size of the layout is less than the available size,
+ * the remaining pixels are equally split amongst the FILL items.
+ */
+ enum
+ {
+ AUTO_DEF = -42, /**< Default value, behaves like AUTO_ADD. */
+ AUTO_SET = -43, /**< Uses the share as the new size. */
+ AUTO_ADD = -44 /**< Adds the share to the current size. */
+ };
+
+ private:
+ bool mComputed;
+};
+
+#endif // WIDGET_LAYOUT_H
diff --git a/src/gui/widgets/layouthelper.cpp b/src/gui/widgets/layouthelper.cpp
new file mode 100644
index 000000000..a12de9bff
--- /dev/null
+++ b/src/gui/widgets/layouthelper.cpp
@@ -0,0 +1,63 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/layouthelper.h"
+
+LayoutHelper::LayoutHelper(gcn::Container *container):
+ mContainer(container)
+{
+ mContainer->addWidgetListener(this);
+}
+
+LayoutHelper::~LayoutHelper()
+{
+ mContainer->removeWidgetListener(this);
+}
+
+Layout &LayoutHelper::getLayout()
+{
+ return mLayout;
+}
+
+LayoutCell &LayoutHelper::place(int x, int y, gcn::Widget *wg, int w, int h)
+{
+ mContainer->add(wg);
+ return mLayout.place(wg, x, y, w, h);
+}
+
+ContainerPlacer LayoutHelper::getPlacer(int x, int y)
+{
+ return ContainerPlacer(mContainer, &mLayout.at(x, y));
+}
+
+void LayoutHelper::reflowLayout(int w, int h)
+{
+ mLayout.reflow(w, h);
+ mContainer->setSize(w, h);
+}
+
+void LayoutHelper::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ const gcn::Rectangle area = mContainer->getChildrenArea();
+ int w = area.width;
+ int h = area.height;
+ mLayout.reflow(w, h);
+}
diff --git a/src/gui/widgets/layouthelper.h b/src/gui/widgets/layouthelper.h
new file mode 100644
index 000000000..033457429
--- /dev/null
+++ b/src/gui/widgets/layouthelper.h
@@ -0,0 +1,90 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LAYOUTHELPER_H
+#define LAYOUTHELPER_H
+
+#include "gui/widgets/layout.h"
+
+#include <guichan/widgetlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+/**
+ * A helper class for adding a layout to a Guichan container widget. The layout
+ * will register itself as a widget listener and relayout the widgets in the
+ * container dynamically on resize.
+ */
+class LayoutHelper : public gcn::WidgetListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ LayoutHelper(gcn::Container *container);
+
+ /**
+ * Destructor.
+ */
+ ~LayoutHelper();
+
+ /**
+ * Gets the layout handler.
+ */
+ Layout &getLayout();
+
+ /**
+ * Computes the position of the widgets according to the current
+ * layout. Resizes the managed container so that the layout fits.
+ *
+ * @note This function is meant to be called with fixed-size
+ * containers.
+ *
+ * @param w if non-zero, force the container to this width.
+ * @param h if non-zero, force the container to this height.
+ */
+ void reflowLayout(int w = 0, int h = 0);
+
+ /**
+ * Adds a widget to the container and sets it at given cell.
+ */
+ LayoutCell &place(int x, int y, gcn::Widget *, int w = 1, int h = 1);
+
+ /**
+ * Returns a proxy for adding widgets in an inner table of the layout.
+ */
+ ContainerPlacer getPlacer(int x, int y);
+
+ /**
+ * Called whenever the managed container changes size.
+ */
+ void widgetResized(const gcn::Event &event);
+
+ private:
+ Layout mLayout; /**< Layout handler */
+ gcn::Container *mContainer; /**< Managed container */
+};
+
+#endif // LAYOUTHELPER_H
diff --git a/src/gui/widgets/linkhandler.h b/src/gui/widgets/linkhandler.h
new file mode 100644
index 000000000..d9d0f1161
--- /dev/null
+++ b/src/gui/widgets/linkhandler.h
@@ -0,0 +1,42 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LINK_HANDLER_H
+#define LINK_HANDLER_H
+
+#include <string>
+
+#include <guichan/mouselistener.hpp>
+
+/**
+ * A simple interface to windows that need to handle links from BrowserBox
+ * widget.
+ */
+class LinkHandler
+{
+ public:
+ virtual ~LinkHandler() { }
+
+ virtual void handleLink(const std::string &link,
+ gcn::MouseEvent *event) = 0;
+};
+
+#endif
diff --git a/src/gui/widgets/listbox.cpp b/src/gui/widgets/listbox.cpp
new file mode 100644
index 000000000..8dcc4ae67
--- /dev/null
+++ b/src/gui/widgets/listbox.cpp
@@ -0,0 +1,146 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/listbox.h"
+
+#include "client.h"
+#include "configuration.h"
+
+#include "gui/palette.h"
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+
+#include <guichan/font.hpp>
+#include <guichan/graphics.hpp>
+#include <guichan/key.hpp>
+#include <guichan/listmodel.hpp>
+
+float ListBox::mAlpha = 1.0;
+
+ListBox::ListBox(gcn::ListModel *listModel):
+ gcn::ListBox(listModel)
+{
+}
+
+ListBox::~ListBox()
+{
+}
+
+void ListBox::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (mAlpha != alpha)
+ mAlpha = alpha;
+}
+
+void ListBox::draw(gcn::Graphics *graphics)
+{
+ if (!mListModel)
+ return;
+
+ updateAlpha();
+
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT,
+ static_cast<int>(mAlpha * 255.0f)));
+ graphics->setFont(getFont());
+
+ const int height = getRowHeight();
+
+ // Draw filled rectangle around the selected list element
+ if (mSelected >= 0)
+ {
+ graphics->fillRectangle(gcn::Rectangle(0, height * mSelected,
+ getWidth(), height));
+ }
+
+ // Draw the list elements
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ for (int i = 0, y = 0; i < mListModel->getNumberOfElements();
+ ++i, y += height)
+ {
+ graphics->drawText(mListModel->getElementAt(i), 1, y);
+ }
+}
+
+void ListBox::keyPressed(gcn::KeyEvent& keyEvent)
+{
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE)
+ {
+ distributeActionEvent();
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::UP)
+ {
+ if (getSelected() > 0)
+ setSelected(mSelected - 1);
+ else if (getSelected() == 0 && mWrappingEnabled && getListModel())
+ setSelected(getListModel()->getNumberOfElements() - 1);
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::DOWN)
+ {
+ if (getSelected() < (getListModel()->getNumberOfElements() - 1))
+ {
+ setSelected(mSelected + 1);
+ }
+ else if (getSelected() == (getListModel()->getNumberOfElements() - 1)
+ && mWrappingEnabled)
+ {
+ setSelected(0);
+ }
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::HOME)
+ {
+ setSelected(0);
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::END && getListModel())
+ {
+ setSelected(getListModel()->getNumberOfElements() - 1);
+ keyEvent.consume();
+ }
+}
+
+// Don't do anything on scrollwheel. ScrollArea will deal with that.
+
+void ListBox::mouseWheelMovedUp(gcn::MouseEvent &mouseEvent _UNUSED_)
+{
+}
+
+void ListBox::mouseWheelMovedDown(gcn::MouseEvent &mouseEvent _UNUSED_)
+{
+}
+
+void ListBox::mouseDragged(gcn::MouseEvent &event)
+{
+ if (event.getButton() != gcn::MouseEvent::LEFT || getRowHeight() == 0)
+ return;
+
+ // Make list selection update on drag, but guard against negative y
+ int y = std::max(0, event.getY());
+ if (getRowHeight())
+ setSelected(y / getRowHeight());
+}
diff --git a/src/gui/widgets/listbox.h b/src/gui/widgets/listbox.h
new file mode 100644
index 000000000..721e1bd36
--- /dev/null
+++ b/src/gui/widgets/listbox.h
@@ -0,0 +1,78 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LISTBOX_H
+#define LISTBOX_H
+
+#include <guichan/widgets/listbox.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class SelectionListener;
+
+/**
+ * A list box, meant to be used inside a scroll area. Same as the Guichan list
+ * box except this one doesn't have a background, instead completely relying
+ * on the scroll area. It also adds selection listener functionality.
+ *
+ * \ingroup GUI
+ */
+class ListBox : public gcn::ListBox
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ListBox(gcn::ListModel *listModel);
+
+ ~ListBox();
+
+ /**
+ * Draws the list box.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Update the alpha value to the graphic components.
+ */
+ void updateAlpha();
+
+ // Inherited from KeyListener
+
+ void keyPressed(gcn::KeyEvent& keyEvent);
+
+ // Inherited from MouseListener
+
+ void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent);
+
+ void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent);
+
+ void mouseDragged(gcn::MouseEvent &event);
+
+ protected:
+ static float mAlpha;
+};
+
+#endif
diff --git a/src/gui/widgets/passwordfield.cpp b/src/gui/widgets/passwordfield.cpp
new file mode 100644
index 000000000..14b924bbd
--- /dev/null
+++ b/src/gui/widgets/passwordfield.cpp
@@ -0,0 +1,36 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "passwordfield.h"
+
+PasswordField::PasswordField(const std::string &text):
+ TextField(text)
+{
+}
+
+void PasswordField::draw(gcn::Graphics *graphics)
+{
+ // std::string uses cow, thus cheap copy
+ const std::string original = mText;
+ mText.assign(mText.length(), '*');
+ TextField::draw(graphics);
+ mText = original;
+}
diff --git a/src/gui/widgets/passwordfield.h b/src/gui/widgets/passwordfield.h
new file mode 100644
index 000000000..619cd8420
--- /dev/null
+++ b/src/gui/widgets/passwordfield.h
@@ -0,0 +1,46 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PASSWORDFIELD_H
+#define PASSWORDFIELD_H
+
+#include "textfield.h"
+
+/**
+ * A password field.
+ *
+ * \ingroup GUI
+ */
+class PasswordField : public TextField
+{
+ public:
+ /**
+ * Constructor, initializes the password field with the given string.
+ */
+ PasswordField(const std::string &text = "");
+
+ /**
+ * Draws the password field.
+ */
+ void draw(gcn::Graphics *graphics);
+};
+
+#endif
diff --git a/src/gui/widgets/playerbox.cpp b/src/gui/widgets/playerbox.cpp
new file mode 100644
index 000000000..e3a660120
--- /dev/null
+++ b/src/gui/widgets/playerbox.cpp
@@ -0,0 +1,120 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/playerbox.h"
+
+#include "animatedsprite.h"
+#include "being.h"
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+int PlayerBox::instances = 0;
+float PlayerBox::mAlpha = 1.0;
+ImageRect PlayerBox::background;
+
+PlayerBox::PlayerBox(const Being *being):
+ mBeing(being)
+{
+ setFrameSize(2);
+
+ if (instances == 0)
+ {
+ // Load the background skin
+ Image *textbox = Theme::getImageFromTheme("deepbox.png");
+ int bggridx[4] = {0, 3, 28, 31};
+ int bggridy[4] = {0, 3, 28, 31};
+ int a = 0, x, y;
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ if (textbox)
+ {
+ background.grid[a] = textbox->getSubImage(
+ bggridx[x], bggridy[y],
+ bggridx[x + 1] - bggridx[x] + 1,
+ bggridy[y + 1] - bggridy[y] + 1);
+ if (background.grid[a])
+ background.grid[a]->setAlpha(Client::getGuiAlpha());
+ }
+ else
+ {
+ background.grid[a] = 0;
+ }
+ a++;
+ }
+ }
+
+ if (textbox)
+ textbox->decRef();
+ }
+
+ instances++;
+}
+
+PlayerBox::~PlayerBox()
+{
+ instances--;
+
+ mBeing = 0;
+
+ if (instances == 0)
+ for_each(background.grid, background.grid + 9, dtor<Image*>());
+}
+
+void PlayerBox::draw(gcn::Graphics *graphics)
+{
+ if (mBeing)
+ {
+ // Draw character
+ const int bs = getFrameSize();
+ const int x = getWidth() / 2 + bs - 16;
+ const int y = getHeight() - bs - 32;
+ mBeing->drawSpriteAt(static_cast<Graphics*>(graphics), x, y);
+ }
+
+ if (Client::getGuiAlpha() != mAlpha)
+ {
+ for (int a = 0; a < 9; a++)
+ {
+ if (background.grid[a])
+ background.grid[a]->setAlpha(Client::getGuiAlpha());
+ }
+ }
+}
+
+void PlayerBox::drawFrame(gcn::Graphics *graphics)
+{
+ int w, h, bs;
+ bs = getFrameSize();
+ w = getWidth() + bs * 2;
+ h = getHeight() + bs * 2;
+
+ static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, background);
+}
diff --git a/src/gui/widgets/playerbox.h b/src/gui/widgets/playerbox.h
new file mode 100644
index 000000000..4505367f8
--- /dev/null
+++ b/src/gui/widgets/playerbox.h
@@ -0,0 +1,74 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PLAYERBOX_H
+#define PLAYERBOX_H
+
+#include <guichan/widgets/scrollarea.hpp>
+
+class Being;
+class ImageRect;
+
+/**
+ * A box showing a player character.
+ *
+ * \ingroup GUI
+ */
+class PlayerBox : public gcn::ScrollArea
+{
+ public:
+ /**
+ * Constructor. Takes the initial player character that this box should
+ * display, which defaults to <code>NULL</code>.
+ */
+ PlayerBox(const Being *being = 0);
+
+ /**
+ * Destructor.
+ */
+ ~PlayerBox();
+
+ /**
+ * Sets a new player character to be displayed by this box. Setting the
+ * player to <code>NULL</code> causes the box not to draw any
+ * character.
+ */
+ void setPlayer(const Being *being) { mBeing = being; }
+
+ /**
+ * Draws the scroll area.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Draws the background and border of the scroll area.
+ */
+ void drawFrame(gcn::Graphics *graphics);
+
+ private:
+ const Being *mBeing; /**< The character used for display */
+
+ static float mAlpha;
+ static int instances;
+ static ImageRect background;
+};
+
+#endif
diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp
new file mode 100644
index 000000000..fa955c8f3
--- /dev/null
+++ b/src/gui/widgets/popup.cpp
@@ -0,0 +1,174 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/popup.h"
+
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/windowcontainer.h"
+
+#include "resources/image.h"
+
+#include <guichan/exception.hpp>
+
+Popup::Popup(const std::string &name, const std::string &skin):
+ mPopupName(name),
+ mMinWidth(100),
+ mMinHeight(40),
+ mMaxWidth(graphics->getWidth()),
+ mMaxHeight(graphics->getHeight())
+{
+ logger->log("Popup::Popup(\"%s\")", name.c_str());
+
+ if (!windowContainer)
+ throw GCN_EXCEPTION("Popup::Popup(): no windowContainer set");
+
+ setPadding(3);
+
+ // Loads the skin
+ mSkin = Theme::instance()->load(skin);
+
+ // Add this window to the window container
+ windowContainer->add(this);
+
+ // Popups are invisible by default
+ setVisible(false);
+}
+
+Popup::~Popup()
+{
+ logger->log("Popup::~Popup(\"%s\")", mPopupName.c_str());
+
+ if (mSkin)
+ mSkin->instances--;
+}
+
+void Popup::setWindowContainer(WindowContainer *wc)
+{
+ windowContainer = wc;
+}
+
+void Popup::draw(gcn::Graphics *graphics)
+{
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder());
+
+ drawChildren(graphics);
+}
+
+gcn::Rectangle Popup::getChildrenArea()
+{
+ return gcn::Rectangle(getPadding(), 0, getWidth() - getPadding() * 2,
+ getHeight() - getPadding() * 2);
+}
+
+void Popup::setContentSize(int width, int height)
+{
+ width += 2 * getPadding();
+ height += 2 * getPadding();
+
+ if (getMinWidth() > width)
+ width = getMinWidth();
+ else if (getMaxWidth() < width)
+ width = getMaxWidth();
+ if (getMinHeight() > height)
+ height = getMinHeight();
+ else if (getMaxHeight() < height)
+ height = getMaxHeight();
+
+ setSize(width, height);
+}
+
+void Popup::setLocationRelativeTo(gcn::Widget *widget)
+{
+ if (!widget)
+ return;
+
+ int wx, wy;
+ int x, y;
+
+ widget->getAbsolutePosition(wx, wy);
+ getAbsolutePosition(x, y);
+
+ setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x),
+ getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y));
+}
+
+void Popup::setMinWidth(int width)
+{
+ mMinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth();
+}
+
+void Popup::setMinHeight(int height)
+{
+ mMinHeight = height > mSkin->getMinHeight() ?
+ height : mSkin->getMinHeight();
+}
+
+void Popup::setMaxWidth(int width)
+{
+ mMaxWidth = width;
+}
+
+void Popup::setMaxHeight(int height)
+{
+ mMaxHeight = height;
+}
+
+void Popup::scheduleDelete()
+{
+ windowContainer->scheduleDelete(this);
+}
+
+void Popup::position(int x, int y)
+{
+ const int distance = 20;
+
+ int posX = std::max(0, x - getWidth() / 2);
+ int posY = y + distance;
+
+ if (posX + getWidth() > graphics->getWidth())
+ posX = graphics->getWidth() - getWidth();
+ if (posY + getHeight() > graphics->getHeight())
+ posY = y - getHeight() - distance;
+
+ setPosition(posX, posY);
+ setVisible(true);
+ requestMoveToTop();
+}
+
+void Popup::mouseMoved(gcn::MouseEvent &event _UNUSED_)
+{
+ if (viewport)
+ viewport->hideBeingPopup();
+}
+
+void Popup::hide()
+{
+ setVisible(false);
+} \ No newline at end of file
diff --git a/src/gui/widgets/popup.h b/src/gui/widgets/popup.h
new file mode 100644
index 000000000..c83368e52
--- /dev/null
+++ b/src/gui/widgets/popup.h
@@ -0,0 +1,174 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef POPUP_H
+#define POPUP_H
+
+#include "configuration.h"
+#include "guichanfwd.h"
+
+#include "gui/widgets/container.h"
+
+#include <guichan/mouselistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Skin;
+class WindowContainer;
+
+/**
+ * A light version of the Window class. Particularly suited for popup type
+ * functionality that doesn't need to be resized or moved around by the mouse
+ * once created, but only needs to display some simple content, like a static
+ * message.
+ *
+ * Popups, in general, shouldn't also need to update their content once
+ * created, although this is not an explicit requirement to use the popup
+ * class.
+ *
+ * \ingroup GUI
+ */
+class Popup : public Container, public gcn::MouseListener
+{
+ public:
+ /**
+ * Constructor. Initializes the title to the given text and hooks
+ * itself into the popup container.
+ *
+ * @param name A human readable name for the popup. Only useful for
+ * debugging purposes.
+ * @param skin The location where the Popup's skin XML can be found.
+ */
+ Popup(const std::string &name = "",
+ const std::string &skin = "window.xml");
+
+ /**
+ * Destructor. Deletes all the added widgets.
+ */
+ ~Popup();
+
+ /**
+ * Sets the window container to be used by new popups.
+ */
+ static void setWindowContainer(WindowContainer *windowContainer);
+
+ /**
+ * Draws the popup.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Sets the size of this popup.
+ */
+ void setContentSize(int width, int height);
+
+ /**
+ * Sets the location relative to the given widget.
+ */
+ void setLocationRelativeTo(gcn::Widget *widget);
+
+ void mouseMoved(gcn::MouseEvent &event);
+
+ /**
+ * Sets the minimum width of the popup.
+ */
+ void setMinWidth(int width);
+
+ int getMinWidth() const { return mMinWidth; }
+
+ /**
+ * Sets the minimum height of the popup.
+ */
+ void setMinHeight(int height);
+
+ int getMinHeight() const { return mMinHeight; }
+
+ /**
+ * Sets the maximum width of the popup.
+ */
+ void setMaxWidth(int width);
+
+ int getMaxWidth() const { return mMaxWidth; }
+
+ /**
+ * Sets the minimum height of the popup.
+ */
+ void setMaxHeight(int height);
+
+ int getMaxHeight() const { return mMaxHeight; }
+
+ /**
+ * Gets the padding of the popup. The padding is the distance between
+ * the popup border and the content.
+ *
+ * @return The padding of the popup.
+ * @see setPadding
+ */
+ int getPadding() const { return mPadding; }
+
+ void setPadding(int padding) { mPadding = padding; }
+
+ /**
+ * Sets the name of the popup. This is only useful for debug purposes.
+ */
+ void setPopupName(const std::string &name)
+ { mPopupName = name; }
+
+ const std::string &getPopupName() const
+ { return mPopupName; }
+
+ /**
+ * Schedule this popup for deletion. It will be deleted at the start
+ * of the next logic update.
+ */
+ void scheduleDelete();
+
+ // Inherited from BasicContainer
+
+ virtual gcn::Rectangle getChildrenArea();
+
+ /**
+ * Sets the location to display the popup. Tries to horizontally center
+ * the popup and provide a vertical buffer between the given point and
+ * the popup. Prevents the popup from extending off-screen, if
+ * possible.
+ */
+ void position(int x, int y);
+
+ void hide();
+
+ private:
+ std::string mPopupName; /**< Name of the popup */
+ int mMinWidth; /**< Minimum popup width */
+ int mMinHeight; /**< Minimum popup height */
+ int mMaxWidth; /**< Maximum popup width */
+ int mMaxHeight; /**< Maximum popup height */
+ int mPadding; /**< Holds the padding of the popup. */
+
+ Skin *mSkin; /**< Skin in use by this popup */
+};
+
+#endif
diff --git a/src/gui/widgets/progressbar.cpp b/src/gui/widgets/progressbar.cpp
new file mode 100644
index 000000000..5275aacf6
--- /dev/null
+++ b/src/gui/widgets/progressbar.cpp
@@ -0,0 +1,225 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/progressbar.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "textrenderer.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+#include <guichan/font.hpp>
+
+ImageRect ProgressBar::mBorder;
+int ProgressBar::mInstances = 0;
+float ProgressBar::mAlpha = 1.0;
+
+ProgressBar::ProgressBar(float progress,
+ int width, int height,
+ int color):
+ gcn::Widget(),
+ mSmoothProgress(true),
+ mProgressPalette(color),
+ mSmoothColorChange(true)
+{
+ // The progress value is directly set at load time:
+ if (progress > 1.0f || progress < 0.0f)
+ progress = 1.0f;
+
+ mProgress = progress;
+ mProgressToGo = progress;
+
+ mColor = Theme::getProgressColor(color >= 0 ? color : 0, mProgress);
+ mColorToGo = mColor;
+
+ setSize(width, height);
+
+ if (mInstances == 0)
+ {
+ Image *dBorders = Theme::getImageFromTheme("vscroll_grey.png");
+ if (dBorders)
+ {
+ mBorder.grid[0] = dBorders->getSubImage(0, 0, 4, 4);
+ mBorder.grid[1] = dBorders->getSubImage(4, 0, 3, 4);
+ mBorder.grid[2] = dBorders->getSubImage(7, 0, 4, 4);
+ mBorder.grid[3] = dBorders->getSubImage(0, 4, 4, 10);
+ mBorder.grid[4] = dBorders->getSubImage(4, 4, 3, 10);
+ mBorder.grid[5] = dBorders->getSubImage(7, 4, 4, 10);
+ mBorder.grid[6] = dBorders->getSubImage(0, 15, 4, 4);
+ mBorder.grid[7] = dBorders->getSubImage(4, 15, 3, 4);
+ mBorder.grid[8] = dBorders->getSubImage(7, 15, 4, 4);
+
+ for (int i = 0; i < 9; i++)
+ mBorder.grid[i]->setAlpha(mAlpha);
+
+ dBorders->decRef();
+ }
+ else
+ {
+ for (int f = 0; f < 9; f ++)
+ mBorder.grid[f] = 0;
+ }
+
+ }
+
+ mInstances++;
+}
+
+ProgressBar::~ProgressBar()
+{
+ mInstances--;
+
+ if (mInstances == 0)
+ for_each(mBorder.grid, mBorder.grid + 9, dtor<Image*>());
+}
+
+void ProgressBar::logic()
+{
+ if (mSmoothColorChange && mColorToGo != mColor)
+ {
+ // Smoothly changing the color for a nicer effect.
+ if (mColorToGo.r > mColor.r)
+ mColor.r++;
+ if (mColorToGo.g > mColor.g)
+ mColor.g++;
+ if (mColorToGo.b > mColor.b)
+ mColor.b++;
+ if (mColorToGo.r < mColor.r)
+ mColor.r--;
+ if (mColorToGo.g < mColor.g)
+ mColor.g--;
+ if (mColorToGo.b < mColor.b)
+ mColor.b--;
+ }
+
+ if (mSmoothProgress && mProgressToGo != mProgress)
+ {
+ // Smoothly showing the progressbar changes.
+ if (mProgressToGo > mProgress)
+ mProgress = std::min(1.0f, mProgress + 0.005f);
+ if (mProgressToGo < mProgress)
+ mProgress = std::max(0.0f, mProgress - 0.005f);
+ }
+}
+
+void ProgressBar::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (mAlpha != alpha)
+ {
+ mAlpha = alpha;
+ for (int i = 0; i < 9; i++)
+ {
+ if (mBorder.grid[i])
+ mBorder.grid[i]->setAlpha(mAlpha);
+ }
+ }
+
+}
+
+void ProgressBar::draw(gcn::Graphics *graphics)
+{
+ updateAlpha();
+
+ mColor.a = static_cast<int>(mAlpha * 255);
+
+ gcn::Rectangle rect = getDimension();
+ rect.x = 0;
+ rect.y = 0;
+
+ render(static_cast<Graphics*>(graphics), rect, mColor,
+ mProgress, mText);
+}
+
+void ProgressBar::setProgress(float progress)
+{
+ const float p = std::min(1.0f, std::max(0.0f, progress));
+ mProgressToGo = p;
+
+ if (!mSmoothProgress)
+ mProgress = p;
+
+ if (mProgressPalette >= 0)
+ mColorToGo = Theme::getProgressColor(mProgressPalette, progress);
+}
+
+void ProgressBar::setProgressPalette(int progressPalette)
+{
+ int oldPalette = mProgressPalette;
+ mProgressPalette = progressPalette;
+
+ if (mProgressPalette != oldPalette && mProgressPalette >= 0)
+ mColorToGo = Theme::getProgressColor(mProgressPalette, mProgressToGo);
+}
+
+void ProgressBar::setColor(const gcn::Color &color)
+{
+ mColorToGo = color;
+
+ if (!mSmoothColorChange)
+ mColor = color;
+}
+
+void ProgressBar::render(Graphics *graphics, const gcn::Rectangle &area,
+ const gcn::Color &color, float progress,
+ const std::string &text)
+{
+ gcn::Font *oldFont = graphics->getFont();
+ gcn::Color oldColor = graphics->getColor();
+
+ graphics->drawImageRect(area, mBorder);
+
+ // The bar
+ if (progress > 0)
+ {
+ graphics->setColor(color);
+ graphics->fillRectangle(gcn::Rectangle(static_cast<int>(area.x + 4),
+ static_cast<int>(area.y + 4),
+ static_cast<int>(static_cast<float>(progress)
+ * static_cast<float>(area.width - 8)),
+ static_cast<int>(area.height - 8)));
+ }
+
+ // The label
+ if (!text.empty())
+ {
+ const int textX = area.x + area.width / 2;
+ const int textY = area.y + (area.height - boldFont->getHeight()) / 2;
+
+ TextRenderer::renderText(graphics, text, textX, textY,
+ gcn::Graphics::CENTER,
+ Theme::getThemeColor(Theme::PROGRESS_BAR),
+ gui->getFont(), true, false);
+ }
+
+ graphics->setFont(oldFont);
+ graphics->setColor(oldColor);
+}
diff --git a/src/gui/widgets/progressbar.h b/src/gui/widgets/progressbar.h
new file mode 100644
index 000000000..56bcb0a0f
--- /dev/null
+++ b/src/gui/widgets/progressbar.h
@@ -0,0 +1,139 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PROGRESSBAR_H
+#define PROGRESSBAR_H
+
+#include <guichan/widget.hpp>
+
+#include <string>
+
+class Graphics;
+class ImageRect;
+
+/**
+ * A progress bar.
+ *
+ * \ingroup GUI
+ */
+class ProgressBar : public gcn::Widget
+{
+ public:
+ /**
+ * Constructor, initializes the progress with the given value.
+ */
+ ProgressBar(float progress = 0.0f,
+ int width = 40, int height = 7,
+ int color = -1);
+
+ ~ProgressBar();
+
+ /**
+ * Performs progress bar logic (fading colors)
+ */
+ void logic();
+
+ /**
+ * Update the alpha value to the graphic components.
+ */
+ void updateAlpha();
+
+ /**
+ * Draws the progress bar.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Sets the current progress.
+ */
+ void setProgress(float progress);
+
+ /**
+ * Returns the current progress.
+ */
+ float getProgress() const { return mProgress; }
+
+ /**
+ * Change the ProgressPalette for this ProgressBar to follow or -1 to
+ * disable this and manage color manually.
+ */
+ void setProgressPalette(int progressPalette);
+
+ /**
+ * Change the color of the progress bar.
+ */
+ void setColor(const gcn::Color &color);
+
+ /**
+ * Returns the color of the progress bar.
+ */
+ const gcn::Color &getColor() const { return mColor; }
+
+ /**
+ * Sets the text shown on the progress bar.
+ */
+ void setText(const std::string &text)
+ { mText = text; }
+
+ /**
+ * Returns the text shown on the progress bar.
+ */
+ const std::string &text() const
+ { return mText; }
+
+ /**
+ * Set whether the progress is moved smoothly.
+ */
+ void setSmoothProgress(bool smoothProgress)
+ { mSmoothProgress = smoothProgress; }
+
+ /**
+ * Set whether the color changing is made smoothly.
+ */
+ void setSmoothColorChange(bool smoothColorChange)
+ { mSmoothColorChange = smoothColorChange; }
+
+ /**
+ * Renders a progressbar with the given properties.
+ */
+ static void render(Graphics *graphics, const gcn::Rectangle &area,
+ const gcn::Color &color, float progress,
+ const std::string &text = "");
+
+ private:
+ float mProgress, mProgressToGo;
+ bool mSmoothProgress;
+
+ int mProgressPalette; /** < Entry in ProgressPalette or -1 for none. */
+ gcn::Color mColor;
+ gcn::Color mColorToGo;
+ bool mSmoothColorChange;
+
+ std::string mText;
+
+ static ImageRect mBorder;
+ static int mInstances;
+ static float mAlpha;
+
+ static const gcn::Color TEXT_COLOR;
+};
+
+#endif
diff --git a/src/gui/widgets/progressindicator.cpp b/src/gui/widgets/progressindicator.cpp
new file mode 100644
index 000000000..9f1a8f032
--- /dev/null
+++ b/src/gui/widgets/progressindicator.cpp
@@ -0,0 +1,78 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "progressindicator.h"
+
+#include "graphics.h"
+#include "simpleanimation.h"
+
+#include "gui/theme.h"
+
+#include "resources/animation.h"
+#include "resources/imageset.h"
+#include "resources/resourcemanager.h"
+
+#include <guichan/widgets/label.hpp>
+
+ProgressIndicator::ProgressIndicator()
+{
+ ImageSet *images = Theme::getImageSetFromTheme("progress-indicator.png",
+ 32, 32);
+
+ Animation *anim = new Animation;
+ if (images)
+ {
+ for (ImageSet::size_type i = 0; i < images->size(); ++i)
+ anim->addFrame(images->get(i), 100, 0, 0);
+
+ mIndicator = new SimpleAnimation(anim);
+
+ images->decRef();
+ }
+ else
+ {
+ mIndicator = 0;
+ }
+
+ setSize(32, 32);
+}
+
+ProgressIndicator::~ProgressIndicator()
+{
+ delete mIndicator;
+ mIndicator = 0;
+}
+
+void ProgressIndicator::logic()
+{
+ if (mIndicator)
+ mIndicator->update(10);
+}
+
+void ProgressIndicator::draw(gcn::Graphics *graphics)
+{
+ if (mIndicator)
+ {
+ // Draw the indicator centered on the widget
+ const int x = (getWidth() - 32) / 2;
+ const int y = (getHeight() - 32) / 2;
+ mIndicator->draw(static_cast<Graphics*>(graphics), x, y);
+ }
+}
diff --git a/src/gui/widgets/progressindicator.h b/src/gui/widgets/progressindicator.h
new file mode 100644
index 000000000..b990d8bef
--- /dev/null
+++ b/src/gui/widgets/progressindicator.h
@@ -0,0 +1,45 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PROGRESSINDICATOR_H
+#define PROGRESSINDICATOR_H
+
+#include <guichan/widget.hpp>
+
+class SimpleAnimation;
+
+/**
+ * A widget that indicates progress. Suitable to use instead of a progress bar
+ * in cases where it is unknown how long something is going to take.
+ */
+class ProgressIndicator : public gcn::Widget
+{
+public:
+ ProgressIndicator();
+ ~ProgressIndicator();
+
+ void logic();
+ void draw(gcn::Graphics *graphics);
+
+private:
+ SimpleAnimation *mIndicator;
+};
+
+#endif // PROGRESSINDICATOR_H
diff --git a/src/gui/widgets/radiobutton.cpp b/src/gui/widgets/radiobutton.cpp
new file mode 100644
index 000000000..c9738e3cd
--- /dev/null
+++ b/src/gui/widgets/radiobutton.cpp
@@ -0,0 +1,163 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/radiobutton.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+int RadioButton::instances = 0;
+float RadioButton::mAlpha = 1.0;
+Image *RadioButton::radioNormal;
+Image *RadioButton::radioChecked;
+Image *RadioButton::radioDisabled;
+Image *RadioButton::radioDisabledChecked;
+Image *RadioButton::radioNormalHi;
+Image *RadioButton::radioCheckedHi;
+
+RadioButton::RadioButton(const std::string &caption, const std::string &group,
+ bool marked):
+ gcn::RadioButton(caption, group, marked),
+ mHasMouse(false)
+{
+ if (instances == 0)
+ {
+ radioNormal = Theme::getImageFromTheme("radioout.png");
+ radioChecked = Theme::getImageFromTheme("radioin.png");
+ radioDisabled = Theme::getImageFromTheme("radioout.png");
+ radioDisabledChecked = Theme::getImageFromTheme("radioin.png");
+ radioNormalHi = Theme::getImageFromTheme("radioout_highlight.png");
+ radioCheckedHi = Theme::getImageFromTheme("radioin_highlight.png");
+ if (radioNormal)
+ radioNormal->setAlpha(mAlpha);
+ if (radioChecked)
+ radioChecked->setAlpha(mAlpha);
+ if (radioDisabled)
+ radioDisabled->setAlpha(mAlpha);
+ if (radioDisabledChecked)
+ radioDisabledChecked->setAlpha(mAlpha);
+ if (radioNormalHi)
+ radioNormalHi->setAlpha(mAlpha);
+ if (radioCheckedHi)
+ radioCheckedHi->setAlpha(mAlpha);
+ }
+
+ instances++;
+}
+
+RadioButton::~RadioButton()
+{
+ instances--;
+
+ if (instances == 0)
+ {
+ if (radioNormal)
+ radioNormal->decRef();
+ if (radioChecked)
+ radioChecked->decRef();
+ if (radioDisabled)
+ radioDisabled->decRef();
+ if (radioDisabledChecked)
+ radioDisabledChecked->decRef();
+ if (radioNormalHi)
+ radioNormalHi->decRef();
+ if (radioCheckedHi)
+ radioCheckedHi->decRef();
+ }
+}
+
+void RadioButton::drawBox(gcn::Graphics* graphics)
+{
+ if (Client::getGuiAlpha() != mAlpha)
+ {
+ mAlpha = Client::getGuiAlpha();
+ if (radioNormal)
+ radioNormal->setAlpha(mAlpha);
+ if (radioChecked)
+ radioChecked->setAlpha(mAlpha);
+ if (radioDisabled)
+ radioDisabled->setAlpha(mAlpha);
+ if (radioDisabledChecked)
+ radioDisabledChecked->setAlpha(mAlpha);
+ if (radioNormalHi)
+ radioNormalHi->setAlpha(mAlpha);
+ if (radioCheckedHi)
+ radioCheckedHi->setAlpha(mAlpha);
+ }
+
+ Image *box = NULL;
+
+ if (isEnabled())
+ {
+ if (isSelected())
+ if (mHasMouse)
+ box = radioCheckedHi;
+ else
+ box = radioChecked;
+ else
+ if (mHasMouse)
+ box = radioNormalHi;
+ else
+ box = radioNormal;
+ }
+ else
+ {
+ if (isSelected())
+ box = radioDisabledChecked;
+ else
+ box = radioDisabled;
+ }
+
+ if (box)
+ static_cast<Graphics*>(graphics)->drawImage(box, 2, 2);
+}
+
+void RadioButton::draw(gcn::Graphics* graphics)
+{
+ graphics->pushClipArea(gcn::Rectangle(1, 1, getWidth() - 1,
+ getHeight() - 1));
+
+ drawBox(graphics);
+
+ graphics->popClipArea();
+
+ graphics->setFont(getFont());
+ graphics->setColor(getForegroundColor());
+
+ int h = getHeight() + getHeight() / 2;
+ graphics->drawText(getCaption(), h - 2, 0);
+}
+
+void RadioButton::mouseEntered(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = true;
+}
+
+void RadioButton::mouseExited(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = false;
+}
+
diff --git a/src/gui/widgets/radiobutton.h b/src/gui/widgets/radiobutton.h
new file mode 100644
index 000000000..b7383aa7c
--- /dev/null
+++ b/src/gui/widgets/radiobutton.h
@@ -0,0 +1,85 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RADIOBUTTON_H
+#define RADIOBUTTON_H
+
+#include <guichan/widgets/radiobutton.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+
+/**
+ * Guichan based RadioButton with custom look
+ */
+class RadioButton : public gcn::RadioButton
+{
+ public:
+ /**
+ * Constructor.
+ */
+ RadioButton(const std::string &caption, const std::string &group,
+ bool marked = false);
+
+ /**
+ * Destructor.
+ */
+ ~RadioButton();
+
+ /**
+ * Draws the radiobutton, not the caption.
+ */
+ void drawBox(gcn::Graphics* graphics);
+
+ /**
+ * Implementation of the draw methods.
+ * Thus, avoiding the rhomb around the radio button.
+ */
+ void draw(gcn::Graphics* graphics);
+
+ /**
+ * Called when the mouse enteres the widget area.
+ */
+ void mouseEntered(gcn::MouseEvent& event);
+
+ /**
+ * Called when the mouse leaves the widget area.
+ */
+ void mouseExited(gcn::MouseEvent& event);
+
+ private:
+ static int instances;
+ static float mAlpha;
+ bool mHasMouse;
+ static Image *radioNormal;
+ static Image *radioChecked;
+ static Image *radioDisabled;
+ static Image *radioDisabledChecked;
+ static Image *radioNormalHi;
+ static Image *radioCheckedHi;
+};
+
+#endif // RADIOBUTTON_H
diff --git a/src/gui/widgets/resizegrip.cpp b/src/gui/widgets/resizegrip.cpp
new file mode 100644
index 000000000..e8ccd0740
--- /dev/null
+++ b/src/gui/widgets/resizegrip.cpp
@@ -0,0 +1,82 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/resizegrip.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+#include <guichan/graphics.hpp>
+
+Image *ResizeGrip::gripImage = 0;
+int ResizeGrip::mInstances = 0;
+float ResizeGrip::mAlpha = 1.0;
+
+ResizeGrip::ResizeGrip(const std::string &image)
+{
+ if (mInstances == 0)
+ {
+ // Load the grip image
+ gripImage = Theme::getImageFromTheme(image);
+ if (gripImage)
+ gripImage->setAlpha(mAlpha);
+ }
+
+ mInstances++;
+
+ if (gripImage)
+ {
+ setWidth(gripImage->getWidth() + 2);
+ setHeight(gripImage->getHeight() + 2);
+ }
+ else
+ {
+ setWidth(2);
+ setHeight(2);
+ }
+}
+
+ResizeGrip::~ResizeGrip()
+{
+ mInstances--;
+
+ if (mInstances == 0 && gripImage)
+ gripImage->decRef();
+}
+
+void ResizeGrip::draw(gcn::Graphics *graphics)
+{
+ if (!gripImage)
+ return;
+
+ if (Client::getGuiAlpha() != mAlpha)
+ {
+ mAlpha = Client::getGuiAlpha();
+ gripImage->setAlpha(mAlpha);
+ }
+
+ static_cast<Graphics*>(graphics)->drawImage(gripImage, 0, 0);
+}
diff --git a/src/gui/widgets/resizegrip.h b/src/gui/widgets/resizegrip.h
new file mode 100644
index 000000000..5ef93f297
--- /dev/null
+++ b/src/gui/widgets/resizegrip.h
@@ -0,0 +1,60 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RESIZEGRIP_H
+#define RESIZEGRIP_H
+
+#include <guichan/widget.hpp>
+
+class Image;
+
+/**
+ * Resize grip. The resize grip is part of a resizable Window. It relies on the
+ * fact that uncaught mouse events are automatically routed to the parent
+ * window.
+ *
+ * \ingroup GUI
+ */
+class ResizeGrip : public gcn::Widget
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ResizeGrip(const std::string &image = "resize.png");
+
+ /**
+ * Destructor.
+ */
+ ~ResizeGrip();
+
+ /**
+ * Draws the resize grip.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ private:
+ static Image *gripImage; /**< Resize grip image */
+ static int mInstances; /**< Number of resize grip instances */
+ static float mAlpha;
+};
+
+#endif
diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp
new file mode 100644
index 000000000..187794b1d
--- /dev/null
+++ b/src/gui/widgets/scrollarea.cpp
@@ -0,0 +1,445 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/scrollarea.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+int ScrollArea::instances = 0;
+float ScrollArea::mAlpha = 1.0;
+ImageRect ScrollArea::background;
+ImageRect ScrollArea::vMarker;
+ImageRect ScrollArea::vMarkerHi;
+Image *ScrollArea::buttons[4][2];
+
+ScrollArea::ScrollArea():
+ gcn::ScrollArea(),
+ mX(0),
+ mY(0),
+ mHasMouse(false),
+ mOpaque(true)
+{
+ addWidgetListener(this);
+ init();
+}
+
+ScrollArea::ScrollArea(gcn::Widget *widget):
+ gcn::ScrollArea(widget),
+ mX(0),
+ mY(0),
+ mHasMouse(false),
+ mOpaque(true)
+{
+ init();
+}
+
+ScrollArea::~ScrollArea()
+{
+ // Garbage collection
+ delete getContent();
+
+ instances--;
+
+ if (instances == 0)
+ {
+ for_each(background.grid, background.grid + 9, dtor<Image*>());
+ for_each(vMarker.grid, vMarker.grid + 9, dtor<Image*>());
+ for_each(vMarkerHi.grid, vMarkerHi.grid + 9, dtor<Image*>());
+
+ if (buttons[UP][0])
+ buttons[UP][0]->decRef();
+ if (buttons[UP][1])
+ buttons[UP][1]->decRef();
+ if (buttons[DOWN][0])
+ buttons[DOWN][0]->decRef();
+ if (buttons[DOWN][1])
+ buttons[DOWN][1]->decRef();
+ if (buttons[LEFT][0])
+ buttons[LEFT][0]->decRef();
+ if (buttons[LEFT][1])
+ buttons[LEFT][1]->decRef();
+ if (buttons[RIGHT][0])
+ buttons[RIGHT][0]->decRef();
+ if (buttons[RIGHT][1])
+ buttons[RIGHT][1]->decRef();
+ }
+}
+
+void ScrollArea::init()
+{
+ // Draw background by default
+ setOpaque(true);
+
+ setUpButtonScrollAmount(2);
+ setDownButtonScrollAmount(2);
+ setLeftButtonScrollAmount(2);
+ setRightButtonScrollAmount(2);
+
+ if (instances == 0)
+ {
+ // Load the background skin
+ Image *textbox = Theme::getImageFromTheme("deepbox.png");
+ const int bggridx[4] = {0, 3, 28, 31};
+ const int bggridy[4] = {0, 3, 28, 31};
+ int a = 0, x, y;
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ if (textbox)
+ {
+ background.grid[a] = textbox->getSubImage(
+ bggridx[x], bggridy[y],
+ bggridx[x + 1] - bggridx[x] + 1,
+ bggridy[y + 1] - bggridy[y] + 1);
+ background.grid[a]->setAlpha(
+ Client::getGuiAlpha());
+ }
+ else
+ {
+ background.grid[a] = 0;
+ }
+ a++;
+ }
+ }
+
+ textbox->decRef();
+
+ // Load vertical scrollbar skin
+ Image *vscroll = Theme::getImageFromTheme("vscroll_grey.png");
+ Image *vscrollHi = Theme::getImageFromTheme("vscroll_highlight.png");
+
+ int vsgridx[4] = {0, 4, 7, 11};
+ int vsgridy[4] = {0, 4, 15, 19};
+ a = 0;
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ if (vscroll)
+ {
+ vMarker.grid[a] = vscroll->getSubImage(
+ vsgridx[x], vsgridy[y],
+ vsgridx[x + 1] - vsgridx[x],
+ vsgridy[y + 1] - vsgridy[y]);
+ vMarker.grid[a]->setAlpha(
+ Client::getGuiAlpha());
+ }
+ else
+ {
+ vMarker.grid[a] = 0;
+ }
+ if (vscrollHi)
+ {
+ vMarkerHi.grid[a] = vscrollHi->getSubImage(
+ vsgridx[x], vsgridy[y],
+ vsgridx[x + 1] - vsgridx[x],
+ vsgridy[y + 1] - vsgridy[y]);
+ vMarkerHi.grid[a]->setAlpha(
+ Client::getGuiAlpha());
+ }
+ else
+ {
+ vMarkerHi.grid[a] = 0;
+ }
+ a++;
+ }
+ }
+
+ if (vscroll)
+ vscroll->decRef();
+ if (vscrollHi)
+ vscrollHi->decRef();
+
+ buttons[UP][0] =
+ Theme::getImageFromTheme("vscroll_up_default.png");
+ buttons[DOWN][0] =
+ Theme::getImageFromTheme("vscroll_down_default.png");
+ buttons[LEFT][0] =
+ Theme::getImageFromTheme("hscroll_left_default.png");
+ buttons[RIGHT][0] =
+ Theme::getImageFromTheme("hscroll_right_default.png");
+ buttons[UP][1] =
+ Theme::getImageFromTheme("vscroll_up_pressed.png");
+ buttons[DOWN][1] =
+ Theme::getImageFromTheme("vscroll_down_pressed.png");
+ buttons[LEFT][1] =
+ Theme::getImageFromTheme("hscroll_left_pressed.png");
+ buttons[RIGHT][1] =
+ Theme::getImageFromTheme("hscroll_right_pressed.png");
+ }
+
+ instances++;
+}
+
+void ScrollArea::logic()
+{
+ if (!isVisible())
+ return;
+
+ gcn::ScrollArea::logic();
+ gcn::Widget *content = getContent();
+
+ // When no scrollbar in a certain direction, adapt content size to match
+ // the content dimension exactly.
+ if (content)
+ {
+ if (getHorizontalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER)
+ {
+ content->setWidth(getChildrenArea().width -
+ 2 * content->getFrameSize());
+ }
+ if (getVerticalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER)
+ {
+ content->setHeight(getChildrenArea().height -
+ 2 * content->getFrameSize());
+ }
+ }
+
+ if (mUpButtonPressed)
+ {
+ setVerticalScrollAmount(getVerticalScrollAmount() -
+ mUpButtonScrollAmount);
+ }
+ else if (mDownButtonPressed)
+ {
+ setVerticalScrollAmount(getVerticalScrollAmount() +
+ mDownButtonScrollAmount);
+ }
+ else if (mLeftButtonPressed)
+ {
+ setHorizontalScrollAmount(getHorizontalScrollAmount() -
+ mLeftButtonScrollAmount);
+ }
+ else if (mRightButtonPressed)
+ {
+ setHorizontalScrollAmount(getHorizontalScrollAmount() +
+ mRightButtonScrollAmount);
+ }
+}
+
+void ScrollArea::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (alpha != mAlpha)
+ {
+ mAlpha = alpha;
+ for (int a = 0; a < 9; a++)
+ {
+ if (background.grid[a])
+ background.grid[a]->setAlpha(mAlpha);
+ if (vMarker.grid[a])
+ vMarker.grid[a]->setAlpha(mAlpha);
+ if (vMarkerHi.grid[a])
+ vMarkerHi.grid[a]->setAlpha(mAlpha);
+ }
+ }
+}
+
+void ScrollArea::draw(gcn::Graphics *graphics)
+{
+ if (mVBarVisible)
+ {
+ drawUpButton(graphics);
+ drawDownButton(graphics);
+ drawVBar(graphics);
+ drawVMarker(graphics);
+ }
+
+ if (mHBarVisible)
+ {
+ drawLeftButton(graphics);
+ drawRightButton(graphics);
+ drawHBar(graphics);
+ drawHMarker(graphics);
+ }
+
+ if (mHBarVisible && mVBarVisible)
+ {
+ graphics->setColor(getBaseColor());
+ graphics->fillRectangle(gcn::Rectangle(getWidth() - mScrollbarWidth,
+ getHeight() - mScrollbarWidth,
+ mScrollbarWidth,
+ mScrollbarWidth));
+ }
+
+ updateAlpha();
+
+ drawChildren(graphics);
+}
+
+void ScrollArea::drawFrame(gcn::Graphics *graphics)
+{
+ if (mOpaque)
+ {
+ const int bs = getFrameSize();
+ const int w = getWidth() + bs * 2;
+ const int h = getHeight() + bs * 2;
+
+ static_cast<Graphics*>(graphics)->
+ drawImageRect(0, 0, w, h, background);
+ }
+}
+
+void ScrollArea::setOpaque(bool opaque)
+{
+ mOpaque = opaque;
+ setFrameSize(mOpaque ? 2 : 0);
+}
+
+void ScrollArea::drawButton(gcn::Graphics *graphics, BUTTON_DIR dir)
+{
+ int state = 0;
+ gcn::Rectangle dim;
+
+ switch (dir)
+ {
+ case UP:
+ state = mUpButtonPressed ? 1 : 0;
+ dim = getUpButtonDimension();
+ break;
+ case DOWN:
+ state = mDownButtonPressed ? 1 : 0;
+ dim = getDownButtonDimension();
+ break;
+ case LEFT:
+ state = mLeftButtonPressed ? 1 : 0;
+ dim = getLeftButtonDimension();
+ break;
+ case RIGHT:
+ state = mRightButtonPressed ? 1 : 0;
+ dim = getRightButtonDimension();
+ break;
+ default:
+ logger->log("ScrollArea::drawButton unknown dir: "
+ + toString(static_cast<unsigned>(dir)));
+ break;
+ }
+
+ if (buttons[dir][state])
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImage(buttons[dir][state], dim.x, dim.y);
+ }
+}
+
+void ScrollArea::drawUpButton(gcn::Graphics *graphics)
+{
+ drawButton(graphics, UP);
+}
+
+void ScrollArea::drawDownButton(gcn::Graphics *graphics)
+{
+ drawButton(graphics, DOWN);
+}
+
+void ScrollArea::drawLeftButton(gcn::Graphics *graphics)
+{
+ drawButton(graphics, LEFT);
+}
+
+void ScrollArea::drawRightButton(gcn::Graphics *graphics)
+{
+ drawButton(graphics, RIGHT);
+}
+
+void ScrollArea::drawVBar(gcn::Graphics *graphics)
+{
+ const gcn::Rectangle dim = getVerticalBarDimension();
+ graphics->setColor(gcn::Color(0, 0, 0, 32));
+ graphics->fillRectangle(dim);
+ graphics->setColor(gcn::Color(255, 255, 255));
+}
+
+void ScrollArea::drawHBar(gcn::Graphics *graphics)
+{
+ const gcn::Rectangle dim = getHorizontalBarDimension();
+ graphics->setColor(gcn::Color(0, 0, 0, 32));
+ graphics->fillRectangle(dim);
+ graphics->setColor(gcn::Color(255, 255, 255));
+}
+
+void ScrollArea::drawVMarker(gcn::Graphics *graphics)
+{
+ gcn::Rectangle dim = getVerticalMarkerDimension();
+
+ if ((mHasMouse) && (mX > (getWidth() - getScrollbarWidth())))
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi);
+ }
+ else
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarker);
+ }
+}
+
+void ScrollArea::drawHMarker(gcn::Graphics *graphics)
+{
+ gcn::Rectangle dim = getHorizontalMarkerDimension();
+
+ if ((mHasMouse) && (mY > (getHeight() - getScrollbarWidth())))
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi);
+ }
+ else
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarker);
+ }
+}
+
+void ScrollArea::mouseMoved(gcn::MouseEvent& event)
+{
+ mX = event.getX();
+ mY = event.getY();
+}
+
+void ScrollArea::mouseEntered(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = true;
+}
+
+void ScrollArea::mouseExited(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = false;
+}
+
+void ScrollArea::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ getContent()->setSize(getWidth() - 2 * getFrameSize(),
+ getHeight() - 2 * getFrameSize());
+}
diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h
new file mode 100644
index 000000000..4f6a07f82
--- /dev/null
+++ b/src/gui/widgets/scrollarea.h
@@ -0,0 +1,151 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SCROLLAREA_H
+#define SCROLLAREA_H
+
+#include <guichan/widgets/scrollarea.hpp>
+#include <guichan/widgetlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+class ImageRect;
+
+/**
+ * A scroll area.
+ *
+ * Contrary to Guichan's scroll area, this scroll area takes ownership over its
+ * content. However, it won't delete a previously set content widget when
+ * setContent is called!
+ *
+ * \ingroup GUI
+ */
+class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener
+{
+ public:
+ /**
+ * Constructor that takes no content. Needed for use with the DropDown
+ * class.
+ */
+ ScrollArea();
+
+ /**
+ * Constructor.
+ *
+ * @param content the initial content to show in the scroll area
+ */
+ ScrollArea(gcn::Widget *content);
+
+ /**
+ * Destructor. Also deletes the content.
+ */
+ ~ScrollArea();
+
+ /**
+ * Logic function optionally adapts width or height of contents. This
+ * depends on the scrollbar settings.
+ */
+ void logic();
+
+ /**
+ * Update the alpha value to the graphic components.
+ */
+ void updateAlpha();
+
+ /**
+ * Draws the scroll area.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Draws the background and border of the scroll area.
+ */
+ void drawFrame(gcn::Graphics *graphics);
+
+ /**
+ * Sets whether the widget should draw its background or not.
+ */
+ void setOpaque(bool opaque);
+
+ /**
+ * Returns whether the widget draws its background or not.
+ */
+ bool isOpaque() const { return mOpaque; }
+
+ /**
+ * Called when the mouse moves in the widget area.
+ */
+ void mouseMoved(gcn::MouseEvent& event);
+
+ /**
+ * Called when the mouse enteres the widget area.
+ */
+ void mouseEntered(gcn::MouseEvent& event);
+
+ /**
+ * Called when the mouse leaves the widget area.
+ */
+ void mouseExited(gcn::MouseEvent& event);
+
+ void widgetResized(const gcn::Event &event);
+
+ protected:
+ enum BUTTON_DIR
+ {
+ UP = 0,
+ DOWN,
+ LEFT,
+ RIGHT
+ };
+
+ /**
+ * Initializes the scroll area.
+ */
+ void init();
+
+ void drawButton(gcn::Graphics *graphics, BUTTON_DIR dir);
+ void drawUpButton(gcn::Graphics *graphics);
+ void drawDownButton(gcn::Graphics *graphics);
+ void drawLeftButton(gcn::Graphics *graphics);
+ void drawRightButton(gcn::Graphics *graphics);
+ void drawVBar(gcn::Graphics *graphics);
+ void drawHBar(gcn::Graphics *graphics);
+ void drawVMarker(gcn::Graphics *graphics);
+ void drawHMarker(gcn::Graphics *graphics);
+
+ static int instances;
+ static float mAlpha;
+ static ImageRect background;
+ static ImageRect vMarker;
+ static ImageRect vMarkerHi;
+ static Image *buttons[4][2];
+
+ int mX, mY;
+ bool mHasMouse;
+ bool mOpaque;
+};
+
+#endif
diff --git a/src/gui/widgets/setuptab.cpp b/src/gui/widgets/setuptab.cpp
new file mode 100644
index 000000000..3c10e6d93
--- /dev/null
+++ b/src/gui/widgets/setuptab.cpp
@@ -0,0 +1,31 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/setuptab.h"
+
+SetupTab::SetupTab()
+{
+ setOpaque(false);
+}
+
+void SetupTab::externalUpdated()
+{
+} \ No newline at end of file
diff --git a/src/gui/widgets/setuptab.h b/src/gui/widgets/setuptab.h
new file mode 100644
index 000000000..2d8742123
--- /dev/null
+++ b/src/gui/widgets/setuptab.h
@@ -0,0 +1,64 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_SETUPTAB_H
+#define GUI_SETUPTAB_H
+
+#include "gui/widgets/container.h"
+
+#include <string>
+
+/**
+ * A container for the contents of a tab in the setup window.
+ */
+class SetupTab : public Container
+{
+public:
+ SetupTab();
+
+ const std::string &getName() const
+ { return mName; }
+
+ /**
+ * Called when the Apply button is pressed in the setup window.
+ */
+ virtual void apply() = 0;
+
+ /**
+ * Called when the Cancel button is pressed in the setup window.
+ */
+ virtual void cancel() = 0;
+
+ virtual void externalUpdated();
+
+protected:
+ /**
+ * Sets the name displayed on the tab. Should be set in the
+ * constructor of a subclass.
+ */
+ void setName(const std::string &name)
+ { mName = name; }
+
+private:
+ std::string mName;
+};
+
+#endif
diff --git a/src/gui/widgets/shopitems.cpp b/src/gui/widgets/shopitems.cpp
new file mode 100644
index 000000000..0aff3d5b9
--- /dev/null
+++ b/src/gui/widgets/shopitems.cpp
@@ -0,0 +1,118 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/shopitems.h"
+
+#include "shopitem.h"
+
+#include "utils/dtor.h"
+
+ShopItems::ShopItems(bool mergeDuplicates) :
+ mMergeDuplicates(mergeDuplicates)
+{
+}
+
+ShopItems::~ShopItems()
+{
+ clear();
+}
+
+int ShopItems::getNumberOfElements()
+{
+ return static_cast<int>(mShopItems.size());
+}
+
+std::string ShopItems::getElementAt(int i)
+{
+ if (i < 0 || (unsigned)i >= mShopItems.size() || !mShopItems.at(i))
+ return "";
+
+ return mShopItems.at(i)->getDisplayName();
+}
+
+void ShopItems::addItem(int id, int amount, int price)
+{
+ mShopItems.push_back(new ShopItem(-1, id, amount, price));
+}
+
+void ShopItems::addItemNoDup(int id, int amount, int price)
+{
+ ShopItem *item = findItem(id);
+ if (!item)
+ mShopItems.push_back(new ShopItem(-1, id, amount, price));
+}
+
+void ShopItems::addItem(int inventoryIndex, int id, int quantity, int price)
+{
+ ShopItem *item = 0;
+ if (mMergeDuplicates)
+ item = findItem(id);
+
+ if (item)
+ {
+ item->addDuplicate (inventoryIndex, quantity);
+ }
+ else
+ {
+ item = new ShopItem(inventoryIndex, id, quantity, price);
+ mShopItems.push_back(item);
+ }
+}
+
+ShopItem *ShopItems::at(unsigned int i) const
+{
+ if (i >= mShopItems.size())
+ return 0;
+
+ return mShopItems.at(i);
+}
+
+void ShopItems::erase(unsigned int i)
+{
+ if (i >= mShopItems.size())
+ return;
+
+ mShopItems.erase(mShopItems.begin() + i);
+}
+
+void ShopItems::clear()
+{
+ delete_all(mShopItems);
+ mShopItems.clear();
+}
+
+ShopItem *ShopItems::findItem(int id)
+{
+ ShopItem *item;
+
+ std::vector<ShopItem*>::iterator it = mShopItems.begin();
+ std::vector<ShopItem*>::iterator e = mShopItems.end();
+ while (it != e)
+ {
+ item = *(it);
+ if (item->getId() == id)
+ return item;
+
+ ++it;
+ }
+
+ return 0;
+}
diff --git a/src/gui/widgets/shopitems.h b/src/gui/widgets/shopitems.h
new file mode 100644
index 000000000..ba325bfa5
--- /dev/null
+++ b/src/gui/widgets/shopitems.h
@@ -0,0 +1,120 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SHOPITEMS_H
+#define SHOPITEMS_H
+
+#include <guichan/listmodel.hpp>
+
+#include <string>
+#include <vector>
+
+class ShopItem;
+
+/**
+ * This class handles the list of items available in a shop.
+ *
+ * The addItem routine can automatically check, if an item already exists and
+ * only adds duplicates to the old item, if one is found. The original
+ * distribution of the duplicates can be retrieved from the item.
+ *
+ * This functionality can be enabled in the constructor.
+ */
+class ShopItems : public gcn::ListModel
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @param mergeDuplicates lets the Shop look for duplicate entries and
+ * merges them to one item.
+ */
+ ShopItems(bool mergeDuplicates = false);
+
+ ~ShopItems();
+
+ /**
+ * Adds an item to the list.
+ */
+ void addItem(int id, int amount, int price);
+
+ /**
+ * Adds an item to the list (used by sell dialog). Looks for
+ * duplicate entries, if mergeDuplicates was turned on.
+ *
+ * @param inventoryIndex the inventory index of the item
+ * @param id the id of the item
+ * @param quantity number of available copies of the item
+ * @param price price of the item
+ */
+ void addItem(int inventoryIndex, int id, int amount, int price);
+
+ void addItemNoDup(int id, int amount, int price);
+
+ /**
+ * Returns the number of items in the shop.
+ */
+ int getNumberOfElements();
+
+ /**
+ * Returns the name of item number i in the shop.
+ *
+ * @param i the index to retrieve
+ */
+ std::string getElementAt(int i);
+
+ /**
+ * Returns the item number i in the shop.
+ */
+ ShopItem *at(unsigned int i) const;
+
+ /**
+ * Removes an element from the shop.
+ *
+ * @param i index to remove
+ */
+ void erase(unsigned int i);
+
+ /**
+ * Clears the list of items in the shop.
+ */
+ void clear();
+
+ std::vector<ShopItem*> &items()
+ { return mShopItems; }
+
+ private:
+ /**
+ * Searches the current items in the shop for the specified
+ * id and returns the item if found, or 0 else.
+ *
+ * @return the item found or 0
+ */
+ ShopItem *findItem(int id);
+
+ /** The list of items in the shop. */
+ std::vector<ShopItem*> mShopItems;
+
+ /** Look for duplicate entries on addition. */
+ bool mMergeDuplicates;
+};
+
+#endif // SHOPITEMS_H
diff --git a/src/gui/widgets/shoplistbox.cpp b/src/gui/widgets/shoplistbox.cpp
new file mode 100644
index 000000000..a0577db03
--- /dev/null
+++ b/src/gui/widgets/shoplistbox.cpp
@@ -0,0 +1,185 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/shoplistbox.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "shopitem.h"
+
+#include "gui/itempopup.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/shopitems.h"
+
+#include "resources/image.h"
+
+#include <guichan/font.hpp>
+#include <guichan/listmodel.hpp>
+
+const int ITEM_ICON_SIZE = 32;
+
+float ShopListBox::mAlpha = 1.0;
+
+ShopListBox::ShopListBox(gcn::ListModel *listModel):
+ ListBox(listModel),
+ mPlayerMoney(0),
+ mShopItems(0)
+{
+ mRowHeight = getFont()->getHeight();
+ mPriceCheck = true;
+
+ mItemPopup = new ItemPopup;
+}
+
+ShopListBox::ShopListBox(gcn::ListModel *listModel, ShopItems *shopListModel):
+ ListBox(listModel),
+ mPlayerMoney(0),
+ mShopItems(shopListModel)
+{
+ mRowHeight = std::max(getFont()->getHeight(), ITEM_ICON_SIZE);
+ mPriceCheck = true;
+
+ mItemPopup = new ItemPopup;
+}
+
+void ShopListBox::setPlayersMoney(int money)
+{
+ mPlayerMoney = money;
+}
+
+void ShopListBox::draw(gcn::Graphics *gcnGraphics)
+{
+ if (!mListModel || !mShopItems)
+ return;
+
+ if (Client::getGuiAlpha() != mAlpha)
+ mAlpha = Client::getGuiAlpha();
+
+ int alpha = static_cast<int>(mAlpha * 255.0f);
+ const gcn::Color* highlightColor =
+ &Theme::getThemeColor(Theme::HIGHLIGHT, alpha);
+
+ Graphics *graphics = static_cast<Graphics*>(gcnGraphics);
+
+ graphics->setFont(getFont());
+
+ // Draw the list elements
+ for (int i = 0, y = 0;
+ i < mListModel->getNumberOfElements();
+ ++i, y += mRowHeight)
+ {
+ gcn::Color temp;
+ const gcn::Color* backgroundColor =
+ &Theme::getThemeColor(Theme::BACKGROUND, alpha);
+
+ if (mShopItems && mShopItems->at(i) &&
+ mPlayerMoney < mShopItems->at(i)->getPrice() && mPriceCheck)
+ {
+ if (i != mSelected)
+ {
+ backgroundColor = &Theme::getThemeColor(Theme::SHOP_WARNING,
+ alpha);
+ }
+ else
+ {
+ temp = Theme::getThemeColor(Theme::SHOP_WARNING, alpha);
+ temp.r = (temp.r + highlightColor->r) / 2;
+ temp.g = (temp.g + highlightColor->g) / 2;
+ temp.b = (temp.g + highlightColor->b) / 2;
+ backgroundColor = &temp;
+ }
+ }
+ else if (i == mSelected)
+ {
+ backgroundColor = highlightColor;
+ }
+
+ graphics->setColor(*backgroundColor);
+ graphics->fillRectangle(gcn::Rectangle(0, y, getWidth(), mRowHeight));
+
+ if (mShopItems)
+ {
+ Image *icon = mShopItems->at(i)->getImage();
+ if (icon)
+ {
+ icon->setAlpha(1.0f);
+ graphics->drawImage(icon, 1, y);
+ }
+ }
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ graphics->drawText(mListModel->getElementAt(i), ITEM_ICON_SIZE + 5,
+ y + (ITEM_ICON_SIZE - getFont()->getHeight()) / 2);
+ }
+}
+
+void ShopListBox::adjustSize()
+{
+ if (mListModel)
+ setHeight(mRowHeight * mListModel->getNumberOfElements());
+}
+
+void ShopListBox::setPriceCheck(bool check)
+{
+ mPriceCheck = check;
+}
+
+void ShopListBox::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!mItemPopup)
+ return;
+
+ if (!mShopItems)
+ {
+ mItemPopup->hide();
+ return;
+ }
+
+ int index = event.getY() / mRowHeight;
+
+ if (index < 0 || index >= mShopItems->getNumberOfElements())
+ {
+ mItemPopup->hide();
+ }
+ else
+ {
+ Item *item = mShopItems->at(index);
+ if (item)
+ {
+ mItemPopup->setItem(item);
+ mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
+ }
+ else
+ {
+ mItemPopup->setVisible(false);
+ }
+ }
+}
+
+void ShopListBox::mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_)
+{
+ if (!mItemPopup)
+ return;
+
+ mItemPopup->hide();
+} \ No newline at end of file
diff --git a/src/gui/widgets/shoplistbox.h b/src/gui/widgets/shoplistbox.h
new file mode 100644
index 000000000..ab77c5969
--- /dev/null
+++ b/src/gui/widgets/shoplistbox.h
@@ -0,0 +1,104 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SHOPLISTBOX_H
+#define SHOPLISTBOX_H
+
+#include "gui/widgets/listbox.h"
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class ShopItems;
+class ItemPopup;
+
+/**
+ * A list box, meant to be used inside a scroll area. Same as the Guichan list
+ * box except this one doesn't have a background, instead completely relying
+ * on the scroll area. It also adds selection listener functionality.
+ *
+ * \ingroup GUI
+ */
+class ShopListBox : public ListBox
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ShopListBox(gcn::ListModel *listModel);
+
+ /**
+ * Constructor with shopitems
+ */
+ ShopListBox(gcn::ListModel *listModel, ShopItems *shopListModel);
+
+ /**
+ * Draws the list box.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Returns the height of a row.
+ */
+ unsigned int getRowHeight() const { return mRowHeight; }
+
+ /**
+ * gives information about the current player's money
+ */
+ void setPlayersMoney(int money);
+
+ /**
+ * Adjust List draw size
+ */
+ void adjustSize();
+
+ /**
+ * Set on/off the disabling of too expensive items.
+ * (Good for selling mode.)
+ */
+ void setPriceCheck(bool check);
+
+ void mouseMoved(gcn::MouseEvent &event);
+
+ void mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_);
+
+ private:
+ int mPlayerMoney;
+
+ /**
+ * Keeps another pointer to the same listModel, permitting to
+ * use the ShopItems specific functions.
+ */
+ ShopItems *mShopItems;
+
+ ItemPopup *mItemPopup;
+
+ unsigned int mRowHeight; /**< Row Height */
+
+ static float mAlpha;
+
+ bool mPriceCheck;
+};
+
+#endif // SHOPLISTBOX_H
diff --git a/src/gui/widgets/shortcutcontainer.cpp b/src/gui/widgets/shortcutcontainer.cpp
new file mode 100644
index 000000000..167296410
--- /dev/null
+++ b/src/gui/widgets/shortcutcontainer.cpp
@@ -0,0 +1,67 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/shortcutcontainer.h"
+
+#include "configuration.h"
+
+#include "resources/image.h"
+
+#include "utils/stringutils.h"
+
+float ShortcutContainer::mAlpha = 1.0;
+
+ShortcutContainer::ShortcutContainer():
+ mGridWidth(1),
+ mGridHeight(1)
+{
+}
+
+void ShortcutContainer::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ mGridWidth = getWidth() / mBoxWidth;
+
+ if (mGridWidth < 1)
+ mGridWidth = 1;
+
+ mGridHeight = mMaxItems / mGridWidth;
+
+ if (mMaxItems % mGridWidth != 0 || mGridHeight < 1)
+ ++mGridHeight;
+
+ setHeight(mGridHeight * mBoxHeight);
+}
+
+int ShortcutContainer::getIndexFromGrid(int pointX, int pointY) const
+{
+ const gcn::Rectangle tRect = gcn::Rectangle(0, 0, mGridWidth * mBoxWidth,
+ mGridHeight * mBoxHeight);
+
+ int index = ((pointY / mBoxHeight) * mGridWidth) + pointX / mBoxWidth;
+
+ if (!tRect.isPointInRect(pointX, pointY) ||
+ index >= (int)mMaxItems || index < 0)
+ {
+ index = -1;
+ }
+
+ return index;
+}
diff --git a/src/gui/widgets/shortcutcontainer.h b/src/gui/widgets/shortcutcontainer.h
new file mode 100644
index 000000000..85d08d0b4
--- /dev/null
+++ b/src/gui/widgets/shortcutcontainer.h
@@ -0,0 +1,115 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SHORTCUTCONTAINER_H
+#define SHORTCUTCONTAINER_H
+
+#include <guichan/mouselistener.hpp>
+#include <guichan/widget.hpp>
+#include <guichan/widgetlistener.hpp>
+
+#include "gui/widgets/tab.h"
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+
+/**
+ * A generic shortcut container.
+ *
+ * \ingroup GUI
+ */
+class ShortcutContainer : public gcn::Widget,
+ public gcn::WidgetListener,
+ public gcn::MouseListener
+{
+ public:
+ /**
+ * Constructor. Initializes the shortcut container.
+ */
+ ShortcutContainer();
+
+ /**
+ * Destructor.
+ */
+ ~ShortcutContainer() {}
+
+ /**
+ * Draws the shortcuts
+ */
+ virtual void draw(gcn::Graphics *graphics) = 0;
+
+ /**
+ * Invoked when a widget changes its size. This is used to determine
+ * the new height of the container.
+ */
+ virtual void widgetResized(const gcn::Event &event);
+
+ /**
+ * Handles mouse when dragged.
+ */
+ virtual void mouseDragged(gcn::MouseEvent &event) = 0;
+
+ /**
+ * Handles mouse when pressed.
+ */
+ virtual void mousePressed(gcn::MouseEvent &event) = 0;
+
+ /**
+ * Handles mouse release.
+ */
+ virtual void mouseReleased(gcn::MouseEvent &event) = 0;
+
+ int getMaxItems() const
+ { return mMaxItems; }
+
+ int getBoxWidth() const
+ { return mBoxWidth; }
+
+ int getBoxHeight() const
+ { return mBoxHeight; }
+
+ protected:
+ /**
+ * Gets the index from the grid provided the point is in an item box.
+ *
+ * @param pointX X coordinate of the point.
+ * @param pointY Y coordinate of the point.
+ * @return index on success, -1 on failure.
+ */
+ int getIndexFromGrid(int pointX, int pointY) const;
+
+ Image *mBackgroundImg;
+
+ static float mAlpha;
+
+ unsigned mMaxItems;
+ int mBoxWidth;
+ int mBoxHeight;
+ int mCursorPosX, mCursorPosY;
+ int mGridWidth, mGridHeight;
+};
+
+#endif
diff --git a/src/gui/widgets/slider.cpp b/src/gui/widgets/slider.cpp
new file mode 100644
index 000000000..9513d5308
--- /dev/null
+++ b/src/gui/widgets/slider.cpp
@@ -0,0 +1,298 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/slider.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+Image *Slider::hStart, *Slider::hMid, *Slider::hEnd, *Slider::hGrip;
+Image *Slider::vStart, *Slider::vMid, *Slider::vEnd, *Slider::vGrip;
+Image *Slider::hStartHi, *Slider::hMidHi, *Slider::hEndHi, *Slider::hGripHi;
+Image *Slider::vStartHi, *Slider::vMidHi, *Slider::vEndHi, *Slider::vGripHi;
+float Slider::mAlpha = 1.0;
+int Slider::mInstances = 0;
+
+Slider::Slider(double scaleEnd):
+ gcn::Slider(scaleEnd),
+ mHasMouse(false)
+{
+ init();
+}
+
+Slider::Slider(double scaleStart, double scaleEnd):
+ gcn::Slider(scaleStart, scaleEnd),
+ mHasMouse(false)
+{
+ init();
+}
+
+Slider::~Slider()
+{
+ mInstances--;
+
+ if (mInstances == 0)
+ {
+ delete hStart;
+ delete hMid;
+ delete hEnd;
+ delete hGrip;
+ delete vStart;
+ delete vMid;
+ delete vEnd;
+ delete vGrip;
+ delete hStartHi;
+ delete hMidHi;
+ delete hEndHi;
+ delete hGripHi;
+ delete vStartHi;
+ delete vMidHi;
+ delete vEndHi;
+ delete vGripHi;
+ }
+}
+
+void Slider::init()
+{
+ setFrameSize(0);
+
+ // Load resources
+ if (mInstances == 0)
+ {
+ int x, y, w, h, o1, o2;
+
+ Image *slider = Theme::getImageFromTheme("slider.png");
+ Image *sliderHi = Theme::getImageFromTheme("slider_hilight.png");
+
+ x = 0; y = 0;
+ w = 15; h = 6;
+ o1 = 4; o2 = 11;
+ if (slider)
+ {
+ hStart = slider->getSubImage(x, y, o1 - x, h);
+ hMid = slider->getSubImage(o1, y, o2 - o1, h);
+ hEnd = slider->getSubImage(o2, y, w - o2 + x, h);
+ }
+ else
+ {
+ hStart = 0;
+ hMid = 0;
+ hEnd = 0;
+ }
+ if (sliderHi)
+ {
+ hStartHi = sliderHi->getSubImage(x, y, o1 - x, h);
+ hMidHi = sliderHi->getSubImage(o1, y, o2 - o1, h);
+ hEndHi = sliderHi->getSubImage(o2, y, w - o2 + x, h);
+ }
+ else
+ {
+ hStartHi = 0;
+ hMidHi = 0;
+ hEndHi = 0;
+ }
+
+ x = 6; y = 8;
+ w = 9; h = 10;
+ if (slider)
+ hGrip = slider->getSubImage(x, y, w, h);
+ else
+ hGrip = 0;
+ if (sliderHi)
+ hGripHi = sliderHi->getSubImage(x, y, w, h);
+ else
+ hGripHi = 0;
+
+ x = 0; y = 6;
+ w = 6; h = 21;
+ o1 = 10; o2 = 18;
+ if (slider)
+ {
+ vStart = slider->getSubImage(x, y, w, o1 - y);
+ vMid = slider->getSubImage(x, o1, w, o2 - o1);
+ vEnd = slider->getSubImage(x, o2, w, h - o2 + y);
+ }
+ else
+ {
+ vStart = 0;
+ vMid = 0;
+ vEnd = 0;
+ }
+ if (sliderHi)
+ {
+ vStartHi = sliderHi->getSubImage(x, y, w, o1 - y);
+ vMidHi = sliderHi->getSubImage(x, o1, w, o2 - o1);
+ vEndHi = sliderHi->getSubImage(x, o2, w, h - o2 + y);
+ }
+ else
+ {
+ vStartHi = 0;
+ vMidHi = 0;
+ vEndHi = 0;
+ }
+
+ x = 6; y = 8;
+ w = 9; h = 10;
+ if (slider)
+ vGrip = slider->getSubImage(x, y, w, h);
+ else
+ vGrip = 0;
+
+ if (sliderHi)
+ vGripHi = sliderHi->getSubImage(x, y, w, h);
+ else
+ vGripHi = 0;
+
+ if (slider)
+ slider->decRef();
+ if (sliderHi)
+ sliderHi->decRef();
+ }
+
+ mInstances++;
+
+ if (hGrip)
+ setMarkerLength(hGrip->getWidth());
+}
+
+void Slider::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (alpha != mAlpha)
+ {
+ mAlpha = alpha;
+ if (hStart)
+ hStart->setAlpha(mAlpha);
+ if (hMid)
+ hMid->setAlpha(mAlpha);
+ if (hEnd)
+ hEnd->setAlpha(mAlpha);
+ if (hGrip)
+ hGrip->setAlpha(mAlpha);
+ if (hStartHi)
+ hStartHi->setAlpha(mAlpha);
+ if (hMidHi)
+ hMidHi->setAlpha(mAlpha);
+ if (hEndHi)
+ hEndHi->setAlpha(mAlpha);
+ if (hGripHi)
+ hGripHi->setAlpha(mAlpha);
+
+ if (vStart)
+ vStart->setAlpha(mAlpha);
+ if (vMid)
+ vMid->setAlpha(mAlpha);
+ if (vEnd)
+ vEnd->setAlpha(mAlpha);
+ if (vGrip)
+ vGrip->setAlpha(mAlpha);
+ if (vStartHi)
+ vStartHi->setAlpha(mAlpha);
+ if (vMidHi)
+ vMidHi->setAlpha(mAlpha);
+ if (vEndHi)
+ vEndHi->setAlpha(mAlpha);
+ if (vGripHi)
+ vGripHi->setAlpha(mAlpha);
+ }
+
+}
+
+void Slider::draw(gcn::Graphics *graphics)
+{
+ if (!hStart || !hStartHi)
+ return;
+
+ int w = getWidth();
+ int h = getHeight();
+ int x = 0;
+ int y = mHasMouse ? (h - hStartHi->getHeight()) / 2 :
+ (h - hStart->getHeight()) / 2;
+
+ updateAlpha();
+
+ if (!mHasMouse)
+ {
+ static_cast<Graphics*>(graphics)->drawImage(hStart, x, y);
+
+ w -= hStart->getWidth() + hEnd->getWidth();
+ x += hStart->getWidth();
+
+ if (hMid)
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImagePattern(hMid, x, y, w, hMid->getHeight());
+ }
+
+ x += w;
+ if (hEnd)
+ static_cast<Graphics*>(graphics)->drawImage(hEnd, x, y);
+ }
+ else
+ {
+ static_cast<Graphics*>(graphics)->drawImage(hStartHi, x, y);
+
+ w -= hStartHi->getWidth();
+ if (hEndHi)
+ w -= hEndHi->getWidth();
+ x += hStartHi->getWidth();
+
+ if (hMidHi)
+ {
+ static_cast<Graphics*>(graphics)->
+ drawImagePattern(hMidHi, x, y, w, hMidHi->getHeight());
+ }
+
+ x += w;
+ if (hEndHi)
+ static_cast<Graphics*>(graphics)->drawImage(hEndHi, x, y);
+ }
+
+ drawMarker(graphics);
+}
+
+void Slider::drawMarker(gcn::Graphics *graphics)
+{
+ if (!(mHasMouse?hGripHi:hGrip))
+ return;
+
+ static_cast<Graphics*>(graphics)->
+ drawImage(mHasMouse?hGripHi:hGrip, getMarkerPosition(),
+ (getHeight() - (mHasMouse?hGripHi:hGrip)->getHeight()) / 2);
+}
+
+void Slider::mouseEntered(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = true;
+}
+
+void Slider::mouseExited(gcn::MouseEvent& event _UNUSED_)
+{
+ mHasMouse = false;
+}
+
diff --git a/src/gui/widgets/slider.h b/src/gui/widgets/slider.h
new file mode 100644
index 000000000..be27b73f1
--- /dev/null
+++ b/src/gui/widgets/slider.h
@@ -0,0 +1,98 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SLIDER_H
+#define SLIDER_H
+
+#include <guichan/widgets/slider.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+
+/**
+ * Slider widget. Same as the Guichan slider but with custom look.
+ *
+ * \ingroup GUI
+ */
+class Slider : public gcn::Slider
+{
+ public:
+ /**
+ * Constructor with scale start equal to 0.
+ */
+ Slider(double scaleEnd = 1.0);
+
+ /**
+ * Constructor.
+ */
+ Slider(double scaleStart, double scaleEnd);
+
+ /**
+ * Destructor.
+ */
+ ~Slider();
+
+ /**
+ * Update the alpha value to the graphic components.
+ */
+ void updateAlpha();
+
+ /**
+ * Draws the slider.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Draws the marker.
+ */
+ void drawMarker(gcn::Graphics *graphics);
+
+ /**
+ * Called when the mouse enteres the widget area.
+ */
+ void mouseEntered(gcn::MouseEvent& event);
+
+ /**
+ * Called when the mouse leaves the widget area.
+ */
+ void mouseExited(gcn::MouseEvent& event);
+
+ private:
+ /**
+ * Used to initialize instances.
+ */
+ void init();
+
+ static Image *hStart, *hMid, *hEnd, *hGrip;
+ static Image *vStart, *vMid, *vEnd, *vGrip;
+ static Image *hStartHi, *hMidHi, *hEndHi, *hGripHi;
+ static Image *vStartHi, *vMidHi, *vEndHi, *vGripHi;
+ bool mHasMouse;
+ static float mAlpha;
+ static int mInstances;
+};
+
+#endif
diff --git a/src/gui/widgets/spellshortcutcontainer.cpp b/src/gui/widgets/spellshortcutcontainer.cpp
new file mode 100644
index 000000000..18482369d
--- /dev/null
+++ b/src/gui/widgets/spellshortcutcontainer.cpp
@@ -0,0 +1,285 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gui/widgets/spellshortcutcontainer.h"
+
+#include "gui/inventorywindow.h"
+#include "gui/okdialog.h"
+#include "gui/palette.h"
+#include "gui/shortcutwindow.h"
+#include "gui/spellpopup.h"
+#include "gui/viewport.h"
+#include "gui/textcommandeditor.h"
+#include "gui/theme.h"
+
+#include "configuration.h"
+#include "graphics.h"
+#include "inventory.h"
+#include "spellshortcut.h"
+#include "itemshortcut.h"
+#include "keyboardconfig.h"
+#include "localplayer.h"
+#include "spellmanager.h"
+#include "log.h"
+
+#include "resources/image.h"
+#include "textcommand.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+SpellShortcutContainer::SpellShortcutContainer():
+ ShortcutContainer(),
+ mSpellClicked(false),
+ mSpellMoved(NULL)
+{
+ mBoxWidth = mBoxWidth;
+
+ addMouseListener(this);
+ addWidgetListener(this);
+
+ mSpellPopup = new SpellPopup;
+
+ mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png");
+ if (spellShortcut)
+ mMaxItems = spellShortcut->getSpellsCount();
+ else
+ mMaxItems = 0;
+
+ if (mBackgroundImg)
+ {
+ mBackgroundImg->setAlpha(Client::getGuiAlpha());
+ mBoxHeight = mBackgroundImg->getHeight();
+ mBoxWidth = mBackgroundImg->getWidth();
+ }
+ else
+ {
+ mBoxHeight = 1;
+ mBoxWidth = 1;
+ }
+}
+
+SpellShortcutContainer::~SpellShortcutContainer()
+{
+ if (mBackgroundImg)
+ mBackgroundImg->decRef();
+ mBackgroundImg = 0;
+ delete mSpellPopup;
+ mSpellPopup = 0;
+}
+
+void SpellShortcutContainer::draw(gcn::Graphics *graphics)
+{
+ if (!spellShortcut)
+ return;
+
+ if (Client::getGuiAlpha() != mAlpha)
+ {
+ mAlpha = Client::getGuiAlpha();
+ if (mBackgroundImg)
+ mBackgroundImg->setAlpha(mAlpha);
+ }
+
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ graphics->setColor(gcn::Color(0, 0, 0, 255));
+ graphics->setFont(getFont());
+
+ int selectedId = spellShortcut->getSelectedItem();
+ g->setColor(Theme::getThemeColor(Theme::TEXT));
+
+ for (unsigned i = 0; i < mMaxItems; i++)
+ {
+ const int itemX = (i % mGridWidth) * mBoxWidth;
+ const int itemY = (i / mGridWidth) * mBoxHeight;
+
+ g->drawImage(mBackgroundImg, itemX, itemY);
+
+ int itemId = spellShortcut->getItem(i);
+ if (selectedId >= 0 && itemId == selectedId)
+ {
+ g->drawRectangle(gcn::Rectangle(
+ itemX + 1, itemY + 1,
+ mBoxWidth - 1, mBoxHeight - 1));
+ }
+
+ if (!spellManager)
+ continue;
+
+ TextCommand *spell = spellManager->getSpell(itemId);
+ if (spell)
+ {
+ if (!spell->isEmpty())
+ {
+ Image* image = spell->getImage();
+
+ if (image)
+ {
+ image->setAlpha(1.0f);
+ g->drawImage(image, itemX, itemY);
+ }
+ }
+
+ g->drawText(spell->getSymbol(), itemX + 2,
+ itemY + mBoxHeight / 2, gcn::Graphics::LEFT);
+ }
+ }
+
+ if (mSpellMoved)
+ {
+ // Draw the item image being dragged by the cursor.
+ }
+
+}
+
+void SpellShortcutContainer::mouseDragged(gcn::MouseEvent &event)
+{
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (!mSpellMoved && mSpellClicked)
+ {
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ const int itemId = spellShortcut->getItem(index);
+
+ if (itemId < 0)
+ return;
+ }
+ if (mSpellMoved)
+ {
+ mCursorPosX = event.getX();
+ mCursorPosY = event.getY();
+ }
+ }
+}
+
+void SpellShortcutContainer::mousePressed(gcn::MouseEvent &event)
+{
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ // Stores the selected item if theirs one.
+ }
+ else if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ }
+ else if (event.getButton() == gcn::MouseEvent::MIDDLE)
+ {
+ if (!spellShortcut || !spellManager)
+ return;
+
+ const int itemId = spellShortcut->getItem(index);
+ spellManager->invoke(itemId);
+ }
+}
+
+void SpellShortcutContainer::mouseReleased(gcn::MouseEvent &event)
+{
+ if (!spellShortcut || !spellManager)
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ const int itemId = spellShortcut->getItem(index);
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (itemId < 0)
+ return;
+
+ const int selectedId = spellShortcut->getSelectedItem();
+
+ if (selectedId != itemId)
+ {
+ TextCommand *spell = spellManager->getSpell(itemId);
+ if (spell && !spell->isEmpty())
+ {
+ int num = itemShortcutWindow->getTabIndex();
+ if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num])
+ {
+ itemShortcut[num]->setItemSelected(
+ spell->getId() + SPELL_MIN_ID);
+ }
+ spellShortcut->setItemSelected(spell->getId());
+ }
+ }
+ else
+ {
+ int num = itemShortcutWindow->getTabIndex();
+ if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num])
+ itemShortcut[num]->setItemSelected(-1);
+ spellShortcut->setItemSelected(-1);
+ }
+ }
+ else if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ TextCommand *spell = NULL;
+ if (itemId >= 0)
+ spell = spellManager->getSpell(itemId);
+
+ if (spell && viewport)
+ viewport->showSpellPopup(spell);
+ }
+}
+
+// Show ItemTooltip
+void SpellShortcutContainer::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!mSpellPopup || !spellShortcut || !spellManager)
+ return;
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+
+ if (index == -1)
+ return;
+
+ const int itemId = spellShortcut->getItem(index);
+
+ mSpellPopup->setVisible(false);
+ TextCommand *spell = spellManager->getSpell(itemId);
+ if (spell && !spell->isEmpty())
+ {
+ mSpellPopup->setItem(spell);
+ mSpellPopup->view(viewport->getMouseX(), viewport->getMouseY());
+ }
+ else
+ {
+ mSpellPopup->setVisible(false);
+ }
+}
+
+// Hide SpellTooltip
+void SpellShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ mSpellPopup->setVisible(false);
+}
diff --git a/src/gui/widgets/spellshortcutcontainer.h b/src/gui/widgets/spellshortcutcontainer.h
new file mode 100644
index 000000000..8f1c4b221
--- /dev/null
+++ b/src/gui/widgets/spellshortcutcontainer.h
@@ -0,0 +1,88 @@
+/*
+ * The Mana World
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ *
+ * This file is part of The Mana World.
+ *
+ * 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
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef SPELLSHORTCUTCONTAINER_H
+#define SPELLSHORTCUTCONTAINER_H
+
+#include <guichan/mouselistener.hpp>
+
+#include "gui/widgets/shortcutcontainer.h"
+//#include "textcommand.h"
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Image;
+class SpellPopup;
+class TextCommand;
+
+/**
+ * An item shortcut container. Used to quickly use items.
+ *
+ * \ingroup GUI
+ */
+class SpellShortcutContainer : public ShortcutContainer
+{
+ public:
+ /**
+ * Constructor. Initializes the graphic.
+ */
+ SpellShortcutContainer();
+
+ /**
+ * Destructor.
+ */
+ virtual ~SpellShortcutContainer();
+
+ /**
+ * Draws the items.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Handles mouse when dragged.
+ */
+ void mouseDragged(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse when pressed.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+
+ /**
+ * Handles mouse release.
+ */
+ void mouseReleased(gcn::MouseEvent &event);
+
+ private:
+ void mouseExited(gcn::MouseEvent &event);
+ void mouseMoved(gcn::MouseEvent &event);
+
+ bool mSpellClicked;
+ TextCommand *mSpellMoved;
+ SpellPopup *mSpellPopup;
+};
+
+#endif
diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp
new file mode 100644
index 000000000..43b1d154e
--- /dev/null
+++ b/src/gui/widgets/tab.cpp
@@ -0,0 +1,196 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/tab.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+
+#include "gui/palette.h"
+#include "gui/theme.h"
+
+#include "gui/widgets/tabbedarea.h"
+
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+#include <guichan/widgets/label.hpp>
+
+int Tab::mInstances = 0;
+float Tab::mAlpha = 1.0;
+
+enum
+{
+ TAB_STANDARD = 0, // 0
+ TAB_HIGHLIGHTED, // 1
+ TAB_SELECTED, // 2
+ TAB_UNUSED, // 3
+ TAB_COUNT // 4 - Must be last.
+};
+
+struct TabData
+{
+ char const *file;
+ int gridX;
+ int gridY;
+};
+
+static TabData const data[TAB_COUNT] =
+{
+ { "tab.png", 0, 0 },
+ { "tab_hilight.png", 9, 4 },
+ { "tabselected.png", 16, 19 },
+ { "tab.png", 25, 23 }
+};
+
+ImageRect Tab::tabImg[TAB_COUNT];
+
+Tab::Tab() : gcn::Tab(),
+ mTabColor(&Theme::getThemeColor(Theme::TAB))
+{
+ init();
+}
+
+Tab::~Tab()
+{
+ mInstances--;
+
+ if (mInstances == 0)
+ {
+ for (int mode = 0; mode < TAB_COUNT; mode++)
+ for_each(tabImg[mode].grid, tabImg[mode].grid + 9, dtor<Image*>());
+ }
+}
+
+void Tab::init()
+{
+ setFocusable(false);
+ setFrameSize(0);
+ mFlash = 0;
+
+ if (mInstances == 0)
+ {
+ // Load the skin
+ Image *tab[TAB_COUNT];
+
+ int a, x, y, mode;
+
+ for (mode = 0; mode < TAB_COUNT; mode++)
+ {
+ tab[mode] = Theme::getImageFromTheme(data[mode].file);
+ a = 0;
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ tabImg[mode].grid[a] = tab[mode]->getSubImage(
+ data[x].gridX, data[y].gridY,
+ data[x + 1].gridX - data[x].gridX + 1,
+ data[y + 1].gridY - data[y].gridY + 1);
+ if (tabImg[mode].grid[a])
+ tabImg[mode].grid[a]->setAlpha(mAlpha);
+ a++;
+ }
+ }
+ if (tab[mode])
+ tab[mode]->decRef();
+ }
+ }
+ mInstances++;
+}
+
+void Tab::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ // TODO We don't need to do this for every tab on every draw
+ // Maybe use a config listener to do it as the value changes.
+ if (alpha != mAlpha)
+ {
+ mAlpha = alpha;
+ for (int a = 0; a < 9; a++)
+ {
+ for (int t = 0; t < TAB_COUNT; t++)
+ {
+ if (tabImg[t].grid[a])
+ tabImg[t].grid[a]->setAlpha(mAlpha);
+ }
+ }
+ }
+}
+
+void Tab::draw(gcn::Graphics *graphics)
+{
+ int mode = TAB_STANDARD;
+
+ // check which type of tab to draw
+ if (mTabbedArea)
+ {
+ mLabel->setForegroundColor(*mTabColor);
+ if (mTabbedArea->isTabSelected(this))
+ {
+ mode = TAB_SELECTED;
+ // if tab is selected, it doesnt need to highlight activity
+ mFlash = 0;
+ }
+ else if (mHasMouse)
+ {
+ mode = TAB_HIGHLIGHTED;
+ }
+
+ switch (mFlash)
+ {
+ case 1:
+ mLabel->setForegroundColor(Theme::getThemeColor(
+ Theme::TAB_FLASH));
+ break;
+ case 2:
+ mLabel->setForegroundColor(Theme::getThemeColor(
+ Theme::TAB_PLAYER_FLASH));
+ break;
+ default:
+ break;
+ }
+ }
+
+ updateAlpha();
+
+ // draw tab
+ static_cast<Graphics*>(graphics)->
+ drawImageRect(0, 0, getWidth(), getHeight(), tabImg[mode]);
+
+ // draw label
+ drawChildren(graphics);
+}
+
+void Tab::setTabColor(const gcn::Color *color)
+{
+ mTabColor = color;
+}
+
+void Tab::setFlash(int flash)
+{
+ mFlash = flash;
+}
diff --git a/src/gui/widgets/tab.h b/src/gui/widgets/tab.h
new file mode 100644
index 000000000..b76717bcd
--- /dev/null
+++ b/src/gui/widgets/tab.h
@@ -0,0 +1,80 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TAB_H
+#define TAB_H
+
+#include <guichan/widgets/tab.hpp>
+
+class ImageRect;
+class TabbedArea;
+
+/**
+ * A tab, the same as the Guichan tab in 0.8, but extended to allow
+ * transparency.
+ */
+class Tab : public gcn::Tab
+{
+ public:
+ Tab();
+ ~Tab();
+
+ /**
+ * Update the alpha value to the graphic components.
+ */
+ void updateAlpha();
+
+ /**
+ * Draw the tabbed area.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Set the normal color fo the tab's text.
+ */
+ void setTabColor(const gcn::Color *color);
+
+ /**
+ * Set tab flashing state
+ */
+ void setFlash(int flash);
+
+ int getFlash()
+ { return mFlash; }
+
+ protected:
+ friend class TabbedArea;
+ virtual void setCurrent()
+ { }
+
+ private:
+ /** Load images if no other instances exist yet */
+ void init();
+
+ static ImageRect tabImg[4]; /**< Tab state graphics */
+ static int mInstances; /**< Number of tab instances */
+ static float mAlpha;
+
+ const gcn::Color *mTabColor;
+ int mFlash;
+};
+
+#endif
diff --git a/src/gui/widgets/tabbedarea.cpp b/src/gui/widgets/tabbedarea.cpp
new file mode 100644
index 000000000..232664860
--- /dev/null
+++ b/src/gui/widgets/tabbedarea.cpp
@@ -0,0 +1,221 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/tabbedarea.h"
+
+#include "gui/widgets/tab.h"
+
+#include "log.h"
+
+#include <guichan/widgets/container.hpp>
+
+TabbedArea::TabbedArea() : gcn::TabbedArea()
+{
+ mWidgetContainer->setOpaque(false);
+ addWidgetListener(this);
+
+ widgetResized(NULL);
+}
+
+int TabbedArea::getNumberOfTabs() const
+{
+ return static_cast<int>(mTabs.size());
+}
+
+Tab *TabbedArea::getTab(const std::string &name) const
+{
+ TabContainer::const_iterator itr = mTabs.begin(), itr_end = mTabs.end();
+ while (itr != itr_end)
+ {
+ if ((*itr).first->getCaption() == name)
+ return static_cast<Tab*>((*itr).first);
+
+ ++itr;
+ }
+ return NULL;
+}
+
+void TabbedArea::draw(gcn::Graphics *graphics)
+{
+ if (mTabs.empty())
+ return;
+
+ drawChildren(graphics);
+}
+
+gcn::Widget *TabbedArea::getWidget(const std::string &name) const
+{
+ TabContainer::const_iterator itr = mTabs.begin(), itr_end = mTabs.end();
+ while (itr != itr_end)
+ {
+ if ((*itr).first->getCaption() == name)
+ return (*itr).second;
+
+ ++itr;
+ }
+
+ return NULL;
+}
+
+gcn::Widget *TabbedArea::getCurrentWidget()
+{
+ gcn::Tab *tab = getSelectedTab();
+
+ if (tab)
+ return getWidget(tab->getCaption());
+ else
+ return NULL;
+}
+
+void TabbedArea::addTab(gcn::Tab* tab, gcn::Widget* widget)
+{
+ if (!tab || !widget)
+ return;
+
+ gcn::TabbedArea::addTab(tab, widget);
+
+ int width = getWidth() - 2 * getFrameSize();
+ int height = getHeight() - 2 * getFrameSize() - mTabContainer->getHeight();
+ widget->setSize(width, height);
+}
+
+void TabbedArea::addTab(const std::string &caption, gcn::Widget *widget)
+{
+ Tab *tab = new Tab;
+ tab->setCaption(caption);
+ mTabsToDelete.push_back(tab);
+
+ addTab(tab, widget);
+}
+
+void TabbedArea::removeTab(Tab *tab)
+{
+ int tabIndexToBeSelected = -1;
+
+ if (tab == mSelectedTab)
+ {
+ int index = getSelectedTabIndex();
+
+ if (index == static_cast<int>(mTabs.size()) - 1 && mTabs.size() == 1)
+ tabIndexToBeSelected = -1;
+ else
+ tabIndexToBeSelected = index - 1;
+ }
+
+ TabContainer::iterator iter;
+ for (iter = mTabs.begin(); iter != mTabs.end(); iter++)
+ {
+ if (iter->first == tab)
+ {
+ mTabContainer->remove(tab);
+ mTabs.erase(iter);
+ break;
+ }
+ }
+
+ std::vector<gcn::Tab*>::iterator iter2;
+ for (iter2 = mTabsToDelete.begin(); iter2 != mTabsToDelete.end(); iter2++)
+ {
+ if (*iter2 == tab)
+ {
+ mTabsToDelete.erase(iter2);
+ delete tab;
+ break;
+ }
+ }
+
+ if (tabIndexToBeSelected >= (signed)mTabs.size())
+ tabIndexToBeSelected = mTabs.size() - 1;
+ if (tabIndexToBeSelected < -1)
+ tabIndexToBeSelected = -1;
+
+ if (tabIndexToBeSelected == -1)
+ {
+ mSelectedTab = 0;
+ mWidgetContainer->clear();
+ }
+ else
+ {
+ setSelectedTab(tabIndexToBeSelected);
+ }
+
+ adjustSize();
+ adjustTabPositions();
+}
+
+void TabbedArea::logic()
+{
+ logicChildren();
+}
+
+void TabbedArea::mousePressed(gcn::MouseEvent &mouseEvent)
+{
+ if (mouseEvent.isConsumed())
+ return;
+
+ if (mouseEvent.getButton() == gcn::MouseEvent::LEFT)
+ {
+ gcn::Widget *widget = mTabContainer->getWidgetAt(mouseEvent.getX(),
+ mouseEvent.getY());
+ gcn::Tab *tab = dynamic_cast<gcn::Tab*>(widget);
+
+ if (tab)
+ {
+ setSelectedTab(tab);
+ requestFocus();
+ }
+ }
+}
+
+void TabbedArea::setSelectedTab(gcn::Tab *tab)
+{
+ gcn::TabbedArea::setSelectedTab(tab);
+
+ Tab *newTab = dynamic_cast<Tab*>(tab);
+
+ if (newTab)
+ newTab->setCurrent();
+
+ widgetResized(NULL);
+}
+
+void TabbedArea::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ int width = getWidth() - 2 * getFrameSize()
+ - 2 * mWidgetContainer->getFrameSize();
+ int height = getHeight() - 2 * getFrameSize() - mWidgetContainer->getY()
+ - 2 * mWidgetContainer->getFrameSize();
+ mWidgetContainer->setSize(width, height);
+
+ gcn::Widget *w = getCurrentWidget();
+ if (w)
+ w->setSize(width, height);
+}
+
+/*
+void TabbedArea::moveLeft(gcn::Tab *tab)
+{
+}
+
+void TabbedArea::moveRight(gcn::Tab *tab)
+{
+}
+*/
diff --git a/src/gui/widgets/tabbedarea.h b/src/gui/widgets/tabbedarea.h
new file mode 100644
index 000000000..de2ae4b0a
--- /dev/null
+++ b/src/gui/widgets/tabbedarea.h
@@ -0,0 +1,129 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TABBEDAREA_H
+#define TABBEDAREA_H
+
+#include <guichan/widget.hpp>
+#include <guichan/widgetlistener.hpp>
+#include <guichan/widgets/container.hpp>
+#include <guichan/widgets/tabbedarea.hpp>
+
+#include <string>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Tab;
+
+/**
+ * A tabbed area, the same as the guichan tabbed area in 0.8, but extended
+ */
+class TabbedArea : public gcn::TabbedArea, public gcn::WidgetListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ TabbedArea();
+
+ /**
+ * Draw the tabbed area.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Return how many tabs have been created.
+ *
+ * @todo Remove this method when upgrading to Guichan 0.9.0
+ */
+ int getNumberOfTabs() const;
+
+ /**
+ * Return tab with specified name as caption.
+ */
+ Tab *getTab(const std::string &name) const;
+
+ /**
+ * Returns the widget with the tab that has specified caption
+ */
+ gcn::Widget *getWidget(const std::string &name) const;
+
+ /**
+ * Returns the widget for the current tab
+ */
+ gcn::Widget *getCurrentWidget();
+
+ using gcn::TabbedArea::addTab;
+
+ /**
+ * Add a tab. Overridden since it needs to size the widget.
+ *
+ * @param tab The tab widget for the tab.
+ * @param widget The widget to view when the tab is selected.
+ */
+ void addTab(gcn::Tab* tab, gcn::Widget* widget);
+
+ /**
+ * Add a tab. Overridden since it needs to create an instance of Tab
+ * instead of gcn::Tab.
+ *
+ * @param caption The Caption to display
+ * @param widget The widget to show when tab is selected
+ */
+ void addTab(const std::string &caption, gcn::Widget *widget);
+
+ /**
+ * Overload the remove tab function as it's broken in guichan 0.8.
+ */
+ void removeTab(Tab *tab);
+
+ /**
+ * Overload the logic function since it's broken in guichan 0.8.
+ */
+ void logic();
+
+ int getContainerHeight() const
+ { return mWidgetContainer->getHeight(); }
+
+ using gcn::TabbedArea::setSelectedTab;
+
+ void setSelectedTab(gcn::Tab *tab);
+
+ void widgetResized(const gcn::Event &event);
+
+/*
+ void moveLeft(gcn::Tab *tab);
+
+ void moveRight(gcn::Tab *tab);
+*/
+ // Inherited from MouseListener
+
+ void mousePressed(gcn::MouseEvent &mouseEvent);
+
+ private:
+ typedef std::vector< std::pair<gcn::Tab*, gcn::Widget*> > TabContainer;
+};
+
+#endif
diff --git a/src/gui/widgets/table.cpp b/src/gui/widgets/table.cpp
new file mode 100644
index 000000000..39ef719ef
--- /dev/null
+++ b/src/gui/widgets/table.cpp
@@ -0,0 +1,585 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/table.h"
+
+#include "client.h"
+#include "configuration.h"
+
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+
+#include "utils/dtor.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/graphics.hpp>
+#include <guichan/key.hpp>
+
+float GuiTable::mAlpha = 1.0;
+
+class GuiTableActionListener : public gcn::ActionListener
+{
+public:
+ GuiTableActionListener(GuiTable *_table, gcn::Widget *_widget,
+ int _row, int _column);
+
+ virtual ~GuiTableActionListener();
+
+ 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()
+{
+ if (mWidget)
+ {
+ mWidget->removeActionListener(this);
+ mWidget->_setParent(NULL);
+ }
+}
+
+void GuiTableActionListener::action(const gcn::ActionEvent
+ &actionEvent _UNUSED_)
+{
+ mTable->setSelected(mRow, mColumn);
+ mTable->distributeActionEvent();
+}
+
+
+GuiTable::GuiTable(TableModel *initial_model, gcn::Color background,
+ bool opacity) :
+ mLinewiseMode(false),
+ mWrappingEnabled(false),
+ mOpaque(opacity),
+ mBackgroundColor(background),
+ mModel(NULL),
+ mSelectedRow(0),
+ mSelectedColumn(0),
+ mTopWidget(NULL)
+{
+ setModel(initial_model);
+ setFocusable(true);
+
+ addMouseListener(this);
+ addKeyListener(this);
+}
+
+GuiTable::~GuiTable()
+{
+ uninstallActionListeners();
+ delete mModel;
+ mModel = 0;
+}
+
+TableModel *GuiTable::getModel() const
+{
+ return mModel;
+}
+
+void GuiTable::setModel(TableModel *new_model)
+{
+ if (mModel)
+ {
+ uninstallActionListeners();
+ mModel->removeListener(this);
+ }
+
+ mModel = new_model;
+ installActionListeners();
+
+ if (new_model)
+ {
+ new_model->installListener(this);
+ recomputeDimensions();
+ }
+}
+
+void GuiTable::recomputeDimensions()
+{
+ if (!mModel)
+ return;
+
+ 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() const
+{
+ return mSelectedRow;
+}
+
+int GuiTable::getSelectedColumn() const
+{
+ return mSelectedColumn;
+}
+
+void GuiTable::setLinewiseSelection(bool linewise)
+{
+ mLinewiseMode = linewise;
+}
+
+int GuiTable::getRowHeight() const
+{
+ if (mModel)
+ return mModel->getRowHeight() + 1; // border
+ else
+ return 0;
+}
+
+int GuiTable::getColumnWidth(int i) const
+{
+ if (mModel)
+ return mModel->getColumnWidth(i) + 1; // border
+ else
+ return 0;
+}
+
+void GuiTable::setSelectedRow(int selected)
+{
+ if (!mModel)
+ {
+ mSelectedRow = -1;
+ }
+ else
+ {
+ if (selected < 0 && !mWrappingEnabled)
+ {
+ mSelectedRow = -1;
+ }
+ else if (selected >= mModel->getRows() && mWrappingEnabled)
+ {
+ mSelectedRow = 0;
+ }
+ else if ((selected >= mModel->getRows() && !mWrappingEnabled) ||
+ (selected < 0 && mWrappingEnabled))
+ {
+ mSelectedRow = mModel->getRows() - 1;
+ }
+ else
+ {
+ mSelectedRow = selected;
+ }
+ }
+}
+
+void GuiTable::setSelectedColumn(int selected)
+{
+ if (!mModel)
+ {
+ mSelectedColumn = -1;
+ }
+ else
+ {
+ if ((selected >= mModel->getColumns() && mWrappingEnabled) ||
+ (selected < 0 && !mWrappingEnabled))
+ {
+ mSelectedColumn = 0;
+ }
+ else if ((selected >= mModel->getColumns() && !mWrappingEnabled) ||
+ (selected < 0 && mWrappingEnabled))
+ {
+ mSelectedColumn = mModel->getColumns() - 1;
+ }
+ else
+ {
+ mSelectedColumn = selected;
+ }
+ }
+}
+
+void GuiTable::uninstallActionListeners()
+{
+ delete_all(mActionListeners);
+ mActionListeners.clear();
+}
+
+void GuiTable::installActionListeners()
+{
+ if (!mModel)
+ return;
+
+ 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);
+ if (widget)
+ {
+ mActionListeners.push_back(new GuiTableActionListener(
+ this, widget, row, column));
+ }
+ }
+ }
+
+ _setFocusHandler(_getFocusHandler()); // propagate focus handler to widgets
+}
+
+// -- widget ops
+void GuiTable::draw(gcn::Graphics* graphics)
+{
+ if (!mModel || !getRowHeight())
+ return;
+
+ if (Client::getGuiAlpha() != mAlpha)
+ mAlpha = Client::getGuiAlpha();
+
+ if (mOpaque)
+ {
+ graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND,
+ static_cast<int>(mAlpha * 255.0f)));
+ graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
+ }
+
+ // 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_column1 = mModel->getColumns();
+
+ // 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 + 1 <= last_column1; ++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->setColor(Theme::getThemeColor(Theme::HIGHLIGHT,
+ static_cast<int>(mAlpha * 255.0f)));
+
+ if (mLinewiseMode && r == mSelectedRow && c == 0)
+ {
+ graphics->fillRectangle(gcn::Rectangle(0, y_offset,
+ getWidth(), height));
+ }
+ else if (!mLinewiseMode &&
+ c == mSelectedColumn && r == mSelectedRow)
+ {
+ graphics->fillRectangle(gcn::Rectangle(x_offset, y_offset,
+ width, height));
+ }
+
+ graphics->pushClipArea(bounds);
+ widget->draw(graphics);
+ graphics->popClipArea();
+ }
+
+ x_offset += width;
+ }
+
+ y_offset += height;
+ }
+
+ if (mTopWidget)
+ {
+ gcn::Rectangle bounds = mTopWidget->getDimension();
+ graphics->pushClipArea(bounds);
+ mTopWidget->draw(graphics);
+ graphics->popClipArea();
+ }
+}
+
+void GuiTable::moveToTop(gcn::Widget *widget)
+{
+ gcn::Widget::moveToTop(widget);
+ mTopWidget = widget;
+}
+
+void GuiTable::moveToBottom(gcn::Widget *widget)
+{
+ gcn::Widget::moveToBottom(widget);
+ if (widget == mTopWidget)
+ mTopWidget = NULL;
+}
+
+gcn::Rectangle GuiTable::getChildrenArea() const
+{
+ return gcn::Rectangle(0, 0, getWidth(), getHeight());
+}
+
+// -- KeyListener notifications
+void GuiTable::keyPressed(gcn::KeyEvent& keyEvent)
+{
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE)
+ {
+ distributeActionEvent();
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::UP)
+ {
+ setSelectedRow(mSelectedRow - 1);
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::DOWN)
+ {
+ setSelectedRow(mSelectedRow + 1);
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::LEFT)
+ {
+ setSelectedColumn(mSelectedColumn - 1);
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::RIGHT)
+ {
+ setSelectedColumn(mSelectedColumn + 1);
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::HOME)
+ {
+ setSelectedRow(0);
+ setSelectedColumn(0);
+ keyEvent.consume();
+ }
+ else if (key.getValue() == Key::END && mModel)
+ {
+ setSelectedRow(mModel->getRows() - 1);
+ setSelectedColumn(mModel->getColumns() - 1);
+ keyEvent.consume();
+ }
+}
+
+// -- MouseListener notifications
+void GuiTable::mousePressed(gcn::MouseEvent& mouseEvent)
+{
+ if (!mModel)
+ return;
+
+ if (mouseEvent.getButton() == gcn::MouseEvent::LEFT)
+ {
+ int row = getRowForY(mouseEvent.getY());
+ int column = getColumnForX(mouseEvent.getX());
+
+ if (row > -1 && column > -1 &&
+ row < mModel->getRows() && column < mModel->getColumns())
+ {
+ mSelectedColumn = column;
+ mSelectedRow = row;
+ }
+
+ distributeActionEvent();
+ }
+}
+
+void GuiTable::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent)
+{
+ if (isFocused())
+ {
+ if (getSelectedRow() > 0 || (getSelectedRow() == 0
+ && mWrappingEnabled))
+ {
+ setSelectedRow(getSelectedRow() - 1);
+ }
+
+ mouseEvent.consume();
+ }
+}
+
+void GuiTable::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent)
+{
+ if (isFocused())
+ {
+ setSelectedRow(getSelectedRow() + 1);
+
+ mouseEvent.consume();
+ }
+}
+
+void GuiTable::mouseDragged(gcn::MouseEvent& mouseEvent)
+{
+ if (mouseEvent.getButton() != gcn::MouseEvent::LEFT)
+ return;
+
+ // Make table selection update on drag
+ const int x = std::max(0, mouseEvent.getX());
+ const int y = std::max(0, mouseEvent.getY());
+
+ setSelectedRow(getRowForY(y));
+ setSelectedColumn(getColumnForX(x));
+}
+
+// -- TableModelListener notifications
+void GuiTable::modelUpdated(bool completed)
+{
+ if (completed)
+ {
+ recomputeDimensions();
+ installActionListeners();
+ }
+ else
+ { // before the update?
+ mTopWidget = NULL; // No longer valid in general
+ uninstallActionListeners();
+ }
+}
+
+gcn::Widget *GuiTable::getWidgetAt(int x, int y) const
+{
+ int row = getRowForY(y);
+ int column = getColumnForX(x);
+
+ if (mTopWidget && mTopWidget->getDimension().isPointInRect(x, y))
+ return mTopWidget;
+
+ if (mModel && row > -1 && column > -1)
+ {
+ gcn::Widget *w = mModel->getElementAt(row, column);
+ if (w && w->isFocusable())
+ return w;
+ else
+ return NULL; // Grab the event locally
+ }
+ else
+ return NULL;
+}
+
+int GuiTable::getRowForY(int y) const
+{
+ int row = -1;
+
+ if (getRowHeight() > 0)
+ row = y / getRowHeight();
+
+ if (!mModel || row < 0 || row >= mModel->getRows())
+ return -1;
+ else
+ return row;
+}
+
+int GuiTable::getColumnForX(int x) const
+{
+ if (!mModel)
+ return -1;
+
+ 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)
+{
+// add check for focusHandler. may be need remove it?
+
+ if (!mModel || !focusHandler)
+ return;
+
+ 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/widgets/table.h b/src/gui/widgets/table.h
new file mode 100644
index 000000000..61c7302b2
--- /dev/null
+++ b/src/gui/widgets/table.h
@@ -0,0 +1,195 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TABLE_H
+#define TABLE_H
+
+#include "tablemodel.h"
+
+#include <guichan/keylistener.hpp>
+#include <guichan/mouselistener.hpp>
+#include <guichan/widget.hpp>
+
+#include <vector>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+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
+{
+ // so that the action listener can call distributeActionEvent
+ friend class GuiTableActionListener;
+
+public:
+ GuiTable(TableModel * initial_model = NULL,
+ gcn::Color background = 0xffffff,
+ bool opacity = true);
+
+ virtual ~GuiTable();
+
+ /**
+ * Retrieves the active table model
+ */
+ TableModel *getModel() 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() const;
+
+ int getSelectedColumn() const;
+
+ void setSelectedRow(int selected);
+
+ void setSelectedColumn(int selected);
+
+ bool isWrappingEnabled() const
+ { return mWrappingEnabled; }
+
+ void setWrappingEnabled(bool wrappingEnabled)
+ { mWrappingEnabled = wrappingEnabled; }
+
+ gcn::Rectangle getChildrenArea() const;
+
+ /**
+ * 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 gcn::Widget *getWidgetAt(int x, int y) const;
+
+ 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);
+
+ /**
+ * Sets the table to be opaque, that is sets the table
+ * to display its background.
+ *
+ * @param opaque True if the table should be opaque, false otherwise.
+ */
+ virtual void setOpaque(bool opaque)
+ { mOpaque = opaque; }
+
+ /**
+ * Checks if the table is opaque, that is if the table area displays its
+ * background.
+ *
+ * @return True if the table is opaque, false otherwise.
+ */
+ virtual bool isOpaque() const
+ { return mOpaque; }
+
+ // 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:
+ /** Frees all action listeners on inner widgets. */
+ virtual void uninstallActionListeners();
+ /** Installs all action listeners on inner widgets. */
+ virtual void installActionListeners();
+
+ virtual int getRowHeight() const;
+ virtual int getColumnWidth(int i) const;
+
+private:
+ int getRowForY(int y) const; // -1 on error
+ int getColumnForX(int x) const; // -1 on error
+ void recomputeDimensions();
+ bool mLinewiseMode;
+ bool mWrappingEnabled;
+ bool mOpaque;
+
+ static float mAlpha;
+
+ /**
+ * Holds the background color of the table.
+ */
+ gcn::Color mBackgroundColor;
+
+ TableModel *mModel;
+
+ int mSelectedRow;
+ int mSelectedColumn;
+
+ /** Number of frames to skip upwards when drawing the selected widget. */
+ int mPopFramesNr;
+
+ /** If someone moves a fresh widget to the top, we must display it. */
+ gcn::Widget *mTopWidget;
+
+ /** Vector for compactness; used as a list in practice. */
+ std::vector<GuiTableActionListener *> mActionListeners;
+};
+
+
+#endif // TABLE_H
diff --git a/src/gui/widgets/tablemodel.cpp b/src/gui/widgets/tablemodel.cpp
new file mode 100644
index 000000000..f1d583ef6
--- /dev/null
+++ b/src/gui/widgets/tablemodel.cpp
@@ -0,0 +1,173 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/tablemodel.h"
+
+#include "utils/dtor.h"
+
+#include <guichan/widget.hpp>
+
+void TableModel::installListener(TableModelListener *listener)
+{
+ if (listener)
+ listeners.insert(listener);
+}
+
+void TableModel::removeListener(TableModelListener *listener)
+{
+ if (listener)
+ listeners.erase(listener);
+}
+
+void TableModel::signalBeforeUpdate()
+{
+ for (std::set<TableModelListener *>::const_iterator it = listeners.begin();
+ it != listeners.end(); it++)
+ {
+ (*it)->modelUpdated(false);
+ }
+}
+
+void TableModel::signalAfterUpdate()
+{
+ for (std::set<TableModelListener *>::const_iterator it = listeners.begin();
+ it != listeners.end(); it++)
+ {
+ if (*it)
+ (*it)->modelUpdated(true);
+ }
+}
+
+
+#define WIDGET_AT(row, column) (((row) * mColumns) + (column))
+#define DYN_SIZE(h) ((h) >= 0)
+
+StaticTableModel::StaticTableModel(int row, int column) :
+ mRows(row),
+ mColumns(column),
+ mHeight(1)
+{
+ mTableModel.resize(row * column, NULL);
+ mWidths.resize(column, 1);
+}
+
+StaticTableModel::~StaticTableModel()
+{
+ delete_all(mTableModel);
+ mTableModel.clear();
+}
+
+void StaticTableModel::resize()
+{
+ mRows = getRows();
+ mColumns = getColumns();
+ mTableModel.resize(mRows * mColumns, NULL);
+}
+
+void StaticTableModel::set(int row, int column, gcn::Widget *widget)
+{
+ if (!widget || row >= mRows || row < 0
+ || column >= mColumns || column < 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();
+
+ delete mTableModel[WIDGET_AT(row, column)];
+
+ mTableModel[WIDGET_AT(row, column)] = widget;
+
+ signalAfterUpdate();
+}
+
+gcn::Widget *StaticTableModel::getElementAt(int row, int column) const
+{
+ 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() const
+{
+ return abs(mHeight);
+}
+
+int StaticTableModel::getColumnWidth(int column) const
+{
+ if (column < 0 || column >= mColumns)
+ return 0;
+
+ return abs(mWidths[column]);
+}
+
+int StaticTableModel::getRows() const
+{
+ return mRows;
+}
+
+int StaticTableModel::getColumns() const
+{
+ return mColumns;
+}
+
+int StaticTableModel::getWidth() const
+{
+ int width = 0;
+
+ for (unsigned int i = 0; i < mWidths.size(); i++)
+ width += mWidths[i];
+
+ return width;
+}
+
+int StaticTableModel::getHeight() const
+{
+ return mColumns * mHeight;
+}
+
diff --git a/src/gui/widgets/tablemodel.h b/src/gui/widgets/tablemodel.h
new file mode 100644
index 000000000..2b8729341
--- /dev/null
+++ b/src/gui/widgets/tablemodel.h
@@ -0,0 +1,149 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TABLE_MODEL_H
+#define TABLE_MODEL_H
+
+#include <guichanfwd.h>
+
+#include <set>
+#include <vector>
+
+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;
+
+ virtual ~TableModelListener() {}
+};
+
+/**
+ * A model for a regular table of widgets.
+ */
+class TableModel
+{
+public:
+ virtual ~TableModel()
+ { }
+
+ /**
+ * Determines the number of rows (lines) in the table
+ */
+ virtual int getRows() const = 0;
+
+ /**
+ * Determines the number of columns in each row
+ */
+ virtual int getColumns() const = 0;
+
+ /**
+ * Determines the height for each row
+ */
+ virtual int getRowHeight() const = 0;
+
+ /**
+ * Determines the width of each individual column
+ */
+ virtual int getColumnWidth(int index) const = 0;
+
+ /**
+ * Retrieves the widget stored at the specified location within the table.
+ */
+ virtual gcn::Widget *getElementAt(int row, int column) const = 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();
+
+ /**
+ * Tells all listeners that the table has seen an update
+ */
+ virtual void signalAfterUpdate();
+
+private:
+ std::set<TableModelListener *> listeners;
+};
+
+
+class StaticTableModel : public TableModel
+{
+public:
+ StaticTableModel(int width, int height);
+ virtual ~StaticTableModel();
+
+ /**
+ * 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);
+
+ /**
+ * Resizes the table model
+ */
+ virtual void resize();
+
+ virtual int getRows() const;
+ virtual int getColumns() const;
+ virtual int getRowHeight() const;
+ virtual int getWidth() const;
+ virtual int getHeight() const;
+ virtual int getColumnWidth(int index) const;
+ virtual gcn::Widget *getElementAt(int row, int column) const;
+
+protected:
+ int mRows, mColumns;
+ int mHeight;
+ std::vector<gcn::Widget *> mTableModel;
+ std::vector<int> mWidths;
+};
+
+#endif // TABLE_MODEL_H
diff --git a/src/gui/widgets/textbox.cpp b/src/gui/widgets/textbox.cpp
new file mode 100644
index 000000000..f248f35d2
--- /dev/null
+++ b/src/gui/widgets/textbox.cpp
@@ -0,0 +1,149 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/textbox.h"
+
+#include "gui/theme.h"
+
+#include <guichan/font.hpp>
+
+#include <sstream>
+
+TextBox::TextBox() :
+ mTextColor(&Theme::getThemeColor(Theme::TEXT))
+{
+ setOpaque(false);
+ setFrameSize(0);
+ mMinWidth = getWidth();
+}
+
+void TextBox::setTextWrapped(const std::string &text, int minDimension)
+{
+ // Make sure parent scroll area sets width of this widget
+ if (getParent())
+ getParent()->logic();
+
+ // Take the supplied minimum dimension as a starting point and try to beat it
+ mMinWidth = minDimension;
+
+ std::stringstream wrappedStream;
+ std::string::size_type spacePos, newlinePos, lastNewlinePos = 0;
+ int minWidth = 0;
+ int xpos;
+
+ spacePos = text.rfind(" ", text.size());
+
+ if (spacePos != std::string::npos)
+ {
+ const std::string word = text.substr(spacePos + 1);
+ const int length = getFont()->getWidth(word);
+
+ if (length > mMinWidth)
+ mMinWidth = length;
+ }
+
+ do
+ {
+ // Determine next piece of string to wrap
+ newlinePos = text.find("\n", lastNewlinePos);
+
+ if (newlinePos == std::string::npos)
+ newlinePos = text.size();
+
+ std::string line =
+ text.substr(lastNewlinePos, newlinePos - lastNewlinePos);
+ std::string::size_type lastSpacePos = 0;
+ xpos = 0;
+
+ do
+ {
+ spacePos = line.find(" ", lastSpacePos);
+
+ if (spacePos == std::string::npos)
+ spacePos = line.size();
+
+ std::string word =
+ line.substr(lastSpacePos, spacePos - lastSpacePos);
+
+ int width = getFont()->getWidth(word);
+
+ if (xpos == 0 && width > mMinWidth)
+ {
+ mMinWidth = width;
+ xpos = width;
+ wrappedStream << word;
+ }
+ else if (xpos != 0 && xpos + getFont()->getWidth(" ") + width <=
+ mMinWidth)
+ {
+ xpos += getFont()->getWidth(" ") + width;
+ wrappedStream << " " << word;
+ }
+ else if (lastSpacePos == 0)
+ {
+ xpos += width;
+ wrappedStream << word;
+ }
+ else
+ {
+ if (xpos > minWidth)
+ minWidth = xpos;
+
+ // The window wasn't big enough. Resize it and try again.
+ if (minWidth > mMinWidth)
+ {
+ mMinWidth = minWidth;
+ wrappedStream.clear();
+ wrappedStream.str("");
+ spacePos = 0;
+ lastNewlinePos = 0;
+ newlinePos = text.find("\n", lastNewlinePos);
+ if (newlinePos == std::string::npos)
+ newlinePos = text.size();
+ line = text.substr(lastNewlinePos, newlinePos -
+ lastNewlinePos);
+ width = 0;
+ break;
+ }
+ else
+ {
+ wrappedStream << "\n" << word;
+ }
+ xpos = width;
+ }
+ lastSpacePos = spacePos + 1;
+ }
+ while (spacePos != line.size());
+
+ if (text.find("\n", lastNewlinePos) != std::string::npos)
+ wrappedStream << "\n";
+
+ lastNewlinePos = newlinePos + 1;
+ }
+ while (newlinePos != text.size());
+
+ if (xpos > minWidth)
+ minWidth = xpos;
+
+ mMinWidth = minWidth;
+
+ gcn::TextBox::setText(wrappedStream.str());
+}
diff --git a/src/gui/widgets/textbox.h b/src/gui/widgets/textbox.h
new file mode 100644
index 000000000..dffaf2736
--- /dev/null
+++ b/src/gui/widgets/textbox.h
@@ -0,0 +1,70 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEXTBOX_H
+#define TEXTBOX_H
+
+#include <guichan/widgets/textbox.hpp>
+
+/**
+ * A text box, meant to be used inside a scroll area. Same as the Guichan text
+ * box except this one doesn't have a background or border, instead completely
+ * relying on the scroll area.
+ *
+ * \ingroup GUI
+ */
+class TextBox : public gcn::TextBox
+{
+ public:
+ /**
+ * Constructor.
+ */
+ TextBox();
+
+ inline void setTextColor(const gcn::Color *color)
+ { mTextColor = color; }
+
+ /**
+ * Sets the text after wrapping it to the current width of the widget.
+ */
+ void setTextWrapped(const std::string &text, int minDimension);
+
+ /**
+ * Get the minimum text width for the text box.
+ */
+ int getMinWidth() const
+ { return mMinWidth; }
+
+ /**
+ * Draws the text.
+ */
+ inline void draw(gcn::Graphics *graphics)
+ {
+ setForegroundColor(*mTextColor);
+ gcn::TextBox::draw(graphics);
+ }
+
+ private:
+ int mMinWidth;
+ const gcn::Color *mTextColor;
+};
+
+#endif
diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp
new file mode 100644
index 000000000..9a5b2de33
--- /dev/null
+++ b/src/gui/widgets/textfield.cpp
@@ -0,0 +1,306 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/textfield.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+
+#include "gui/palette.h"
+#include "gui/sdlinput.h"
+#include "gui/theme.h"
+
+#include "resources/image.h"
+
+#include "utils/copynpaste.h"
+#include "utils/dtor.h"
+
+#include <guichan/font.hpp>
+
+#undef DELETE //Win32 compatibility hack
+
+int TextField::instances = 0;
+float TextField::mAlpha = 1.0;
+ImageRect TextField::skin;
+
+TextField::TextField(const std::string &text, bool loseFocusOnTab,
+ gcn::ActionListener* listener, std::string eventId):
+ gcn::TextField(text),
+ mNumeric(false)
+{
+ setFrameSize(2);
+
+ mLoseFocusOnTab = loseFocusOnTab;
+
+ if (instances == 0)
+ {
+ // Load the skin
+ Image *textbox = Theme::getImageFromTheme("deepbox.png");
+ int gridx[4] = {0, 3, 28, 31};
+ int gridy[4] = {0, 3, 28, 31};
+ int a = 0, x, y;
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ if (textbox)
+ {
+ skin.grid[a] = textbox->getSubImage(
+ gridx[x], gridy[y],
+ gridx[x + 1] - gridx[x] + 1,
+ gridy[y + 1] - gridy[y] + 1);
+ if (skin.grid[a])
+ skin.grid[a]->setAlpha(Client::getGuiAlpha());
+ }
+ else
+ {
+ skin.grid[a] = 0;
+ }
+ a++;
+ }
+ }
+
+ if (textbox)
+ textbox->decRef();
+ }
+
+ instances++;
+
+ if (!eventId.empty())
+ setActionEventId(eventId);
+
+ if (listener)
+ addActionListener(listener);
+}
+
+TextField::~TextField()
+{
+ instances--;
+
+ if (instances == 0)
+ for_each(skin.grid, skin.grid + 9, dtor<Image*>());
+}
+
+void TextField::updateAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+
+ if (alpha != mAlpha)
+ {
+ mAlpha = alpha;
+ for (int a = 0; a < 9; a++)
+ {
+ if (skin.grid[a])
+ skin.grid[a]->setAlpha(mAlpha);
+ }
+ }
+}
+
+void TextField::draw(gcn::Graphics *graphics)
+{
+ updateAlpha();
+
+ if (isFocused())
+ {
+ drawCaret(graphics,
+ getFont()->getWidth(mText.substr(0, mCaretPosition)) -
+ mXScroll);
+ }
+
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ graphics->setFont(getFont());
+ graphics->drawText(mText, 1 - mXScroll, 1);
+}
+
+void TextField::drawFrame(gcn::Graphics *graphics)
+{
+ //updateAlpha(); -> Not useful...
+
+ int w, h, bs;
+ bs = getFrameSize();
+ w = getWidth() + bs * 2;
+ h = getHeight() + bs * 2;
+
+ static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, skin);
+}
+
+void TextField::setNumeric(bool numeric)
+{
+ mNumeric = numeric;
+ if (!numeric)
+ return;
+
+ const char *text = mText.c_str();
+ for (const char *textPtr = text; *textPtr; ++textPtr)
+ {
+ if (*textPtr < '0' || *textPtr > '9')
+ {
+ setText(mText.substr(0, textPtr - text));
+ return;
+ }
+ }
+}
+
+int TextField::getValue() const
+{
+ if (!mNumeric)
+ return 0;
+
+ int value = atoi(mText.c_str());
+ if (value < mMinimum)
+ return mMinimum;
+
+ if (value > mMaximum)
+ return mMaximum;
+
+ return value;
+}
+
+void TextField::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ int val = keyEvent.getKey().getValue();
+
+ if (val >= 32)
+ {
+ int l;
+ if (val < 128)
+ l = 1; // 0xxxxxxx
+ else if (val < 0x800)
+ l = 2; // 110xxxxx 10xxxxxx
+ else if (val < 0x10000)
+ l = 3; // 1110xxxx 10xxxxxx 10xxxxxx
+ else
+ l = 4; // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+
+ char buf[4];
+ for (int i = 0; i < l; ++i)
+ {
+ buf[i] = static_cast<char>(val >> (6 * (l - i - 1)));
+ if (i > 0)
+ buf[i] = static_cast<char>((buf[i] & 63) | 128);
+ }
+
+ if (l > 1)
+ buf[0] |= static_cast<char>(255 << (8 - l));
+
+ mText.insert(mCaretPosition, std::string(buf, buf + l));
+ mCaretPosition += l;
+ }
+
+ /* In UTF-8, 10xxxxxx is only used for inner parts of characters. So skip
+ them when processing key presses. */
+
+ switch (val)
+ {
+ case Key::LEFT:
+ {
+ while (mCaretPosition > 0)
+ {
+ --mCaretPosition;
+ if ((mText[mCaretPosition] & 192) != 128)
+ break;
+ }
+ } break;
+
+ case Key::RIGHT:
+ {
+ unsigned sz = static_cast<unsigned>(mText.size());
+ while (mCaretPosition < sz)
+ {
+ ++mCaretPosition;
+ if (mCaretPosition == sz ||
+ (mText[mCaretPosition] & 192) != 128)
+ {
+ break;
+ }
+ }
+ } break;
+
+ case Key::DELETE:
+ {
+ unsigned sz = static_cast<unsigned>(mText.size());
+ while (mCaretPosition < sz)
+ {
+ --sz;
+ mText.erase(mCaretPosition, 1);
+ if (mCaretPosition == sz ||
+ (mText[mCaretPosition] & 192) != 128)
+ {
+ break;
+ }
+ }
+ } break;
+
+ case Key::BACKSPACE:
+ {
+ while (mCaretPosition > 0)
+ {
+ --mCaretPosition;
+ int v = mText[mCaretPosition];
+ mText.erase(mCaretPosition, 1);
+ if ((v & 192) != 128)
+ break;
+ }
+ } break;
+
+ case Key::ENTER:
+ distributeActionEvent();
+ break;
+
+ case Key::HOME:
+ mCaretPosition = 0;
+ break;
+
+ case Key::END:
+ mCaretPosition = static_cast<unsigned>(mText.size());
+ break;
+
+ case Key::TAB:
+ if (mLoseFocusOnTab)
+ return;
+ break;
+
+ case 22: // Control code 22, SYNCHRONOUS IDLE, sent on Ctrl+v
+ handlePaste();
+ break;
+ default:
+ break;
+ }
+
+ keyEvent.consume();
+ fixScroll();
+}
+
+void TextField::handlePaste()
+{
+ std::string text = getText();
+ std::string::size_type caretPos = getCaretPosition();
+
+ if (RetrieveBuffer(text, caretPos))
+ {
+ setText(text);
+ setCaretPosition(static_cast<unsigned>(caretPos));
+ }
+}
diff --git a/src/gui/widgets/textfield.h b/src/gui/widgets/textfield.h
new file mode 100644
index 000000000..b894fdc85
--- /dev/null
+++ b/src/gui/widgets/textfield.h
@@ -0,0 +1,110 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEXTFIELD_H
+#define TEXTFIELD_H
+
+#include <guichan/widgets/textfield.hpp>
+
+class ImageRect;
+class TextField;
+
+/**
+ * A text field.
+ *
+ * \ingroup GUI
+ */
+class TextField : public gcn::TextField
+{
+ public:
+ /**
+ * Constructor, initializes the text field with the given string.
+ */
+ TextField(const std::string &text = "", bool loseFocusOnTab = true,
+ gcn::ActionListener* listener = NULL,
+ std::string eventId = "");
+
+ ~TextField();
+
+ /**
+ * Draws the text field.
+ */
+ virtual void draw(gcn::Graphics *graphics);
+
+ /**
+ * Update the alpha value to the graphic components.
+ */
+ void updateAlpha();
+
+ /**
+ * Draws the background and border.
+ */
+ void drawFrame(gcn::Graphics *graphics);
+
+ /**
+ * Determine whether the field should be numeric or not
+ */
+ void setNumeric(bool numeric);
+
+ /**
+ * Set the range on the field if it is numeric
+ */
+ void setRange(int min, int max)
+ {
+ mMinimum = min;
+ mMaximum = max;
+ }
+
+ /**
+ * Processes one keypress.
+ */
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ /**
+ * Set the minimum value for a range
+ */
+ void setMinimum(int min)
+ { mMinimum = min; }
+
+ /**
+ * Set the maximum value for a range
+ */
+ void setMaximum(int max)
+ { mMaximum = max; }
+
+ /**
+ * Return the value for a numeric field
+ */
+ int getValue() const;
+
+ private:
+ void handlePaste();
+
+ static int instances;
+ static float mAlpha;
+ static ImageRect skin;
+ bool mNumeric;
+ int mMinimum;
+ int mMaximum;
+ bool mLoseFocusOnTab;
+};
+
+#endif
diff --git a/src/gui/widgets/textpreview.cpp b/src/gui/widgets/textpreview.cpp
new file mode 100644
index 000000000..bd38d8a80
--- /dev/null
+++ b/src/gui/widgets/textpreview.cpp
@@ -0,0 +1,82 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2006-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/textpreview.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "textrenderer.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+#include "gui/truetypefont.h"
+
+#include <typeinfo>
+
+float TextPreview::mAlpha = 1.0;
+
+TextPreview::TextPreview(const std::string &text):
+ mText(text)
+{
+ mTextAlpha = false;
+ mFont = gui->getFont();
+ mTextColor = &Theme::getThemeColor(Theme::TEXT);
+ mTextBGColor = NULL;
+ mBGColor = &Theme::getThemeColor(Theme::BACKGROUND);
+ mOpaque = false;
+}
+
+void TextPreview::draw(gcn::Graphics* graphics)
+{
+ if (Client::getGuiAlpha() != mAlpha)
+ mAlpha = Client::getGuiAlpha();
+
+ int alpha = static_cast<int>(mAlpha * 255.0f);
+
+ if (!mTextAlpha)
+ alpha = 255;
+
+ if (mOpaque)
+ {
+ graphics->setColor(gcn::Color(static_cast<int>(mBGColor->r),
+ static_cast<int>(mBGColor->g),
+ static_cast<int>(mBGColor->b),
+ static_cast<int>(mAlpha * 255.0f)));
+ graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
+ }
+
+ if (mTextBGColor && typeid(*mFont) == typeid(TrueTypeFont))
+ {
+ TrueTypeFont *font = static_cast<TrueTypeFont*>(mFont);
+ int x = font->getWidth(mText) + 1 + 2 * ((mOutline || mShadow) ? 1 :0);
+ int y = font->getHeight() + 1 + 2 * ((mOutline || mShadow) ? 1 : 0);
+ graphics->setColor(gcn::Color(static_cast<int>(mTextBGColor->r),
+ static_cast<int>(mTextBGColor->g),
+ static_cast<int>(mTextBGColor->b),
+ static_cast<int>(mAlpha * 255.0f)));
+ graphics->fillRectangle(gcn::Rectangle(1, 1, x, y));
+ }
+
+ TextRenderer::renderText(graphics, mText, 2, 2, gcn::Graphics::LEFT,
+ gcn::Color(mTextColor->r, mTextColor->g,
+ mTextColor->b, alpha),
+ mFont, mOutline, mShadow);
+}
diff --git a/src/gui/widgets/textpreview.h b/src/gui/widgets/textpreview.h
new file mode 100644
index 000000000..a34ab3853
--- /dev/null
+++ b/src/gui/widgets/textpreview.h
@@ -0,0 +1,130 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2006-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEXTPREVIEW_H
+#define TEXTPREVIEW_H
+
+#include <guichan/color.hpp>
+#include <guichan/font.hpp>
+#include <guichan/widget.hpp>
+
+/**
+ * Preview widget for particle colors, etc.
+ */
+class TextPreview : public gcn::Widget
+{
+ public:
+ TextPreview(const std::string &text);
+
+ /**
+ * Sets the color the text is printed in.
+ *
+ * @param color the color to set
+ */
+ inline void setTextColor(const gcn::Color *color)
+ { mTextColor = color; }
+
+ /**
+ * Sets the text to use the set alpha value.
+ *
+ * @param alpha whether to use alpha values for the text or not
+ */
+ inline void useTextAlpha(bool alpha)
+ { mTextAlpha = alpha; }
+
+ /**
+ * Sets the color the text background is drawn in. This is only the
+ * rectangle directly behind the text, not to full widget.
+ *
+ * @param color the color to set
+ */
+ inline void setTextBGColor(const gcn::Color *color)
+ { mTextBGColor = color; }
+
+ /**
+ * Sets the background color of the widget.
+ *
+ * @param color the color to set
+ */
+ inline void setBGColor(const gcn::Color *color)
+ { mBGColor = color; }
+
+ /**
+ * Sets the font to render the text in.
+ *
+ * @param font the font to use.
+ */
+ inline void setFont(gcn::Font *font)
+ { mFont = font; }
+
+ /**
+ * Sets whether to use a shadow while rendering.
+ *
+ * @param shadow true, if a shadow is wanted, false else
+ */
+ inline void setShadow(bool shadow)
+ { mShadow = shadow; }
+
+ /**
+ * Sets whether to use an outline while rendering.
+ *
+ * @param outline true, if an outline is wanted, false else
+ */
+ inline void setOutline(bool outline)
+ { mOutline = outline; }
+
+ /**
+ * Widget's draw method. Does the actual job.
+ *
+ * @param graphics graphics to draw into
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Set opacity for this widget (whether or not to show the background
+ * color)
+ *
+ * @param opaque Whether the widget should be opaque or not
+ */
+ void setOpaque(bool opaque)
+ { mOpaque = opaque; }
+
+ /**
+ * Gets opacity for this widget (whether or not the background color
+ * is shown below the widget)
+ */
+ bool isOpaque() const
+ { return mOpaque; }
+
+ private:
+ gcn::Font *mFont;
+ std::string mText;
+ const gcn::Color *mTextColor;
+ const gcn::Color *mBGColor;
+ const gcn::Color *mTextBGColor;
+ static float mAlpha;
+ bool mTextAlpha;
+ bool mOpaque;
+ bool mShadow;
+ bool mOutline;
+};
+
+#endif
diff --git a/src/gui/widgets/tradetab.cpp b/src/gui/widgets/tradetab.cpp
new file mode 100644
index 000000000..fb4a57fd5
--- /dev/null
+++ b/src/gui/widgets/tradetab.cpp
@@ -0,0 +1,59 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/tradetab.h"
+
+#include "chatlog.h"
+#include "commandhandler.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "gui/theme.h"
+
+#include "net/net.h"
+
+#include "resources/iteminfo.h"
+#include "resources/itemdb.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+TradeTab::TradeTab() :
+ ChatTab(_("Trade"))
+{
+}
+
+TradeTab::~TradeTab()
+{
+}
+
+void TradeTab::handleInput(const std::string &msg)
+{
+ std::string str = "\302\202" + msg;
+ ChatTab::handleInput(str);
+}
+
+void TradeTab::saveToLogFile(std::string &msg)
+{
+ if (chatLogger)
+ chatLogger->log(std::string("#Trade"), std::string(msg));
+}
diff --git a/src/gui/widgets/tradetab.h b/src/gui/widgets/tradetab.h
new file mode 100644
index 000000000..fceeb1e40
--- /dev/null
+++ b/src/gui/widgets/tradetab.h
@@ -0,0 +1,50 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRADETAB_H
+#define TRADETAB_H
+
+#include "gui/widgets/chattab.h"
+
+/**
+ * A tab for a party chat channel.
+ */
+class TradeTab : public ChatTab
+{
+ public:
+ TradeTab();
+
+ ~TradeTab();
+
+ int getType() const
+ { return ChatTab::TAB_TRADE; }
+
+ void saveToLogFile(std::string &msg);
+
+ protected:
+ void handleInput(const std::string &msg);
+};
+
+extern TradeTab *tradeChatTab;
+#endif
+
+
+
diff --git a/src/gui/widgets/vertcontainer.cpp b/src/gui/widgets/vertcontainer.cpp
new file mode 100644
index 000000000..4dac2a617
--- /dev/null
+++ b/src/gui/widgets/vertcontainer.cpp
@@ -0,0 +1,53 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/vertcontainer.h"
+
+VertContainer::VertContainer(int spacing):
+ mSpacing(spacing),
+ mCount(0)
+{
+ addWidgetListener(this);
+}
+
+void VertContainer::add(gcn::Widget *widget)
+{
+ if (!widget)
+ return;
+
+ Container::add(widget);
+ widget->setPosition(0, mCount * mSpacing);
+ widget->setSize(getWidth(), mSpacing);
+ mCount++;
+ setHeight(mCount * mSpacing);
+}
+
+void VertContainer::clear()
+{
+ Container::clear();
+
+ mCount = 0;
+}
+
+void VertContainer::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ for (WidgetListIterator it = mWidgets.begin(); it != mWidgets.end(); it++)
+ (*it)->setWidth(getWidth());
+}
diff --git a/src/gui/widgets/vertcontainer.h b/src/gui/widgets/vertcontainer.h
new file mode 100644
index 000000000..fe62d8c0e
--- /dev/null
+++ b/src/gui/widgets/vertcontainer.h
@@ -0,0 +1,52 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_VERTCONTAINER_H
+#define GUI_VERTCONTAINER_H
+
+#include "gui/widgets/container.h"
+
+#include <guichan/widgetlistener.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+/**
+ * A widget container.
+ *
+ * This container places it's contents veritcally.
+ */
+class VertContainer : public Container, public gcn::WidgetListener
+{
+ public:
+ VertContainer(int spacing);
+ virtual void add(gcn::Widget *widget);
+ virtual void clear();
+ void widgetResized(const gcn::Event &event);
+
+ private:
+ int mSpacing;
+ int mCount;
+};
+
+#endif
diff --git a/src/gui/widgets/whispertab.cpp b/src/gui/widgets/whispertab.cpp
new file mode 100644
index 000000000..f0c347b59
--- /dev/null
+++ b/src/gui/widgets/whispertab.cpp
@@ -0,0 +1,164 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "whispertab.h"
+
+#include "chatlog.h"
+#include "commandhandler.h"
+#include "localplayer.h"
+#include "log.h"
+
+#include "gui/theme.h"
+
+#include "net/chathandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+WhisperTab::WhisperTab(const std::string &nick) :
+ ChatTab(nick),
+ mNick(nick)
+{
+ setTabColor(&Theme::getThemeColor(Theme::WHISPER));
+}
+
+WhisperTab::~WhisperTab()
+{
+ if (chatWindow)
+ chatWindow->removeWhisper(mNick);
+}
+
+void WhisperTab::handleInput(const std::string &msg)
+{
+// if (msg.empty())
+// {
+// chatLog(_("Cannot send empty chat!"), BY_SERVER, false);
+// return;
+// }
+
+ if (chatWindow)
+ {
+ Net::getChatHandler()->privateMessage(mNick,
+ chatWindow->doReplace(msg));
+ }
+ else
+ {
+ Net::getChatHandler()->privateMessage(mNick, msg);
+ }
+
+ if (player_node)
+ chatLog(player_node->getName(), msg);
+ else
+ chatLog("?", msg);
+}
+
+void WhisperTab::handleCommand(const std::string &msg)
+{
+ if (msg == "close")
+ {
+ delete this;
+ return;
+ }
+
+ std::string::size_type pos = msg.find(' ');
+ std::string type(msg, 0, pos);
+ std::string args(msg, pos == std::string::npos
+ ? msg.size() : pos + 1);
+
+ if (type == "me")
+ {
+ std::string str = strprintf("*%s*", args.c_str());
+ Net::getChatHandler()->privateMessage(mNick, str);
+ if (player_node)
+ chatLog(player_node->getName(), str);
+ else
+ chatLog("?", str);
+ }
+ else
+ {
+ ChatTab::handleCommand(msg);
+ }
+}
+
+void WhisperTab::showHelp()
+{
+ chatLog(_("/ignore > Ignore the other player"));
+ chatLog(_("/unignore > Stop ignoring the other player"));
+ chatLog(_("/close > Close the whisper tab"));
+}
+
+bool WhisperTab::handleCommand(const std::string &type,
+ const std::string &args)
+{
+ if (type == "help")
+ {
+ if (args == "close")
+ {
+ chatLog(_("Command: /close"));
+ chatLog(_("This command closes the current whisper tab."));
+ }
+ else if (args == "ignore")
+ {
+ chatLog(_("Command: /ignore"));
+ chatLog(_("This command ignores the other player regardless of "
+ "current relations."));
+ }
+ else if (args == "unignore")
+ {
+ chatLog(_("Command: /unignore <player>"));
+ chatLog(_("This command stops ignoring the other player if they "
+ "are being ignored."));
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (type == "close")
+ {
+ delete this;
+ if (chatWindow)
+ chatWindow->defaultTab();
+ }
+ else if (type == "ignore")
+ {
+ if (commandHandler)
+ commandHandler->handleIgnore(mNick, this);
+ }
+ else if (type == "unignore")
+ {
+ if (commandHandler)
+ commandHandler->handleUnignore(mNick, this);
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void WhisperTab::saveToLogFile(std::string &msg)
+{
+ if (chatLogger)
+ chatLogger->log(getNick(), msg);
+}
diff --git a/src/gui/widgets/whispertab.h b/src/gui/widgets/whispertab.h
new file mode 100644
index 000000000..89e70695b
--- /dev/null
+++ b/src/gui/widgets/whispertab.h
@@ -0,0 +1,67 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WHISPERTAB_H
+#define WHISPERTAB_H
+
+#include "chattab.h"
+
+class Channel;
+
+/**
+ * A tab for whispers from a single player.
+ */
+class WhisperTab : public ChatTab
+{
+ public:
+ const std::string &getNick() const { return mNick; }
+
+ void showHelp();
+
+ bool handleCommand(const std::string &type,
+ const std::string &args);
+
+ int getType() const
+ { return ChatTab::TAB_WHISPER; }
+
+ void saveToLogFile(std::string &msg);
+
+ protected:
+ friend class ChatWindow;
+
+ /**
+ * Constructor.
+ *
+ * @param nick the name of the player this tab is whispering to
+ */
+ WhisperTab(const std::string &nick);
+
+ ~WhisperTab();
+
+ void handleInput(const std::string &msg);
+
+ void handleCommand(const std::string &msg);
+
+ private:
+ std::string mNick;
+};
+
+#endif // CHANNELTAB_H
diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp
new file mode 100644
index 000000000..6564a8d3f
--- /dev/null
+++ b/src/gui/widgets/window.cpp
@@ -0,0 +1,924 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/window.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "log.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+#include "gui/theme.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/layout.h"
+#include "gui/widgets/resizegrip.h"
+#include "gui/widgets/windowcontainer.h"
+
+#include "resources/image.h"
+
+#include <guichan/exception.hpp>
+#include <guichan/focushandler.hpp>
+
+int Window::instances = 0;
+int Window::mouseResize = 0;
+
+Window::Window(const std::string &caption, bool modal, Window *parent,
+ const std::string &skin):
+ gcn::Window(caption),
+ mGrip(0),
+ mParent(parent),
+ mLayout(NULL),
+ mWindowName("window"),
+ mShowTitle(true),
+ mModal(modal),
+ mCloseButton(false),
+ mDefaultVisible(false),
+ mSaveVisible(false),
+ mStickyButton(false),
+ mSticky(false),
+ mMinWinWidth(100),
+ mMinWinHeight(40),
+ mMaxWinWidth(graphics->getWidth()),
+ mMaxWinHeight(graphics->getHeight())
+{
+ logger->log("Window::Window(\"%s\")", caption.c_str());
+
+ if (!windowContainer)
+ throw GCN_EXCEPTION("Window::Window(): no windowContainer set");
+
+ instances++;
+
+ setFrameSize(0);
+ setPadding(3);
+ setTitleBarHeight(20);
+
+ // Loads the skin
+ mSkin = Theme::instance()->load(skin);
+
+ // Add this window to the window container
+ windowContainer->add(this);
+
+ if (mModal)
+ {
+ gui->setCursorType(Gui::CURSOR_POINTER);
+ requestModalFocus();
+ }
+
+ // Windows are invisible by default
+ setVisible(false);
+
+ addWidgetListener(this);
+}
+
+Window::~Window()
+{
+ logger->log("Window::~Window(\"%s\")", getCaption().c_str());
+
+ saveWindowState();
+
+ delete mLayout;
+ mLayout = 0;
+
+ while (!mWidgets.empty())
+ delete mWidgets.front();
+
+// need mWidgets.clean ?
+
+ removeWidgetListener(this);
+
+ instances--;
+
+ if (mSkin)
+ mSkin->instances--;
+}
+
+void Window::setWindowContainer(WindowContainer *wc)
+{
+ windowContainer = wc;
+}
+
+void Window::draw(gcn::Graphics *graphics)
+{
+ if (!mSkin)
+ return;
+
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder());
+
+ // Draw title
+ if (mShowTitle)
+ {
+ g->setColor(Theme::getThemeColor(Theme::TEXT));
+ g->setFont(getFont());
+ g->drawText(getCaption(), 7, 5, gcn::Graphics::LEFT);
+ }
+
+ // Draw Close Button
+ if (mCloseButton && mSkin->getCloseImage())
+ {
+ g->drawImage(mSkin->getCloseImage(),
+ getWidth() - mSkin->getCloseImage()->getWidth() - getPadding(),
+ getPadding());
+ }
+
+ // Draw Sticky Button
+ if (mStickyButton)
+ {
+ Image *button = mSkin->getStickyImage(mSticky);
+ if (button)
+ {
+ int x = getWidth() - button->getWidth() - getPadding();
+ if (mCloseButton && mSkin->getCloseImage())
+ x -= mSkin->getCloseImage()->getWidth();
+
+ g->drawImage(button, x, getPadding());
+ }
+ }
+
+ drawChildren(graphics);
+}
+
+void Window::setContentSize(int width, int height)
+{
+ width = width + 2 * getPadding();
+ height = height + getPadding() + getTitleBarHeight();
+
+ if (getMinWidth() > width)
+ width = getMinWidth();
+ else if (getMaxWidth() < width)
+ width = getMaxWidth();
+ if (getMinHeight() > height)
+ height = getMinHeight();
+ else if (getMaxHeight() < height)
+ height = getMaxHeight();
+
+ setSize(width, height);
+}
+
+void Window::setLocationRelativeTo(gcn::Widget *widget)
+{
+ if (!widget)
+ return;
+
+ int wx, wy;
+ int x, y;
+
+ widget->getAbsolutePosition(wx, wy);
+ getAbsolutePosition(x, y);
+
+ setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x),
+ getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y));
+}
+
+void Window::setLocationHorisontallyRelativeTo(gcn::Widget *widget)
+{
+ if (!widget)
+ return;
+
+ int wx, wy;
+ int x, y;
+
+ widget->getAbsolutePosition(wx, wy);
+ getAbsolutePosition(x, y);
+
+ setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x), 0);
+}
+
+void Window::setLocationRelativeTo(ImageRect::ImagePosition position,
+ int offsetX, int offsetY)
+{
+ if (position == ImageRect::UPPER_LEFT)
+ {
+ }
+ else if (position == ImageRect::UPPER_CENTER)
+ {
+ offsetX += (graphics->getWidth() - getWidth()) / 2;
+ }
+ else if (position == ImageRect::UPPER_RIGHT)
+ {
+ offsetX += graphics->getWidth() - getWidth();
+ }
+ else if (position == ImageRect::LEFT)
+ {
+ offsetY += (graphics->getHeight() - getHeight()) / 2;
+ }
+ else if (position == ImageRect::CENTER)
+ {
+ offsetX += (graphics->getWidth() - getWidth()) / 2;
+ offsetY += (graphics->getHeight() - getHeight()) / 2;
+ }
+ else if (position == ImageRect::RIGHT)
+ {
+ offsetX += graphics->getWidth() - getWidth();
+ offsetY += (graphics->getHeight() - getHeight()) / 2;
+ }
+ else if (position == ImageRect::LOWER_LEFT)
+ {
+ offsetY += graphics->getHeight() - getHeight();
+ }
+ else if (position == ImageRect::LOWER_CENTER)
+ {
+ offsetX += (graphics->getWidth() - getWidth()) / 2;
+ offsetY += graphics->getHeight() - getHeight();
+ }
+ else if (position == ImageRect::LOWER_RIGHT)
+ {
+ offsetX += graphics->getWidth() - getWidth();
+ offsetY += graphics->getHeight() - getHeight();
+ }
+
+ setPosition(offsetX, offsetY);
+}
+
+void Window::setMinWidth(int width)
+{
+ mMinWinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth();
+}
+
+void Window::setMinHeight(int height)
+{
+ mMinWinHeight = height > mSkin->getMinHeight() ?
+ height : mSkin->getMinHeight();
+}
+
+void Window::setMaxWidth(int width)
+{
+ mMaxWinWidth = width;
+}
+
+void Window::setMaxHeight(int height)
+{
+ mMaxWinHeight = height;
+}
+
+void Window::setResizable(bool r)
+{
+ if (static_cast<bool>(mGrip) == r)
+ return;
+
+ if (r)
+ {
+ mGrip = new ResizeGrip;
+ mGrip->setX(getWidth() - mGrip->getWidth() - getChildrenArea().x);
+ mGrip->setY(getHeight() - mGrip->getHeight() - getChildrenArea().y);
+ add(mGrip);
+ }
+ else
+ {
+ remove(mGrip);
+ delete mGrip;
+ mGrip = 0;
+ }
+}
+
+void Window::widgetResized(const gcn::Event &event _UNUSED_)
+{
+ const gcn::Rectangle area = getChildrenArea();
+
+ if (mGrip)
+ {
+ mGrip->setPosition(getWidth() - mGrip->getWidth() - area.x,
+ getHeight() - mGrip->getHeight() - area.y);
+ }
+
+ if (mLayout)
+ {
+ int w = area.width;
+ int h = area.height;
+ mLayout->reflow(w, h);
+ }
+}
+
+void Window::widgetHidden(const gcn::Event &event _UNUSED_)
+{
+ if (gui)
+ gui->setCursorType(Gui::CURSOR_POINTER);
+
+ WidgetListIterator it;
+
+ if (!mFocusHandler)
+ return;
+
+ for (it = mWidgets.begin(); it != mWidgets.end(); it++)
+ {
+ if (mFocusHandler->isFocused(*it))
+ mFocusHandler->focusNone();
+ }
+}
+
+void Window::setCloseButton(bool flag)
+{
+ mCloseButton = flag;
+}
+
+bool Window::isResizable() const
+{
+ return mGrip;
+}
+
+void Window::setStickyButton(bool flag)
+{
+ mStickyButton = flag;
+}
+
+void Window::setSticky(bool sticky)
+{
+ mSticky = sticky;
+}
+
+void Window::setVisible(bool visible)
+{
+ setVisible(visible, false);
+}
+
+void Window::setVisible(bool visible, bool forceSticky)
+{
+ if (visible == isVisible())
+ return; // Nothing to do
+
+ // Check if the window is off screen...
+ if (visible)
+ checkIfIsOffScreen();
+
+ gcn::Window::setVisible((!forceSticky && isSticky()) || visible);
+}
+
+void Window::scheduleDelete()
+{
+ windowContainer->scheduleDelete(this);
+}
+
+void Window::mousePressed(gcn::MouseEvent &event)
+{
+ // Let Guichan move window to top and figure out title bar drag
+ gcn::Window::mousePressed(event);
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ const int x = event.getX();
+ const int y = event.getY();
+
+ // Handle close button
+ if (mCloseButton)
+ {
+ Image *img = mSkin->getCloseImage();
+ if (img)
+ {
+ gcn::Rectangle closeButtonRect(
+ getWidth() - img->getWidth()
+ - getPadding(), getPadding(),
+ img->getWidth(), img->getHeight());
+
+ if (closeButtonRect.isPointInRect(x, y))
+ {
+ mouseResize = 0;
+ mMoved = 0;
+ close();
+ return;
+ }
+ }
+ }
+
+ // Handle sticky button
+ if (mStickyButton)
+ {
+ Image *button = mSkin->getStickyImage(mSticky);
+ if (button)
+ {
+ int rx = getWidth() - button->getWidth() - getPadding();
+ if (mCloseButton)
+ {
+ Image *img = mSkin->getCloseImage();
+ if (img)
+ rx -= img->getWidth();
+ }
+ gcn::Rectangle stickyButtonRect(rx, getPadding(),
+ button->getWidth(), button->getHeight());
+ if (stickyButtonRect.isPointInRect(x, y))
+ {
+ setSticky(!isSticky());
+ mouseResize = 0;
+ mMoved = 0;
+ return;
+ }
+ }
+ }
+
+ // Handle window resizing
+ mouseResize = getResizeHandles(event);
+ mMoved = !mouseResize;
+ }
+}
+
+void Window::close()
+{
+ setVisible(false);
+}
+
+void Window::mouseReleased(gcn::MouseEvent &event _UNUSED_)
+{
+ if (mGrip && mouseResize)
+ {
+ mouseResize = 0;
+ if (gui)
+ gui->setCursorType(Gui::CURSOR_POINTER);
+ }
+
+ // This should be the responsibility of Guichan (and is from 0.8.0 on)
+ mMoved = false;
+}
+
+void Window::mouseExited(gcn::MouseEvent &event _UNUSED_)
+{
+ if (mGrip && !mouseResize && gui)
+ gui->setCursorType(Gui::CURSOR_POINTER);
+}
+
+void Window::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!gui)
+ return;
+
+ int resizeHandles = getResizeHandles(event);
+
+ // Changes the custom mouse cursor based on it's current position.
+ switch (resizeHandles)
+ {
+ case BOTTOM | RIGHT:
+ case TOP | LEFT:
+ gui->setCursorType(Gui::CURSOR_RESIZE_DOWN_RIGHT);
+ break;
+ case TOP | RIGHT:
+ case BOTTOM | LEFT:
+ gui->setCursorType(Gui::CURSOR_RESIZE_DOWN_LEFT);
+ break;
+ case BOTTOM:
+ case TOP:
+ gui->setCursorType(Gui::CURSOR_RESIZE_DOWN);
+ break;
+ case RIGHT:
+ case LEFT:
+ gui->setCursorType(Gui::CURSOR_RESIZE_ACROSS);
+ break;
+ default:
+ gui->setCursorType(Gui::CURSOR_POINTER);
+ }
+
+ if (viewport)
+ viewport->hideBeingPopup();
+}
+
+void Window::mouseDragged(gcn::MouseEvent &event)
+{
+ // Let Guichan handle title bar drag
+ gcn::Window::mouseDragged(event);
+
+ // Keep guichan window inside screen when it may be moved
+ if (isMovable() && mMoved)
+ {
+ int newX = std::max(0, getX());
+ int newY = std::max(0, getY());
+ newX = std::min(graphics->getWidth() - getWidth(), newX);
+ newY = std::min(graphics->getHeight() - getHeight(), newY);
+ setPosition(newX, newY);
+ }
+
+ if (mouseResize && !mMoved)
+ {
+ const int dx = event.getX() - mDragOffsetX;
+ const int dy = event.getY() - mDragOffsetY;
+ gcn::Rectangle newDim = getDimension();
+
+ if (mouseResize & (TOP | BOTTOM))
+ {
+ int newHeight = newDim.height + ((mouseResize & TOP) ? -dy : dy);
+ newDim.height = std::min(mMaxWinHeight,
+ std::max(mMinWinHeight, newHeight));
+
+ if (mouseResize & TOP)
+ newDim.y -= newDim.height - getHeight();
+ }
+
+ if (mouseResize & (LEFT | RIGHT))
+ {
+ int newWidth = newDim.width + ((mouseResize & LEFT) ? -dx : dx);
+ newDim.width = std::min(mMaxWinWidth,
+ std::max(mMinWinWidth, newWidth));
+
+ if (mouseResize & LEFT)
+ newDim.x -= newDim.width - getWidth();
+ }
+
+ // Keep guichan window inside screen (supports resizing any side)
+ if (newDim.x < 0)
+ {
+ newDim.width += newDim.x;
+ newDim.x = 0;
+ }
+ if (newDim.y < 0)
+ {
+ newDim.height += newDim.y;
+ newDim.y = 0;
+ }
+ if (newDim.x + newDim.width > graphics->getWidth())
+ {
+ newDim.width = graphics->getWidth() - newDim.x;
+ }
+ if (newDim.y + newDim.height > graphics->getHeight())
+ {
+ newDim.height = graphics->getHeight() - newDim.y;
+ }
+
+ // Update mouse offset when dragging bottom or right border
+ if (mouseResize & BOTTOM)
+ mDragOffsetY += newDim.height - getHeight();
+
+ if (mouseResize & RIGHT)
+ mDragOffsetX += newDim.width - getWidth();
+
+ // Set the new window and content dimensions
+ setDimension(newDim);
+ }
+}
+
+void Window::setModal(bool modal)
+{
+ if (mModal != modal)
+ {
+ mModal = modal;
+ if (mModal)
+ {
+ if (gui)
+ gui->setCursorType(Gui::CURSOR_POINTER);
+ requestModalFocus();
+ }
+ else
+ {
+ releaseModalFocus();
+ }
+ }
+}
+
+void Window::loadWindowState()
+{
+ const std::string &name = mWindowName;
+ assert(!name.empty());
+
+ setPosition(config.getValueInt(name + "WinX", mDefaultX),
+ config.getValueInt(name + "WinY", mDefaultY));
+
+ if (mSaveVisible)
+ {
+ setVisible(config.getValueBool(name
+ + "Visible", mDefaultVisible));
+ }
+
+ if (mStickyButton)
+ {
+ setSticky(config.getValueBool(name
+ + "Sticky", isSticky()));
+ }
+
+ if (mGrip)
+ {
+ int width = config.getValueInt(name + "WinWidth", mDefaultWidth);
+ int height = config.getValueInt(name + "WinHeight", mDefaultHeight);
+
+ if (getMinWidth() > width)
+ width = getMinWidth();
+ else if (getMaxWidth() < width)
+ width = getMaxWidth();
+ if (getMinHeight() > height)
+ height = getMinHeight();
+ else if (getMaxHeight() < height)
+ height = getMaxHeight();
+
+ setSize(width, height);
+ }
+ else
+ {
+ setSize(mDefaultWidth, mDefaultHeight);
+ }
+
+ // Check if the window is off screen...
+ checkIfIsOffScreen();
+}
+
+void Window::saveWindowState()
+{
+ // Saving X, Y and Width and Height for resizables in the config
+ if (!mWindowName.empty() && mWindowName != "window")
+ {
+ config.setValue(mWindowName + "WinX", getX());
+ config.setValue(mWindowName + "WinY", getY());
+
+ if (mSaveVisible)
+ config.setValue(mWindowName + "Visible", isVisible());
+
+ if (mStickyButton)
+ config.setValue(mWindowName + "Sticky", isSticky());
+
+ if (mGrip)
+ {
+ if (getMinWidth() > getWidth())
+ setWidth(getMinWidth());
+ else if (getMaxWidth() < getWidth())
+ setWidth(getMaxWidth());
+ if (getMinHeight() > getHeight())
+ setHeight(getMinHeight());
+ else if (getMaxHeight() < getHeight())
+ setHeight(getMaxHeight());
+
+ config.setValue(mWindowName + "WinWidth", getWidth());
+ config.setValue(mWindowName + "WinHeight", getHeight());
+ }
+ }
+}
+
+void Window::setDefaultSize(int defaultX, int defaultY,
+ int defaultWidth, int defaultHeight)
+{
+ if (getMinWidth() > defaultWidth)
+ defaultWidth = getMinWidth();
+ else if (getMaxWidth() < defaultWidth)
+ defaultWidth = getMaxWidth();
+ if (getMinHeight() > defaultHeight)
+ defaultHeight = getMinHeight();
+ else if (getMaxHeight() < defaultHeight)
+ defaultHeight = getMaxHeight();
+
+ mDefaultX = defaultX;
+ mDefaultY = defaultY;
+ mDefaultWidth = defaultWidth;
+ mDefaultHeight = defaultHeight;
+}
+
+void Window::setDefaultSize()
+{
+ mDefaultX = getX();
+ mDefaultY = getY();
+ mDefaultWidth = getWidth();
+ mDefaultHeight = getHeight();
+}
+
+void Window::setDefaultSize(int defaultWidth, int defaultHeight,
+ ImageRect::ImagePosition position,
+ int offsetX, int offsetY)
+{
+ int x = 0, y = 0;
+
+ if (position == ImageRect::UPPER_LEFT)
+ {
+ }
+ else if (position == ImageRect::UPPER_CENTER)
+ {
+ x = (graphics->getWidth() - defaultWidth) / 2;
+ }
+ else if (position == ImageRect::UPPER_RIGHT)
+ {
+ x = graphics->getWidth() - defaultWidth;
+ }
+ else if (position == ImageRect::LEFT)
+ {
+ y = (graphics->getHeight() - defaultHeight) / 2;
+ }
+ else if (position == ImageRect::CENTER)
+ {
+ x = (graphics->getWidth() - defaultWidth) / 2;
+ y = (graphics->getHeight() - defaultHeight) / 2;
+ }
+ else if (position == ImageRect::RIGHT)
+ {
+ x = graphics->getWidth() - defaultWidth;
+ y = (graphics->getHeight() - defaultHeight) / 2;
+ }
+ else if (position == ImageRect::LOWER_LEFT)
+ {
+ y = graphics->getHeight() - defaultHeight;
+ }
+ else if (position == ImageRect::LOWER_CENTER)
+ {
+ x = (graphics->getWidth() - defaultWidth) / 2;
+ y = graphics->getHeight() - defaultHeight;
+ }
+ else if (position == ImageRect::LOWER_RIGHT)
+ {
+ x = graphics->getWidth() - defaultWidth;
+ y = graphics->getHeight() - defaultHeight;
+ }
+
+ mDefaultX = x - offsetX;
+ mDefaultY = y - offsetY;
+ mDefaultWidth = defaultWidth;
+ mDefaultHeight = defaultHeight;
+}
+
+void Window::resetToDefaultSize()
+{
+ setPosition(mDefaultX, mDefaultY);
+ setSize(mDefaultWidth, mDefaultHeight);
+ saveWindowState();
+}
+
+int Window::getResizeHandles(gcn::MouseEvent &event)
+{
+ int resizeHandles = 0;
+ const int y = event.getY();
+
+ if (mGrip && (y > static_cast<int>(mTitleBarHeight)
+ || (y < (int)getPadding() && mTitleBarHeight > getPadding())))
+ {
+ const int x = event.getX();
+
+ if (!getWindowArea().isPointInRect(x, y) && event.getSource() == this)
+ {
+ resizeHandles |= (x > getWidth() - resizeBorderWidth) ? RIGHT :
+ (x < resizeBorderWidth) ? LEFT : 0;
+ resizeHandles |= (y > getHeight() - resizeBorderWidth) ? BOTTOM :
+ (y < resizeBorderWidth) ? TOP : 0;
+ }
+
+ if (event.getSource() == mGrip)
+ {
+ mDragOffsetX = x;
+ mDragOffsetY = y;
+ resizeHandles |= BOTTOM | RIGHT;
+ }
+ }
+
+ return resizeHandles;
+}
+
+bool Window::isResizeAllowed(gcn::MouseEvent &event)
+{
+ const int y = event.getY();
+
+ if (mGrip && (y > static_cast<int>(mTitleBarHeight)
+ || y < (int)getPadding()))
+ {
+ const int x = event.getX();
+
+ if (!getWindowArea().isPointInRect(x, y) && event.getSource() == this)
+ return true;
+
+ if (event.getSource() == mGrip)
+ return true;
+ }
+
+ return false;
+}
+
+int Window::getGuiAlpha()
+{
+ float alpha = std::max(Client::getGuiAlpha(),
+ Theme::instance()->getMinimumOpacity());
+ return static_cast<int>(alpha * 255.0f);
+}
+
+Layout &Window::getLayout()
+{
+ if (!mLayout)
+ mLayout = new Layout;
+ return *mLayout;
+}
+
+void Window::clearLayout()
+{
+ clear();
+
+ // Restore the resize grip
+ if (mGrip)
+ add(mGrip);
+
+ // Recreate layout instance when one is present
+ if (mLayout)
+ {
+ delete mLayout;
+ mLayout = new Layout;
+ }
+}
+
+LayoutCell &Window::place(int x, int y, gcn::Widget *wg, int w, int h)
+{
+ add(wg);
+ return getLayout().place(wg, x, y, w, h);
+}
+
+ContainerPlacer Window::getPlacer(int x, int y)
+{
+ return ContainerPlacer(this, &getLayout().at(x, y));
+}
+
+void Window::reflowLayout(int w, int h)
+{
+ if (!mLayout)
+ return;
+
+ mLayout->reflow(w, h);
+ delete mLayout;
+ mLayout = 0;
+ setContentSize(w, h);
+}
+
+void Window::redraw()
+{
+ if (mLayout)
+ {
+ const gcn::Rectangle area = getChildrenArea();
+ int w = area.width;
+ int h = area.height;
+ mLayout->reflow(w, h);
+ }
+}
+
+void Window::center()
+{
+ setLocationRelativeTo(getParent());
+}
+
+void Window::centerHorisontally()
+{
+ setLocationHorisontallyRelativeTo(getParent());
+}
+
+void Window::checkIfIsOffScreen(bool partially, bool entirely)
+{
+ // Move the window onto screen if it has become off screen
+ // For instance, because of resolution change...
+
+ // First of all, don't deal when a window hasn't got
+ // any size initialized yet...
+ if (getWidth() == 0 && getHeight() == 0)
+ return;
+
+ // Made partially the default behaviour
+ if (!partially && !entirely)
+ partially = true;
+
+ // Keep guichan window inside screen (supports resizing any side)
+
+ gcn::Rectangle winDimension = getDimension();
+
+ if (winDimension.x < 0)
+ {
+ winDimension.width += winDimension.x;
+ winDimension.x = 0;
+ }
+ if (winDimension.y < 0)
+ {
+ winDimension.height += winDimension.y;
+ winDimension.y = 0;
+ }
+
+ // Look if the window is partially off-screen limits...
+ if (partially)
+ {
+ if (winDimension.x + winDimension.width > graphics->getWidth())
+ winDimension.x = graphics->getWidth() - winDimension.width;
+
+ if (winDimension.y + winDimension.height > graphics->getHeight())
+ winDimension.y = graphics->getHeight() - winDimension.height;
+
+ setDimension(winDimension);
+ return;
+ }
+
+ if (entirely)
+ {
+ if (winDimension.x > graphics->getWidth())
+ winDimension.x = graphics->getWidth() - winDimension.width;
+
+ if (winDimension.y > graphics->getHeight())
+ winDimension.y = graphics->getHeight() - winDimension.height;
+ }
+ setDimension(winDimension);
+}
+
+gcn::Rectangle Window::getWindowArea()
+{
+ return gcn::Rectangle(getPadding(),
+ getPadding(),
+ getWidth() - getPadding() * 2,
+ getHeight() - getPadding() * 2);
+} \ No newline at end of file
diff --git a/src/gui/widgets/window.h b/src/gui/widgets/window.h
new file mode 100644
index 000000000..09e15d3f4
--- /dev/null
+++ b/src/gui/widgets/window.h
@@ -0,0 +1,441 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WINDOW_H
+#define WINDOW_H
+
+#include "graphics.h"
+#include "guichanfwd.h"
+
+#include <guichan/widgetlistener.hpp>
+
+#include <guichan/widgets/window.hpp>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class ContainerPlacer;
+class Layout;
+class LayoutCell;
+class ResizeGrip;
+class Skin;
+class WindowContainer;
+
+/**
+ * A window. This window can be dragged around and has a title bar. Windows are
+ * invisible by default.
+ *
+ * \ingroup GUI
+ */
+class Window : public gcn::Window, gcn::WidgetListener
+{
+ public:
+ /**
+ * Constructor. Initializes the title to the given text and hooks
+ * itself into the window container.
+ *
+ * @param caption The initial window title, "Window" by default.
+ * @param modal Block input to other windows.
+ * @param parent The parent window. This is the window standing above
+ * this one in the window hiearchy. When reordering,
+ * a window will never go below its parent window.
+ * @param skin The location where the window's skin XML can be found.
+ */
+ Window(const std::string &caption = "Window", bool modal = false,
+ Window *parent = NULL, const std::string &skin = "window.xml");
+
+ /**
+ * Destructor. Deletes all the added widgets.
+ */
+ ~Window();
+
+ /**
+ * Sets the window container to be used by new windows.
+ */
+ static void setWindowContainer(WindowContainer *windowContainer);
+
+ /**
+ * Draws the window.
+ */
+ void draw(gcn::Graphics *graphics);
+
+ /**
+ * Sets the size of this window.
+ */
+ void setContentSize(int width, int height);
+
+ /**
+ * Sets the location relative to the given widget.
+ */
+ void setLocationRelativeTo(gcn::Widget *widget);
+
+ /**
+ * Sets the location relative to the given widget (only horisontally)
+ */
+ void setLocationHorisontallyRelativeTo(gcn::Widget *widget);
+
+ /**
+ * Sets the location relative to the given enumerated position.
+ */
+ void setLocationRelativeTo(ImageRect::ImagePosition position,
+ int offsetX = 0, int offsetY = 0);
+
+ /**
+ * Sets whether or not the window can be resized.
+ */
+ void setResizable(bool resize);
+
+ void redraw();
+
+ /**
+ * Called whenever the widget changes size.
+ */
+ void widgetResized(const gcn::Event &event);
+
+ /**
+ * Called whenever the widget is hidden.
+ */
+ virtual void widgetHidden(const gcn::Event& event);
+
+ /**
+ * Sets whether or not the window has a close button.
+ */
+ void setCloseButton(bool flag);
+
+ /**
+ * Returns whether the window can be resized.
+ */
+ bool isResizable() const;
+
+ /**
+ * Sets the minimum width of the window.
+ */
+ void setMinWidth(int width);
+
+ int getMinWidth() const
+ { return mMinWinWidth; }
+
+ /**
+ * Sets the minimum height of the window.
+ */
+ void setMinHeight(int height);
+
+ int getMinHeight() const
+ { return mMinWinHeight; }
+
+ /**
+ * Sets the maximum width of the window.
+ */
+ void setMaxWidth(int width);
+
+ int getMaxWidth() const
+ { return mMaxWinWidth; }
+
+ /**
+ * Sets the minimum height of the window.
+ */
+ void setMaxHeight(int height);
+
+ int getMaxHeight() const
+ { return mMaxWinHeight; }
+
+ /**
+ * Sets flag to show a title or not.
+ */
+ void setShowTitle(bool flag)
+ { mShowTitle = flag; }
+
+ /**
+ * Sets whether or not the window has a sticky button.
+ */
+ void setStickyButton(bool flag);
+
+ /**
+ * Sets whether the window is sticky. A sticky window will not have
+ * its visibility set to false on a general setVisible(false) call.
+ * Use this to set the default before you call loadWindowState().
+ */
+ void setSticky(bool sticky);
+
+ /**
+ * Returns whether the window is sticky.
+ */
+ bool isSticky() const
+ { return mSticky; }
+
+ /**
+ * Overloads window setVisible by Guichan to allow sticky window
+ * handling.
+ */
+ virtual void setVisible(bool visible);
+
+ /**
+ * Overloads window setVisible by Guichan to allow sticky window
+ * handling, or not, if you force the sticky state.
+ */
+ void setVisible(bool visible, bool forceSticky);
+
+ /**
+ * Returns whether the window is visible by default.
+ */
+ bool isDefaultVisible() const
+ { return mDefaultVisible; }
+
+ /**
+ * Sets whether the window is visible by default.
+ */
+ void setDefaultVisible(bool save)
+ { mDefaultVisible = save; }
+
+ /**
+ * Returns whether the window will save it's visibility.
+ */
+ bool willSaveVisible() const
+ { return mSaveVisible; }
+
+ /**
+ * Sets whether the window will save it's visibility.
+ */
+ void setSaveVisible(bool save)
+ { mSaveVisible = save; }
+
+ /**
+ * Returns the parent window.
+ *
+ * @return The parent window or <code>NULL</code> if there is none.
+ */
+ Window *getParentWindow() const
+ { return mParent; }
+
+ /**
+ * Schedule this window for deletion. It will be deleted at the start
+ * of the next logic update.
+ */
+ void scheduleDelete();
+
+ /**
+ * Starts window resizing when appropriate.
+ */
+ void mousePressed(gcn::MouseEvent &event);
+
+ /**
+ * Implements window resizing and makes sure the window is not
+ * dragged/resized outside of the screen.
+ */
+ void mouseDragged(gcn::MouseEvent &event);
+
+ /**
+ * Implements custom cursor image changing context, based on mouse
+ * relative position.
+ */
+ void mouseMoved(gcn::MouseEvent &event);
+
+ /**
+ * When the mouse button has been let go, this ensures that the mouse
+ * custom cursor is restored back to it's standard image.
+ */
+ void mouseReleased(gcn::MouseEvent &event);
+
+ /**
+ * When the mouse leaves the window this ensures that the custom cursor
+ * is restored back to it's standard image.
+ */
+ void mouseExited(gcn::MouseEvent &event);
+
+ /**
+ * Sets the name of the window. This is not the window title.
+ */
+ void setWindowName(const std::string &name)
+ { mWindowName = name; }
+
+ /**
+ * Returns the name of the window. This is not the window title.
+ */
+ const std::string &getWindowName() const
+ { return mWindowName; }
+
+ /**
+ * Reads the position (and the size for resizable windows) in the
+ * configuration based on the given string.
+ * Uses the default values when config values are missing.
+ * Don't forget to set these default values and resizable before
+ * calling this function.
+ */
+ void loadWindowState();
+
+ /**
+ * Saves the window state so that when the window is reloaded, it'll
+ * maintain its previous state and location.
+ */
+ void saveWindowState();
+
+ /**
+ * Set the default win pos and size.
+ * (which can be different of the actual ones.)
+ */
+ void setDefaultSize(int defaultX, int defaultY,
+ int defaultWidth, int defaultHeight);
+
+ /**
+ * Set the default win pos and size to the current ones.
+ */
+ void setDefaultSize();
+
+ /**
+ * Set the default win pos and size.
+ * (which can be different of the actual ones.)
+ * This version of setDefaultSize sets the window's position based
+ * on a relative enumerated position, rather than a coordinate position.
+ */
+ void setDefaultSize(int defaultWidth, int defaultHeight,
+ ImageRect::ImagePosition position,
+ int offsetx = 0, int offsetY = 0);
+
+ /**
+ * Reset the win pos and size to default. Don't forget to set defaults
+ * first.
+ */
+ virtual void resetToDefaultSize();
+
+ /**
+ * Gets the layout handler for this window.
+ */
+ Layout &getLayout();
+
+ /**
+ * Clears the window's layout (useful for redesigning the window). Does
+ * not delete the widgets!
+ */
+ void clearLayout();
+
+ /**
+ * Computes the position of the widgets according to the current
+ * layout. Resizes the window so that the layout fits. Deletes the
+ * layout.
+ * @param w if non-zero, force the window to this width.
+ * @param h if non-zero, force the window to this height.
+ * @note This function is meant to be called with fixed-size windows.
+ */
+ void reflowLayout(int w = 0, int h = 0);
+
+ /**
+ * Adds a widget to the window and sets it at given cell.
+ */
+ LayoutCell &place(int x, int y, gcn::Widget *, int w = 1, int h = 1);
+
+ /**
+ * Returns a proxy for adding widgets in an inner table of the layout.
+ */
+ ContainerPlacer getPlacer(int x, int y);
+
+ /**
+ * Positions the window in the center of it's parent.
+ */
+ void center();
+
+ /**
+ * Positions the window in the horisontal center of it's parent.
+ */
+ void centerHorisontally();
+
+ /**
+ * Overrideable functionality for when the window is to close. This
+ * allows for class implementations to clean up or do certain actions
+ * on window close they couldn't do otherwise.
+ */
+ virtual void close();
+
+ /**
+ * Allows the windows modal status to change
+ */
+ void setModal(bool modal);
+
+ /**
+ * Gets the alpha value used by the window, in a GUIChan usable format.
+ */
+ int getGuiAlpha();
+
+ gcn::Rectangle getWindowArea();
+
+ bool isResizeAllowed(gcn::MouseEvent &event);
+
+ private:
+ enum ResizeHandles
+ {
+ TOP = 0x01,
+ RIGHT = 0x02,
+ BOTTOM = 0x04,
+ LEFT = 0x08
+ };
+
+ /**
+ * Check if the window is off-screen and then move it to be visible
+ * again. This is internally used by loadWindowState
+ * and setVisible(true) members.
+ */
+ void checkIfIsOffScreen(bool partially = true, bool entirely = true);
+
+ /**
+ * Determines if the mouse is in a resize area and returns appropriate
+ * resize handles. Also initializes drag offset in case the resize
+ * grip is used.
+ *
+ * @see ResizeHandles
+ */
+ int getResizeHandles(gcn::MouseEvent &event);
+
+ ResizeGrip *mGrip; /**< Resize grip */
+ Window *mParent; /**< The parent window */
+ Layout *mLayout; /**< Layout handler */
+ std::string mWindowName; /**< Name of the window */
+ bool mShowTitle; /**< Window has a title bar */
+ bool mModal; /**< Window is modal */
+ bool mCloseButton; /**< Window has a close button */
+ bool mDefaultVisible; /**< Window's default visibility */
+ bool mSaveVisible; /**< Window will save visibility */
+ bool mStickyButton; /**< Window has a sticky button */
+ bool mSticky; /**< Window resists hiding*/
+ int mMinWinWidth; /**< Minimum window width */
+ int mMinWinHeight; /**< Minimum window height */
+ int mMaxWinWidth; /**< Maximum window width */
+ int mMaxWinHeight; /**< Maximum window height */
+ int mDefaultX; /**< Default window X position */
+ int mDefaultY; /**< Default window Y position */
+ int mDefaultWidth; /**< Default window width */
+ int mDefaultHeight; /**< Default window height */
+
+ static int mouseResize; /**< Active resize handles */
+ static int instances; /**< Number of Window instances */
+
+ Skin *mSkin; /**< Skin in use by this window */
+
+ /**
+ * The width of the resize border. Is independent of the actual window
+ * border width, and determines mostly the size of the corner area
+ * where two borders are moved at the same time.
+ */
+ static const int resizeBorderWidth = 10;
+};
+
+#endif
diff --git a/src/gui/widgets/windowcontainer.cpp b/src/gui/widgets/windowcontainer.cpp
new file mode 100644
index 000000000..0fff491e4
--- /dev/null
+++ b/src/gui/widgets/windowcontainer.cpp
@@ -0,0 +1,40 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/windowcontainer.h"
+
+#include "utils/dtor.h"
+
+WindowContainer *windowContainer = NULL;
+
+void WindowContainer::logic()
+{
+ delete_all(mDeathList);
+ mDeathList.clear();
+
+ gcn::Container::logic();
+}
+
+void WindowContainer::scheduleDelete(gcn::Widget *widget)
+{
+ if (widget)
+ mDeathList.push_back(widget);
+}
diff --git a/src/gui/widgets/windowcontainer.h b/src/gui/widgets/windowcontainer.h
new file mode 100644
index 000000000..2ec65d15a
--- /dev/null
+++ b/src/gui/widgets/windowcontainer.h
@@ -0,0 +1,59 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WINDOWCONTAINER_H
+#define WINDOWCONTAINER_H
+
+#include "gui/widgets/container.h"
+
+/**
+ * A window container. This container adds functionality for more convenient
+ * widget (windows in particular) destruction.
+ *
+ * \ingroup GUI
+ */
+class WindowContainer : public Container
+{
+ public:
+ /**
+ * Do GUI logic. This functions adds automatic deletion of objects that
+ * volunteered to be deleted.
+ */
+ void logic();
+
+ /**
+ * Schedule a widget for deletion. It will be deleted at the start of
+ * the next logic update.
+ */
+ void scheduleDelete(gcn::Widget *widget);
+
+ private:
+ /**
+ * List of widgets that are scheduled to be deleted.
+ */
+ typedef std::list<gcn::Widget*> Widgets;
+ typedef Widgets::iterator WidgetIterator;
+ Widgets mDeathList;
+};
+
+extern WindowContainer *windowContainer;
+
+#endif
diff --git a/src/gui/windowmenu.cpp b/src/gui/windowmenu.cpp
new file mode 100644
index 000000000..eb146f700
--- /dev/null
+++ b/src/gui/windowmenu.cpp
@@ -0,0 +1,285 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/windowmenu.h"
+
+#include "emoteshortcut.h"
+#include "graphics.h"
+#include "keyboardconfig.h"
+
+#include "gui/emotepopup.h"
+#include "gui/skilldialog.h"
+#include "gui/specialswindow.h"
+#include "gui/textpopup.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/window.h"
+#include "gui/widgets/windowcontainer.h"
+
+#include "net/net.h"
+#include "net/playerhandler.h"
+
+#include "utils/gettext.h"
+
+#include <string>
+
+extern Window *equipmentWindow;
+extern Window *inventoryWindow;
+extern Window *itemShortcutWindow;
+extern Window *dropShortcutWindow;
+extern Window *setupWindow;
+extern Window *statusWindow;
+extern Window *whoIsOnline;
+extern Window *killStats;
+extern Window *spellShortcutWindow;
+extern Window *botCheckerWindow;
+extern Window *socialWindow;
+
+WindowMenu::WindowMenu():
+ mEmotePopup(0)
+{
+ int x = 0, h = 0;
+
+ addButton(N_("BC"), _("Bot checker"), x, h,
+ KeyboardConfig::KEY_WINDOW_BOT_CHECKER);
+ addButton(N_("ONL"), _("Who is online"), x, h,
+ KeyboardConfig::KEY_NO_VALUE);
+ addButton(N_("KS"), _("Kill stats"), x, h,
+ KeyboardConfig::KEY_WINDOW_KILLS);
+ addButton(":-)", _("Smiles"), x, h,
+ KeyboardConfig::KEY_WINDOW_EMOTE_SHORTCUT);
+ addButton(N_("STA"), _("Status"), x, h, KeyboardConfig::KEY_WINDOW_STATUS);
+ addButton(N_("EQU"), _("Equipment"), x, h,
+ KeyboardConfig::KEY_WINDOW_EQUIPMENT);
+ addButton(N_("INV"), _("Inventory"), x, h,
+ KeyboardConfig::KEY_WINDOW_INVENTORY);
+
+ if (skillDialog->hasSkills())
+ {
+ addButton(N_("SKI"), _("Skills"), x, h,
+ KeyboardConfig::KEY_WINDOW_SKILL);
+ }
+
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ addButton(N_("SPE"), _("Specials"), x, h,
+ KeyboardConfig::KEY_NO_VALUE);
+ }
+
+ addButton(N_("SOC"), _("Social"), x, h, KeyboardConfig::KEY_WINDOW_SOCIAL);
+ addButton(N_("SH"), _("Shortcuts"), x, h,
+ KeyboardConfig::KEY_WINDOW_SHORTCUT);
+ addButton(N_("SP"), _("Spells"), x, h, KeyboardConfig::KEY_WINDOW_SPELLS);
+ addButton(N_("DR"), _("Drop"), x, h, KeyboardConfig::KEY_WINDOW_DROP);
+ addButton(N_("SET"), _("Setup"), x, h, KeyboardConfig::KEY_WINDOW_SETUP);
+
+ mTextPopup = new TextPopup;
+ setDimension(gcn::Rectangle(graphics->getWidth() - x - 3, 3,
+ x - 3, h));
+
+ addMouseListener(this);
+ setVisible(true);
+}
+
+WindowMenu::~WindowMenu()
+{
+ delete mTextPopup;
+ mTextPopup = 0;
+ mButtonNames.clear();
+}
+
+void WindowMenu::action(const gcn::ActionEvent &event)
+{
+ Window *window = 0;
+
+ if (event.getId() == ":-)")
+ {
+ if (!mEmotePopup)
+ {
+ const gcn::Widget *s = event.getSource();
+ if (s)
+ {
+ const gcn::Rectangle &r = s->getDimension();
+ const int parentX = s->getParent()->getX();
+
+ mEmotePopup = new EmotePopup;
+ const int offset = (r.width - mEmotePopup->getWidth()) / 2;
+ mEmotePopup->setPosition(parentX + r.x + offset,
+ r.y + r.height + 5);
+
+ mEmotePopup->addSelectionListener(this);
+ }
+ else
+ {
+ mEmotePopup = 0;
+ }
+ }
+ else
+ {
+ if (windowContainer)
+ windowContainer->scheduleDelete(mEmotePopup);
+ mEmotePopup = 0;
+ }
+ }
+ else if (event.getId() == "STA")
+ {
+ window = statusWindow;
+ }
+ else if (event.getId() == "EQU")
+ {
+ window = equipmentWindow;
+ }
+ else if (event.getId() == "INV")
+ {
+ window = inventoryWindow;
+ }
+ else if (event.getId() == "SKI")
+ {
+ window = skillDialog;
+ }
+ else if (event.getId() == "SPE")
+ {
+ window = specialsWindow;
+ }
+ else if (event.getId() == "SH")
+ {
+ window = itemShortcutWindow;
+ }
+ else if (event.getId() == "SOC")
+ {
+ window = socialWindow;
+ }
+ else if (event.getId() == "DR")
+ {
+ window = dropShortcutWindow;
+ }
+ else if (event.getId() == "SET")
+ {
+ window = setupWindow;
+ }
+ else if (event.getId() == "ONL")
+ {
+ window = whoIsOnline;
+ }
+ else if (event.getId() == "KS")
+ {
+ window = killStats;
+ }
+ else if (event.getId() == "BC")
+ {
+ window = botCheckerWindow;
+ }
+ else if (event.getId() == "SP")
+ {
+ window = spellShortcutWindow;
+ }
+
+ if (window)
+ {
+ window->setVisible(!window->isVisible());
+ if (window->isVisible())
+ window->requestMoveToTop();
+ }
+}
+
+void WindowMenu::valueChanged(const gcn::SelectionEvent &event)
+{
+ if (event.getSource() == mEmotePopup)
+ {
+ int emote = mEmotePopup->getSelectedEmote();
+ if (emote && emoteShortcut)
+ emoteShortcut->useEmote(emote);
+
+ if (windowContainer)
+ windowContainer->scheduleDelete(mEmotePopup);
+ mEmotePopup = 0;
+ }
+}
+
+void WindowMenu::addButton(const char* text, std::string description,
+ int &x, int &h, int key)
+{
+ Button *btn = new Button(gettext(text), text, this);
+ btn->setPosition(x, 0);
+ btn->setDescription(description);
+ btn->setTag(key);
+ add(btn);
+ mButtons.push_back(btn);
+ x += btn->getWidth() + 3;
+ h = btn->getHeight();
+ mButtonNames[text] = btn;
+}
+
+void WindowMenu::mousePressed(gcn::MouseEvent &event)
+{
+ if (!viewport)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ Button *btn = dynamic_cast<Button*>(event.getSource());
+ if (!btn)
+ return;
+ if (viewport)
+ viewport->showPopup(event.getX(), event.getY(), btn);
+ }
+}
+
+void WindowMenu::mouseMoved(gcn::MouseEvent &event)
+{
+ if (!mTextPopup)
+ return;
+
+ if (event.getSource() == this)
+ {
+ mTextPopup->hide();
+ return;
+ }
+
+ Button *btn = dynamic_cast<Button*>(event.getSource());
+
+ if (!btn)
+ {
+ mTextPopup->hide();
+ return;
+ }
+
+ const int x = event.getX();
+ const int y = event.getY();
+ int key = btn->getTag();
+ if (key != KeyboardConfig::KEY_NO_VALUE)
+ {
+ mTextPopup->show(x + getX(), y + getY(), btn->getDescription(),
+ "Key: " + keyboard.getKeyValueString(key));
+ }
+ else
+ {
+ mTextPopup->show(x + getX(), y + getY(), btn->getDescription());
+ }
+}
+
+void WindowMenu::mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_)
+{
+ if (!mTextPopup)
+ return;
+
+ mTextPopup->hide();
+} \ No newline at end of file
diff --git a/src/gui/windowmenu.h b/src/gui/windowmenu.h
new file mode 100644
index 000000000..f619a161a
--- /dev/null
+++ b/src/gui/windowmenu.h
@@ -0,0 +1,82 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WINDOWMENU_H
+#define WINDOWMENU_H
+
+#include "gui/widgets/container.h"
+#include "gui/widgets/button.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#include <map>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class EmotePopup;
+class TextPopup;
+
+/**
+ * The window menu. Allows showing and hiding many of the different windows
+ * used in the game.
+ *
+ * \ingroup Interface
+ */
+class WindowMenu : public Container,
+ public gcn::ActionListener,
+ public gcn::SelectionListener,
+ public gcn::MouseListener
+{
+ public:
+ WindowMenu();
+ ~WindowMenu();
+
+ void action(const gcn::ActionEvent &event);
+
+ void valueChanged(const gcn::SelectionEvent &event);
+
+ void mousePressed(gcn::MouseEvent &event);
+
+ void mouseMoved(gcn::MouseEvent &event);
+
+ void mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_);
+
+ std::map <std::string, gcn::Button*> &getButtonNames()
+ { return mButtonNames; }
+
+ private:
+ inline void addButton(const char* text, std::string description,
+ int &x, int &h, int key);
+
+ EmotePopup *mEmotePopup;
+ TextPopup *mTextPopup;
+ std::list <gcn::Button*> mButtons;
+ std::map <std::string, gcn::Button*> mButtonNames;
+};
+
+extern WindowMenu *windowMenu;
+
+#endif
diff --git a/src/gui/worldselectdialog.cpp b/src/gui/worldselectdialog.cpp
new file mode 100644
index 000000000..f00871bd8
--- /dev/null
+++ b/src/gui/worldselectdialog.cpp
@@ -0,0 +1,139 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/worldselectdialog.h"
+
+#include "client.h"
+
+#include "gui/sdlinput.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+#include "net/worldinfo.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+extern WorldInfo **server_info;
+
+/**
+ * The list model for the server list.
+ */
+class WorldListModel : public gcn::ListModel
+{
+ public:
+ WorldListModel(Worlds worlds):
+ mWorlds(worlds)
+ {
+ }
+
+ virtual ~WorldListModel() {}
+
+ int getNumberOfElements()
+ {
+ return static_cast<int>(mWorlds.size());
+ }
+
+ std::string getElementAt(int i)
+ {
+ const WorldInfo *si = mWorlds[i];
+ if (si)
+ return si->name + " (" + toString(si->online_users) + ")";
+ else
+ return "???";
+ }
+ private:
+ Worlds mWorlds;
+};
+
+WorldSelectDialog::WorldSelectDialog(Worlds worlds):
+ Window(_("Select World"))
+{
+ mWorldListModel = new WorldListModel(worlds);
+ mWorldList = new ListBox(mWorldListModel);
+ ScrollArea *worldsScroll = new ScrollArea(mWorldList);
+ mChangeLoginButton = new Button(_("Change Login"), "login", this);
+ mChooseWorld = new Button(_("Choose World"), "world", this);
+
+ worldsScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ place(0, 0, worldsScroll, 3, 5).setPadding(2);
+ place(1, 5, mChangeLoginButton);
+ place(2, 5, mChooseWorld);
+
+ // Make sure the list has enough height
+ getLayout().setRowHeight(0, 60);
+
+ reflowLayout(0, 0);
+
+ if (worlds.size() == 0)
+ // Disable Ok button
+ mChooseWorld->setEnabled(false);
+ else
+ // Select first server
+ mWorldList->setSelected(0);
+
+ addKeyListener(this);
+
+ center();
+ setVisible(true);
+ mChooseWorld->requestFocus();
+}
+
+WorldSelectDialog::~WorldSelectDialog()
+{
+ delete mWorldListModel;
+ mWorldListModel = 0;
+}
+
+void WorldSelectDialog::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "world")
+ {
+ mChangeLoginButton->setEnabled(false);
+ mChooseWorld->setEnabled(false);
+ Net::getLoginHandler()->chooseServer(mWorldList->getSelected());
+
+ // Check in case netcode moves us forward
+ if (Client::getState() == STATE_WORLD_SELECT)
+ Client::setState(STATE_WORLD_SELECT_ATTEMPT);
+ }
+ else if (event.getId() == "login")
+ {
+ Client::setState(STATE_LOGIN);
+ }
+}
+
+void WorldSelectDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ gcn::Key key = keyEvent.getKey();
+
+ if (key.getValue() == Key::ESCAPE)
+ action(gcn::ActionEvent(NULL, mChangeLoginButton->getActionEventId()));
+ else if (key.getValue() == Key::ENTER)
+ action(gcn::ActionEvent(NULL, mChooseWorld->getActionEventId()));
+}
diff --git a/src/gui/worldselectdialog.h b/src/gui/worldselectdialog.h
new file mode 100644
index 000000000..e22438714
--- /dev/null
+++ b/src/gui/worldselectdialog.h
@@ -0,0 +1,73 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WORLD_SELECT_DIALOG_H
+#define WORLD_SELECT_DIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include "net/worldinfo.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/listmodel.hpp>
+
+#include <vector>
+
+class LoginData;
+class WorldListModel;
+
+/**
+ * The server select dialog.
+ *
+ * \ingroup Interface
+ */
+class WorldSelectDialog : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ WorldSelectDialog(Worlds worlds);
+
+ /**
+ * Destructor.
+ */
+ ~WorldSelectDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event);
+
+ void keyPressed(gcn::KeyEvent &keyEvent);
+
+ private:
+ WorldListModel *mWorldListModel;
+ gcn::ListBox *mWorldList;
+ gcn::Button *mChangeLoginButton;
+ gcn::Button *mChooseWorld;
+};
+
+#endif // WORLD_SELECT_DIALOG_H