summaryrefslogtreecommitdiff
path: root/src/gui/windows
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/windows')
-rw-r--r--src/gui/windows/botcheckerwindow.cpp421
-rw-r--r--src/gui/windows/botcheckerwindow.h94
-rw-r--r--src/gui/windows/buydialog.cpp553
-rw-r--r--src/gui/windows/buydialog.h163
-rw-r--r--src/gui/windows/buyselldialog.cpp150
-rw-r--r--src/gui/windows/buyselldialog.h83
-rw-r--r--src/gui/windows/changeemaildialog.cpp181
-rw-r--r--src/gui/windows/changeemaildialog.h80
-rw-r--r--src/gui/windows/changepassworddialog.cpp172
-rw-r--r--src/gui/windows/changepassworddialog.h75
-rw-r--r--src/gui/windows/charcreatedialog.cpp698
-rw-r--r--src/gui/windows/charcreatedialog.h165
-rw-r--r--src/gui/windows/charselectdialog.cpp587
-rw-r--r--src/gui/windows/charselectdialog.h126
-rw-r--r--src/gui/windows/chatwindow.cpp1795
-rw-r--r--src/gui/windows/chatwindow.h378
-rw-r--r--src/gui/windows/confirmdialog.cpp109
-rw-r--r--src/gui/windows/confirmdialog.h65
-rw-r--r--src/gui/windows/connectiondialog.cpp71
-rw-r--r--src/gui/windows/connectiondialog.h64
-rw-r--r--src/gui/windows/debugwindow.cpp537
-rw-r--r--src/gui/windows/debugwindow.h165
-rw-r--r--src/gui/windows/didyouknowwindow.cpp177
-rw-r--r--src/gui/windows/didyouknowwindow.h78
-rw-r--r--src/gui/windows/editdialog.cpp72
-rw-r--r--src/gui/windows/editdialog.h69
-rw-r--r--src/gui/windows/editserverdialog.cpp300
-rw-r--r--src/gui/windows/editserverdialog.h113
-rw-r--r--src/gui/windows/emotewindow.cpp229
-rw-r--r--src/gui/windows/emotewindow.h80
-rw-r--r--src/gui/windows/equipmentwindow.cpp669
-rw-r--r--src/gui/windows/equipmentwindow.h155
-rw-r--r--src/gui/windows/helpwindow.cpp196
-rw-r--r--src/gui/windows/helpwindow.h93
-rw-r--r--src/gui/windows/inventorywindow.cpp835
-rw-r--r--src/gui/windows/inventorywindow.h196
-rw-r--r--src/gui/windows/itemamountwindow.cpp451
-rw-r--r--src/gui/windows/itemamountwindow.h126
-rw-r--r--src/gui/windows/killstats.cpp520
-rw-r--r--src/gui/windows/killstats.h132
-rw-r--r--src/gui/windows/logindialog.cpp413
-rw-r--r--src/gui/windows/logindialog.h108
-rw-r--r--src/gui/windows/minimap.cpp485
-rw-r--r--src/gui/windows/minimap.h89
-rw-r--r--src/gui/windows/ministatuswindow.cpp527
-rw-r--r--src/gui/windows/ministatuswindow.h135
-rw-r--r--src/gui/windows/npcdialog.cpp961
-rw-r--r--src/gui/windows/npcdialog.h301
-rw-r--r--src/gui/windows/npcpostdialog.cpp136
-rw-r--r--src/gui/windows/npcpostdialog.h74
-rw-r--r--src/gui/windows/okdialog.cpp89
-rw-r--r--src/gui/windows/okdialog.h71
-rw-r--r--src/gui/windows/outfitwindow.cpp662
-rw-r--r--src/gui/windows/outfitwindow.h129
-rw-r--r--src/gui/windows/questswindow.cpp567
-rw-r--r--src/gui/windows/questswindow.h106
-rw-r--r--src/gui/windows/quitdialog.cpp256
-rw-r--r--src/gui/windows/quitdialog.h83
-rw-r--r--src/gui/windows/registerdialog.cpp316
-rw-r--r--src/gui/windows/registerdialog.h112
-rw-r--r--src/gui/windows/selldialog.cpp389
-rw-r--r--src/gui/windows/selldialog.h147
-rw-r--r--src/gui/windows/serverdialog.cpp867
-rw-r--r--src/gui/windows/serverdialog.h194
-rw-r--r--src/gui/windows/setup.cpp255
-rw-r--r--src/gui/windows/setup.h82
-rw-r--r--src/gui/windows/shopwindow.cpp866
-rw-r--r--src/gui/windows/shopwindow.h179
-rw-r--r--src/gui/windows/shortcutwindow.cpp242
-rw-r--r--src/gui/windows/shortcutwindow.h90
-rw-r--r--src/gui/windows/skilldialog.cpp722
-rw-r--r--src/gui/windows/skilldialog.h109
-rw-r--r--src/gui/windows/socialwindow.cpp1895
-rw-r--r--src/gui/windows/socialwindow.h170
-rw-r--r--src/gui/windows/statuswindow.cpp888
-rw-r--r--src/gui/windows/statuswindow.h125
-rw-r--r--src/gui/windows/textcommandeditor.cpp396
-rw-r--r--src/gui/windows/textcommandeditor.h103
-rw-r--r--src/gui/windows/textdialog.cpp131
-rw-r--r--src/gui/windows/textdialog.h80
-rw-r--r--src/gui/windows/tradewindow.cpp486
-rw-r--r--src/gui/windows/tradewindow.h187
-rw-r--r--src/gui/windows/unregisterdialog.cpp154
-rw-r--r--src/gui/windows/unregisterdialog.h70
-rw-r--r--src/gui/windows/updaterwindow.cpp943
-rw-r--r--src/gui/windows/updaterwindow.h258
-rw-r--r--src/gui/windows/whoisonline.cpp866
-rw-r--r--src/gui/windows/whoisonline.h223
-rw-r--r--src/gui/windows/worldselectdialog.cpp166
-rw-r--r--src/gui/windows/worldselectdialog.h74
90 files changed, 28200 insertions, 0 deletions
diff --git a/src/gui/windows/botcheckerwindow.cpp b/src/gui/windows/botcheckerwindow.cpp
new file mode 100644
index 000000000..f51683b40
--- /dev/null
+++ b/src/gui/windows/botcheckerwindow.cpp
@@ -0,0 +1,421 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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/windows/botcheckerwindow.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/guitable.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+
+#include "being/localplayer.h"
+
+#include "utils/gettext.h"
+
+#include <vector>
+
+#include "debug.h"
+
+const int COLUMNS_NR = 5; // name plus listbox
+const int NAME_COLUMN = 0;
+const int TIME_COLUMN = 1;
+
+const int 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.
+const int NAME_COLUMN_WIDTH = 185;
+const int TIME_COLUMN_WIDTH = 70;
+
+#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column)
+
+class UsersTableModel final : public TableModel,
+ public Widget2
+{
+public:
+ explicit UsersTableModel(const Widget2 *const widget) :
+ TableModel(),
+ Widget2(widget),
+ mPlayers(0),
+ mWidgets()
+ {
+ playersUpdated();
+ }
+
+ A_DELETE_COPY(UsersTableModel)
+
+ ~UsersTableModel()
+ {
+ freeWidgets();
+ }
+
+ int getRows() const
+ {
+ return static_cast<int>(mPlayers.size());
+ }
+
+ int getColumns() const
+ {
+ return COLUMNS_NR;
+ }
+
+ int getRowHeight() const
+ {
+ return ROW_HEIGHT;
+ }
+
+ int getColumnWidth(const int index) const
+ {
+ if (index == NAME_COLUMN)
+ return NAME_COLUMN_WIDTH;
+ else
+ return TIME_COLUMN_WIDTH;
+ }
+
+ void playersUpdated()
+ {
+ signalBeforeUpdate();
+
+ freeWidgets();
+ mPlayers.clear();
+ if (actorSpriteManager && botCheckerWindow
+ && botCheckerWindow->mEnabled)
+ {
+ std::set<ActorSprite*> beings = actorSpriteManager->getAll();
+ FOR_EACH (ActorSprites::const_iterator, i, beings)
+ {
+ Being *const being = dynamic_cast<Being*>(*i);
+
+ if (being && being->getType() == Being::PLAYER
+ && being != player_node && being->getName() != "")
+ {
+ mPlayers.push_back(being);
+ }
+ }
+ }
+
+ const unsigned int curTime = cur_time;
+ const unsigned int sz = mPlayers.size();
+ // set up widgets
+ for (unsigned int r = 0; r < sz; ++r)
+ {
+ if (!mPlayers.at(r))
+ continue;
+
+ const Being *const player = mPlayers.at(r);
+ gcn::Widget *widget = new Label(this, player->getName());
+
+ mWidgets.push_back(widget);
+
+ if (player->getAttackTime() != 0)
+ {
+ widget = new Label(this, toString(curTime
+ - player->getAttackTime()));
+ }
+ else
+ {
+ widget = new Label(this, toString(curTime
+ - player->getTestTime()).append("?"));
+ }
+ mWidgets.push_back(widget);
+
+ if (player->getTalkTime() != 0)
+ {
+ widget = new Label(this, toString(curTime
+ - player->getTalkTime()));
+ }
+ else
+ {
+ widget = new Label(this, toString(curTime
+ - player->getTestTime()).append("?"));
+ }
+ mWidgets.push_back(widget);
+
+ if (player->getMoveTime() != 0)
+ {
+ widget = new Label(this, toString(curTime
+ - player->getMoveTime()));
+ }
+ else
+ {
+ widget = new Label(this, toString(curTime
+ - player->getTestTime()).append("?"));
+ }
+ mWidgets.push_back(widget);
+
+ std::string str;
+ bool talkBot = false;
+ bool moveBot = false;
+ bool attackBot = false;
+ bool otherBot = false;
+
+ if (curTime - player->getTestTime() > 2 * 60)
+ {
+ const int attack = curTime - (player->getAttackTime()
+ ? player->getAttackTime()
+ : player->getTestTime());
+ const int talk = curTime - (player->getTalkTime()
+ ? player->getTalkTime()
+ : player->getTestTime()) - attack;
+ const int move = curTime - (player->getMoveTime()
+ ? player->getMoveTime()
+ : player->getTestTime()) - attack;
+ const int other = curTime - (player->getOtherTime()
+ ? player->getMoveTime()
+ : player->getOtherTime()) - attack;
+
+ if (attack < 2 * 60)
+ attackBot = true;
+
+ // attacking but not talking more than 2 minutes
+ if (talk > 2 * 60)
+ {
+ talkBot = true;
+ str.append(toString((talk) / 60)).append(" ");
+ }
+
+ // attacking but not moving more than 2 minutes
+ if (move > 2 * 60)
+ {
+ moveBot = true;
+ str.append(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(this, str);
+ mWidgets.push_back(widget);
+ }
+
+ signalAfterUpdate();
+ }
+
+ void updateModelInRow(const int row A_UNUSED) const
+ {
+ }
+
+ gcn::Widget *getElementAt(const int row, const int column) const
+ {
+ return mWidgets[WIDGET_AT(row, column)];
+ }
+
+ 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():
+ // TRANSLATORS: bot checker window header
+ Window(_("Bot Checker"), false, nullptr, "botchecker.xml"),
+ gcn::ActionListener(),
+ mTableModel(new UsersTableModel(this)),
+ mTable(new GuiTable(this, mTableModel)),
+ playersScrollArea(new ScrollArea(mTable, true,
+ "bochecker_background.xml")),
+ mPlayerTableTitleModel(new StaticTableModel(1, COLUMNS_NR)),
+ mPlayerTitleTable(new GuiTable(this, mPlayerTableTitleModel)),
+ // TRANSLATORS: bot checker window button
+ mIncButton(new Button(this, _("Reset"), "reset", this)),
+ mLastUpdateTime(0),
+ mNeedUpdate(false),
+ mEnabled(false)
+{
+ const int w = 500;
+ const int h = 250;
+
+ setSaveVisible(true);
+
+ mTable->setOpaque(false);
+ mTable->setLinewiseSelection(true);
+ mTable->setWrappingEnabled(true);
+ mTable->setActionEventId("skill");
+ mTable->addActionListener(this);
+
+ mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH);
+
+ for (int f = 0; f < 4; f++)
+ {
+ mPlayerTableTitleModel->fixColumnWidth(
+ TIME_COLUMN + f, TIME_COLUMN_WIDTH);
+ }
+
+ mPlayerTitleTable->setHeight(1);
+
+ // TRANSLATORS: bot checker window table header
+ mPlayerTableTitleModel->set(0, 0, new Label(this, _("Name")));
+ // TRANSLATORS: bot checker window table header
+ mPlayerTableTitleModel->set(0, 1, new Label(this, _("Attack")));
+ // TRANSLATORS: bot checker window table header
+ mPlayerTableTitleModel->set(0, 2, new Label(this, _("Talk")));
+ // TRANSLATORS: bot checker window table header
+ mPlayerTableTitleModel->set(0, 3, new Label(this, _("Move")));
+ // TRANSLATORS: bot checker window table header
+ mPlayerTableTitleModel->set(0, 4, new Label(this, _("Result")));
+
+ mPlayerTitleTable->setLinewiseSelection(true);
+
+ setWindowName("BotCheckerWindow");
+ setCloseButton(true);
+ setStickyButtonLock(true);
+ setDefaultSize(w, h, ImageRect::CENTER);
+
+ playersScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+
+ mPlayerTitleTable->setPosition(mPadding, mPadding);
+ mPlayerTitleTable->setWidth(w - 10);
+ mPlayerTitleTable->setHeight(20);
+
+ playersScrollArea->setPosition(mPadding, 20 + 2 * mPadding);
+ playersScrollArea->setWidth(w - 15);
+ playersScrollArea->setHeight(h - 80);
+
+ mIncButton->setPosition(mPadding, 190 + 3 * mPadding);
+ mIncButton->setWidth(80);
+ mIncButton->setHeight(20);
+
+ add(mPlayerTitleTable);
+ add(playersScrollArea);
+ add(mIncButton);
+
+ center();
+
+ setWidth(w);
+ setHeight(h);
+ loadWindowState();
+ enableVisibleSound(true);
+
+ config.addListener("enableBotCheker", this);
+ mEnabled = config.getBoolValue("enableBotCheker");
+}
+
+BotCheckerWindow::~BotCheckerWindow()
+{
+ config.removeListener("enableBotCheker", this);
+}
+
+void BotCheckerWindow::slowLogic()
+{
+ BLOCK_START("BotCheckerWindow::slowLogic")
+ if (mEnabled && mTableModel)
+ {
+ const 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;
+ }
+ }
+ BLOCK_END("BotCheckerWindow::slowLogic")
+}
+
+void BotCheckerWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "reset")
+ {
+ reset();
+ mNeedUpdate = true;
+ }
+}
+
+void BotCheckerWindow::update()
+{
+}
+
+void BotCheckerWindow::updateList()
+{
+ if (mTableModel)
+ mNeedUpdate = true;
+}
+
+void BotCheckerWindow::reset()
+{
+ if (actorSpriteManager)
+ {
+ std::set<ActorSprite*> beings = actorSpriteManager->getAll();
+ FOR_EACH (ActorSprites::const_iterator, i, beings)
+ {
+ Being *const 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");
+}
+
+#ifdef USE_PROFILER
+void BotCheckerWindow::logicChildren()
+{
+ BLOCK_START("BotCheckerWindow::logicChildren")
+ BasicContainer::logicChildren();
+ BLOCK_END("BotCheckerWindow::logicChildren")
+}
+#endif
diff --git a/src/gui/windows/botcheckerwindow.h b/src/gui/windows/botcheckerwindow.h
new file mode 100644
index 000000000..fbe53cf82
--- /dev/null
+++ b/src/gui/windows/botcheckerwindow.h
@@ -0,0 +1,94 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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_BOTCHECKERWINDOW_H
+#define GUI_BOTCHECKERWINDOW_H
+
+#include "configlistener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+struct BOTCHK final
+{
+ int16_t id; /**< Index into "botchecker_db" array */
+ int16_t lv;
+ int16_t sp;
+};
+
+class Button;
+class GuiTable;
+class ScrollArea;
+class UsersTableModel;
+class StaticTableModel;
+
+class BotCheckerWindow final : public Window,
+ public gcn::ActionListener,
+ public ConfigListener
+{
+ public:
+ friend class UsersTableModel;
+
+ /**
+ * Constructor.
+ */
+ BotCheckerWindow();
+
+ A_DELETE_COPY(BotCheckerWindow)
+
+ /**
+ * Destructor.
+ */
+ ~BotCheckerWindow();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void update();
+
+ void slowLogic();
+
+ void updateList();
+
+ void reset();
+
+ void optionChanged(const std::string &name) override;
+
+#ifdef USE_PROFILER
+ void logicChildren();
+#endif
+
+ private:
+ UsersTableModel *mTableModel;
+ GuiTable *mTable;
+ ScrollArea *playersScrollArea;
+ StaticTableModel *mPlayerTableTitleModel;
+ GuiTable *mPlayerTitleTable;
+ Button *mIncButton;
+ int mLastUpdateTime;
+ bool mNeedUpdate;
+ bool mEnabled;
+};
+
+extern BotCheckerWindow *botCheckerWindow;
+
+#endif // GUI_BOTCHECKERWINDOW_H
diff --git a/src/gui/windows/buydialog.cpp b/src/gui/windows/buydialog.cpp
new file mode 100644
index 000000000..44b143e50
--- /dev/null
+++ b/src/gui/windows/buydialog.cpp
@@ -0,0 +1,553 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/buydialog.h"
+
+#include "shopitem.h"
+#include "units.h"
+
+#include "gui/windows/tradewindow.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/inttextfield.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/adminhandler.h"
+#include "net/buysellhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+
+#include <algorithm>
+
+#include "debug.h"
+
+static const char *const SORT_NAME_BUY[7] =
+{
+ // TRANSLATORS: buy dialog sort type.
+ N_("unsorted"),
+ // TRANSLATORS: buy dialog sort type.
+ N_("by price"),
+ // TRANSLATORS: buy dialog sort type.
+ N_("by name"),
+ // TRANSLATORS: buy dialog sort type.
+ N_("by id"),
+ // TRANSLATORS: buy dialog sort type.
+ N_("by weight"),
+ // TRANSLATORS: buy dialog sort type.
+ N_("by amount"),
+ // TRANSLATORS: buy dialog sort type.
+ N_("by type")
+};
+
+class SortListModelBuy final : public gcn::ListModel
+{
+public:
+ ~SortListModelBuy()
+ { }
+
+ int getNumberOfElements()
+ { return 7; }
+
+ std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "???";
+ return gettext(SORT_NAME_BUY[i]);
+ }
+};
+
+class SortItemPriceFunctor final
+{
+ public:
+ bool operator() (const ShopItem *const item1,
+ const ShopItem *const item2) const
+ {
+ if (!item1 || !item2)
+ return false;
+
+ const int price1 = item1->getPrice();
+ const int price2 = item2->getPrice();
+ if (price1 == price2)
+ return item1->getDisplayName() < item2->getDisplayName();
+ return price1 < price2;
+ }
+} itemPriceBuySorter;
+
+class SortItemNameFunctor final
+{
+ public:
+ bool operator() (const ShopItem *const item1,
+ const ShopItem *const item2) const
+ {
+ if (!item1 || !item2)
+ return false;
+
+ const std::string &name1 = item1->getDisplayName();
+ const std::string &name2 = item2->getDisplayName();
+ if (name1 == name2)
+ return item1->getPrice() < item2->getPrice();
+ return name1 < name2;
+ }
+} itemNameBuySorter;
+
+class SortItemIdFunctor final
+{
+ public:
+ bool operator() (const ShopItem *const item1,
+ const ShopItem *const item2) const
+ {
+ if (!item1 || !item2)
+ return false;
+
+ const int id1 = item1->getId();
+ const int id2 = item2->getId();
+ if (id1 == id2)
+ return item1->getPrice() < item2->getPrice();
+ return id1 < id2;
+ }
+} itemIdBuySorter;
+
+class SortItemWeightFunctor final
+{
+ public:
+ bool operator() (const ShopItem *const item1,
+ const ShopItem *const item2) const
+ {
+ if (!item1 || !item2)
+ return false;
+
+ const int weight1 = item1->getInfo().getWeight();
+ const int weight2 = item2->getInfo().getWeight();
+ if (weight1 == weight2)
+ return item1->getPrice() < item2->getPrice();
+ return weight1 < weight2;
+ }
+} itemWeightBuySorter;
+
+class SortItemAmountFunctor final
+{
+ public:
+ bool operator() (const ShopItem *const item1,
+ const ShopItem *const item2) const
+ {
+ if (!item1 || !item2)
+ return false;
+
+ const int amount1 = item1->getQuantity();
+ const int amount2 = item2->getQuantity();
+ if (amount1 == amount2)
+ return item1->getPrice() < item2->getPrice();
+ return amount1 < amount2;
+ }
+} itemAmountBuySorter;
+
+class SortItemTypeFunctor final
+{
+ public:
+ bool operator() (const ShopItem *const item1,
+ const ShopItem *const item2) const
+ {
+ if (!item1 || !item2)
+ return false;
+
+ const int type1 = item1->getInfo().getType();
+ const int type2 = item2->getInfo().getType();
+ if (type1 == type2)
+ return item1->getPrice() < item2->getPrice();
+ return type1 < type2;
+ }
+} itemTypeBuySorter;
+
+BuyDialog::DialogList BuyDialog::instances;
+
+BuyDialog::BuyDialog() :
+ // TRANSLATORS: buy dialog name
+ Window(_("Create items"), false, nullptr, "buy.xml"),
+ gcn::ActionListener(),
+ gcn::SelectionListener(),
+ mNpcId(-2), mMoney(0), mAmountItems(0), mMaxItems(0), mNick(),
+ mSortModel(nullptr),
+ mSortDropDown(nullptr)
+{
+ init();
+}
+
+BuyDialog::BuyDialog(const int npcId) :
+ // TRANSLATORS: buy dialog name
+ Window(_("Buy"), false, nullptr, "buy.xml"),
+ gcn::ActionListener(),
+ gcn::SelectionListener(),
+ mNpcId(npcId), mMoney(0), mAmountItems(0), mMaxItems(0), mNick(),
+ mSortModel(nullptr),
+ mSortDropDown(nullptr)
+{
+ init();
+}
+
+BuyDialog::BuyDialog(std::string nick) :
+ // TRANSLATORS: buy dialog name
+ Window(_("Buy"), false, nullptr, "buy.xml"),
+ gcn::ActionListener(),
+ gcn::SelectionListener(),
+ mNpcId(-1), mMoney(0), mAmountItems(0), mMaxItems(0), mNick(nick),
+ mSortModel(new SortListModelBuy),
+ mSortDropDown(new DropDown(this, mSortModel, false, false, this, "sort"))
+{
+ init();
+}
+
+void BuyDialog::init()
+{
+ setWindowName("Buy");
+ setResizable(true);
+ setCloseButton(true);
+ setStickyButtonLock(true);
+ setMinWidth(260);
+ setMinHeight(220);
+ setDefaultSize(260, 230, ImageRect::CENTER);
+
+ mShopItems = new ShopItems;
+
+ mShopItemList = new ShopListBox(this, mShopItems, mShopItems);
+ mScrollArea = new ScrollArea(mShopItemList,
+ getOptionBool("showbackground"), "buy_background.xml");
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mSlider = new Slider(1.0);
+ mQuantityLabel = new Label(this, strprintf(
+ "%d / %d", mAmountItems, mMaxItems));
+ mQuantityLabel->setAlignment(gcn::Graphics::CENTER);
+ // TRANSLATORS: buy dialog label
+ mMoneyLabel = new Label(this, strprintf(
+ _("Price: %s / Total: %s"), "", ""));
+
+ mAmountField = new IntTextField(this, 1, 1, 123);
+ mAmountField->setActionEventId("amount");
+ mAmountField->addActionListener(this);
+ mAmountField->setSendAlwaysEvents(true);
+ mAmountField->setEnabled(false);
+
+ // TRANSLATORS: buy dialog label
+ mAmountLabel = new Label(this, _("Amount:"));
+ mAmountLabel->adjustSize();
+
+ // TRANSLATORS: This is a narrow symbol used to denote 'increasing'.
+ // You may change this symbol if your language uses another.
+ mIncreaseButton = new Button(this, _("+"), "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(this, _("-"), "dec", this);
+ // TRANSLATORS: buy dialog button
+ mBuyButton = new Button(this, mNpcId == -2
+ ? _("Create") :_("Buy"), "buy", this);
+ // TRANSLATORS: buy dialog button
+ mQuitButton = new Button(this, _("Quit"), "quit", this);
+ // TRANSLATORS: buy dialog button
+ mAddMaxButton = new Button(this, _("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->setDistributeMousePressed(false);
+ mShopItemList->setActionEventId("buy");
+ mShopItemList->addActionListener(this);
+ mShopItemList->addSelectionListener(this);
+
+ ContainerPlacer placer = getPlacer(0, 0);
+ placer(0, 0, mScrollArea, 9, 5).setPadding(3);
+ placer(0, 5, mDecreaseButton);
+ placer(1, 5, mSlider, 4);
+ placer(5, 5, mIncreaseButton);
+ placer(6, 5, mQuantityLabel, 2);
+ placer(8, 5, mAddMaxButton);
+ placer(0, 6, mAmountLabel, 2);
+ placer(2, 6, mAmountField, 2);
+ placer(0, 7, mMoneyLabel, 8);
+ if (mSortDropDown)
+ placer(0, 8, mSortDropDown, 2);
+ placer(7, 8, mBuyButton);
+ placer(8, 8, mQuitButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ center();
+ loadWindowState();
+ enableVisibleSound(true);
+
+ instances.push_back(this);
+ setVisible(true);
+}
+
+BuyDialog::~BuyDialog()
+{
+ delete mShopItems;
+ mShopItems = nullptr;
+
+ instances.remove(this);
+}
+
+void BuyDialog::setMoney(const 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(const int id, const unsigned char color,
+ const int amount, const int price)
+{
+ mShopItems->addItem(id, color, amount, price);
+ mShopItemList->adjustSize();
+}
+
+void BuyDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "quit")
+ {
+ close();
+ return;
+ }
+ else if (eventId == "sort")
+ {
+ if (mSortDropDown && mShopItems)
+ {
+ std::vector<ShopItem*> &items = mShopItems->items();
+ switch (mSortDropDown->getSelected())
+ {
+ case 1:
+ std::sort(items.begin(), items.end(), itemPriceBuySorter);
+ break;
+ case 2:
+ std::sort(items.begin(), items.end(), itemNameBuySorter);
+ break;
+ case 3:
+ std::sort(items.begin(), items.end(), itemIdBuySorter);
+ break;
+ case 4:
+ std::sort(items.begin(), items.end(), itemWeightBuySorter);
+ break;
+ case 5:
+ std::sort(items.begin(), items.end(), itemAmountBuySorter);
+ break;
+ case 6:
+ std::sort(items.begin(), items.end(), itemTypeBuySorter);
+ break;
+ case 0:
+ default:
+ break;
+ }
+ }
+ return;
+ }
+
+ const int selectedItem = mShopItemList->getSelected();
+
+ // The following actions require a valid selection
+ if (selectedItem < 0 || selectedItem >= mShopItems->getNumberOfElements())
+ return;
+
+ if (eventId == "slider")
+ {
+ mAmountItems = static_cast<int>(mSlider->getValue());
+ mAmountField->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "inc" && mAmountItems < mMaxItems)
+ {
+ mAmountItems++;
+ mSlider->setValue(mAmountItems);
+ mAmountField->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "dec" && mAmountItems > 1)
+ {
+ mAmountItems--;
+ mSlider->setValue(mAmountItems);
+ mAmountField->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "max")
+ {
+ mAmountItems = mMaxItems;
+ mSlider->setValue(mAmountItems);
+ mAmountField->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "amount")
+ {
+ mAmountItems = mAmountField->getValue();
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "buy" && mAmountItems > 0 && mAmountItems <= mMaxItems)
+ {
+ if (mNpcId == -2)
+ {
+ const ShopItem *const item = mShopItems->at(selectedItem);
+ Net::getAdminHandler()->createItems(item->getId(),
+ mAmountItems, item->getColor());
+ }
+ else if (mNpcId != -1)
+ {
+ const ShopItem *const item = mShopItems->at(selectedItem);
+ Net::getNpcHandler()->buyItem(mNpcId, item->getId(),
+ item->getColor(), 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->setScale(1, mMaxItems);
+ }
+ else if (tradeWindow)
+ {
+ const ShopItem *const item = mShopItems->at(selectedItem);
+ if (item)
+ {
+ Net::getBuySellHandler()->sendBuyRequest(mNick,
+ item, mAmountItems);
+ if (tradeWindow)
+ {
+ tradeWindow->addAutoMoney(mNick,
+ item->getPrice() * mAmountItems);
+ }
+ }
+ }
+ }
+}
+
+void BuyDialog::valueChanged(const gcn::SelectionEvent &event A_UNUSED)
+{
+ // Reset amount of items and update labels
+ mAmountItems = 1;
+ mSlider->setValue(1);
+
+ updateButtonsAndLabels();
+ mSlider->setScale(1, mMaxItems);
+ mAmountField->setRange(1, mMaxItems);
+ mAmountField->setValue(1);
+}
+
+void BuyDialog::updateButtonsAndLabels()
+{
+ const int selectedItem = mShopItemList->getSelected();
+ int price = 0;
+
+ if (selectedItem > -1)
+ {
+ const ShopItem *const item = mShopItems->at(selectedItem);
+ if (item)
+ {
+ const int itemPrice = item->getPrice();
+
+ // Calculate how many the player can afford
+ if (mNpcId == -2)
+ mMaxItems = 100;
+ else if (itemPrice)
+ mMaxItems = mMoney / itemPrice;
+ else
+ mMaxItems = 1;
+
+ if (item->getQuantity() > 0 && mMaxItems > item->getQuantity())
+ mMaxItems = item->getQuantity();
+
+ if (mAmountItems > mMaxItems)
+ mAmountItems = mMaxItems;
+
+ price = mAmountItems * itemPrice;
+ }
+ }
+ else
+ {
+ mMaxItems = 0;
+ mAmountItems = 0;
+ }
+
+ mIncreaseButton->setEnabled(mAmountItems < mMaxItems);
+ mDecreaseButton->setEnabled(mAmountItems > 1);
+ mBuyButton->setEnabled(mAmountItems > 0);
+ mSlider->setEnabled(mMaxItems > 1);
+ mAmountField->setEnabled(mAmountItems > 0);
+
+ mQuantityLabel->setCaption(strprintf("%d / %d", mAmountItems, mMaxItems));
+ // TRANSLATORS: buy dialog label
+ 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()
+{
+ FOR_EACH (DialogList::const_iterator, it, instances)
+ {
+ if (*it)
+ (*it)->close();
+ }
+}
diff --git a/src/gui/windows/buydialog.h b/src/gui/windows/buydialog.h
new file mode 100644
index 000000000..875184851
--- /dev/null
+++ b/src/gui/windows/buydialog.h
@@ -0,0 +1,163 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_BUYDIALOG_H
+#define GUI_BUYDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+class Button;
+class DropDown;
+class ShopItems;
+class ShopListBox;
+class SortListModelBuy;
+class IntTextField;
+class Label;
+class ListBox;
+class ScrollArea;
+class Slider;
+
+/**
+ * The buy dialog.
+ *
+ * \ingroup Interface
+ */
+class BuyDialog final : public Window,
+ public gcn::ActionListener,
+ public gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ BuyDialog();
+
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ explicit BuyDialog(const int npcId);
+
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ explicit BuyDialog(std::string nick);
+
+ A_DELETE_COPY(BuyDialog)
+
+ /**
+ * Destructor
+ */
+ ~BuyDialog();
+
+ void init();
+
+ /**
+ * Resets the dialog, clearing shop inventory.
+ */
+ void reset();
+
+ /**
+ * Sets the amount of available money.
+ */
+ void setMoney(const int amount);
+
+ /**
+ * Adds an item to the shop inventory.
+ */
+ void addItem(const int id, const unsigned char color,
+ const int amount, const int price);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Returns the number of items in the shop inventory.
+ */
+ int getNumberOfElements() A_WARN_UNUSED;
+
+ /**
+ * Updates the labels according to the selected item.
+ */
+ void valueChanged(const gcn::SelectionEvent &event) override;
+
+ /**
+ * 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() A_WARN_UNUSED
+ { return !instances.empty(); }
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+ private:
+ typedef std::list<BuyDialog*> DialogList;
+ static DialogList instances;
+
+ int mNpcId;
+
+ Button *mBuyButton;
+ Button *mQuitButton;
+ Button *mAddMaxButton;
+ Button *mIncreaseButton;
+ Button *mDecreaseButton;
+ ShopListBox *mShopItemList;
+ ScrollArea *mScrollArea;
+ Label *mMoneyLabel;
+ Label *mQuantityLabel;
+ Slider *mSlider;
+ Label *mAmountLabel;
+ IntTextField *mAmountField;
+
+ ShopItems *mShopItems;
+
+ int mMoney;
+ int mAmountItems;
+ int mMaxItems;
+ std::string mNick;
+ SortListModelBuy *mSortModel;
+ DropDown *mSortDropDown;
+};
+
+#endif // GUI_BUYDIALOG_H
diff --git a/src/gui/windows/buyselldialog.cpp b/src/gui/windows/buyselldialog.cpp
new file mode 100644
index 000000000..d26b0ceb8
--- /dev/null
+++ b/src/gui/windows/buyselldialog.cpp
@@ -0,0 +1,150 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/buyselldialog.h"
+
+#include "net/buysellhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "gui/widgets/button.h"
+
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+BuySellDialog::DialogList BuySellDialog::dialogInstances;
+
+BuySellDialog::BuySellDialog(const int npcId) :
+ // TRANSLATORS: shop window name
+ Window(_("Shop"), false, nullptr, "buysell.xml"),
+ gcn::ActionListener(),
+ mNpcId(npcId),
+ mNick(""),
+ mBuyButton(nullptr)
+{
+ init();
+}
+
+BuySellDialog::BuySellDialog(const std::string &nick) :
+ // TRANSLATORS: shop window name
+ Window(_("Shop"), false, nullptr, "buysell.xml"),
+ gcn::ActionListener(),
+ mNpcId(-1),
+ mNick(nick),
+ mBuyButton(nullptr)
+{
+ init();
+}
+
+void BuySellDialog::init()
+{
+ setWindowName("BuySell");
+ setCloseButton(true);
+
+ static const char *buttonNames[] =
+ {
+ // TRANSLATORS: shop window button
+ N_("Buy"),
+ // TRANSLATORS: shop window button
+ N_("Sell"),
+ // TRANSLATORS: shop window button
+ N_("Cancel"),
+ nullptr
+ };
+ const int buttonPadding = getOption("buttonpadding", 10);
+ int x = buttonPadding;
+ const int y = buttonPadding;
+
+ for (const char *const *curBtn = buttonNames; *curBtn; curBtn++)
+ {
+ Button *const btn = new Button(this, gettext(*curBtn), *curBtn, this);
+ if (!mBuyButton)
+ mBuyButton = btn; // For focus request
+ btn->setPosition(x, y);
+ add(btn);
+ x += btn->getWidth() + buttonPadding;
+ }
+ if (mBuyButton)
+ {
+ mBuyButton->requestFocus();
+ setContentSize(x, 2 * y + mBuyButton->getHeight());
+ }
+
+ center();
+ setDefaultSize();
+ loadWindowState();
+ enableVisibleSound(true);
+
+ dialogInstances.push_back(this);
+ setVisible(true);
+}
+
+BuySellDialog::~BuySellDialog()
+{
+ dialogInstances.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)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "Buy")
+ {
+ if (mNpcId != -1)
+ Net::getNpcHandler()->buy(mNpcId);
+ else
+ Net::getBuySellHandler()->requestSellList(mNick);
+ }
+ else if (eventId == "Sell")
+ {
+ if (mNpcId != -1)
+ Net::getNpcHandler()->sell(mNpcId);
+ else
+ Net::getBuySellHandler()->requestBuyList(mNick);
+ }
+
+ close();
+}
+
+void BuySellDialog::closeAll()
+{
+ FOR_EACH (DialogList::const_iterator, it, dialogInstances)
+ {
+ if (*it)
+ (*it)->close();
+ }
+}
diff --git a/src/gui/windows/buyselldialog.h b/src/gui/windows/buyselldialog.h
new file mode 100644
index 000000000..193364ff8
--- /dev/null
+++ b/src/gui/windows/buyselldialog.h
@@ -0,0 +1,83 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_BUYSELLDIALOG_H
+#define GUI_BUYSELLDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Button;
+
+/**
+ * A dialog to choose between buying or selling at a shop.
+ *
+ * \ingroup Interface
+ */
+class BuySellDialog final : 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
+ */
+ explicit BuySellDialog(const int npcId);
+
+ explicit BuySellDialog(const std::string &nick);
+
+ A_DELETE_COPY(BuySellDialog)
+
+ ~BuySellDialog();
+
+ void init();
+
+ void setVisible(bool visible);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive() A_WARN_UNUSED
+ { return !dialogInstances.empty(); }
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+ private:
+ typedef std::list<BuySellDialog*> DialogList;
+ static DialogList dialogInstances;
+
+ int mNpcId;
+ std::string mNick;
+ Button *mBuyButton;
+};
+
+#endif // GUI_BUYSELLDIALOG_H
diff --git a/src/gui/windows/changeemaildialog.cpp b/src/gui/windows/changeemaildialog.cpp
new file mode 100644
index 000000000..61ff7ef3d
--- /dev/null
+++ b/src/gui/windows/changeemaildialog.cpp
@@ -0,0 +1,181 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/changeemaildialog.h"
+
+#include "client.h"
+
+#include "gui/windows/registerdialog.h"
+#include "gui/windows/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 <string>
+#include <sstream>
+
+#include "debug.h"
+
+ChangeEmailDialog::ChangeEmailDialog(LoginData *const data):
+ // TRANSLATORS: change email dialog header
+ Window(_("Change Email Address"), true, nullptr, "changeemail.xml"),
+ gcn::ActionListener(),
+ mFirstEmailField(new TextField(this)),
+ mSecondEmailField(new TextField(this)),
+ // TRANSLATORS: button in change email dialog
+ mChangeEmailButton(new Button(this, _("Change Email Address"),
+ "change_email", this)),
+ // TRANSLATORS: button in change email dialog
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ mWrongDataNoticeListener(new WrongDataNoticeListener),
+ mLoginData(data)
+{
+ // TRANSLATORS: label in change email dialog
+ Label *const accountLabel = new Label(this, strprintf(_("Account: %s"),
+ mLoginData->username.c_str()));
+ Label *const newEmailLabel = new Label(this,
+ // TRANSLATORS: label in change email dialog
+ _("Type new email address twice:"));
+
+ 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 = nullptr;
+}
+
+void ChangeEmailDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "cancel")
+ {
+ client->setState(STATE_CHAR_SELECT);
+ }
+ else if (eventId == "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 errorMsg;
+ int error = 0;
+
+ const unsigned int min = Net::getLoginHandler()
+ ->getMinPasswordLength();
+ const unsigned int max = Net::getLoginHandler()
+ ->getMaxPasswordLength();
+
+ if (newFirstEmail.length() < min)
+ {
+ // First email address too short
+ // TRANSLATORS: change email error
+ errorMsg << strprintf(_("The new email address needs to be at "
+ "least %u characters long."), min);
+ error = 1;
+ }
+ else if (newFirstEmail.length() > max)
+ {
+ // First email address too long
+ // TRANSLATORS: change email error
+ errorMsg << strprintf(_("The new email address needs to be "
+ "less than %u characters long."), max);
+ error = 1;
+ }
+ else if (newFirstEmail != newSecondEmail)
+ {
+ // Second Pass mismatch
+ // TRANSLATORS: change email error
+ errorMsg << _("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);
+
+ // TRANSLATORS: change email error header
+ OkDialog *const dlg = new OkDialog(_("Error"),
+ errorMsg.str(), DIALOG_ERROR);
+ 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/windows/changeemaildialog.h b/src/gui/windows/changeemaildialog.h
new file mode 100644
index 000000000..e61ac8bc5
--- /dev/null
+++ b/src/gui/windows/changeemaildialog.h
@@ -0,0 +1,80 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_CHANGEEMAILDIALOG_H
+#define GUI_CHANGEEMAILDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Button;
+class LoginData;
+class TextField;
+class WrongDataNoticeListener;
+
+/**
+ * The Change email dialog.
+ *
+ * \ingroup Interface
+ */
+class ChangeEmailDialog final : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ explicit ChangeEmailDialog(LoginData *const data);
+
+ A_DELETE_COPY(ChangeEmailDialog)
+
+ /**
+ * Destructor.
+ */
+ ~ChangeEmailDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * 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:
+ TextField *mFirstEmailField;
+ TextField *mSecondEmailField;
+
+ Button *mChangeEmailButton;
+ Button *mCancelButton;
+
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+
+ LoginData *mLoginData;
+};
+
+#endif // GUI_CHANGEEMAILDIALOG_H
diff --git a/src/gui/windows/changepassworddialog.cpp b/src/gui/windows/changepassworddialog.cpp
new file mode 100644
index 000000000..365ee19a8
--- /dev/null
+++ b/src/gui/windows/changepassworddialog.cpp
@@ -0,0 +1,172 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/changepassworddialog.h"
+
+#include "client.h"
+
+#include "gui/windows/registerdialog.h"
+#include "gui/windows/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/passwordfield.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 <string>
+#include <sstream>
+
+#include "debug.h"
+
+ChangePasswordDialog::ChangePasswordDialog(LoginData *const data):
+ // TRANSLATORS: change password window name
+ Window(_("Change Password"), true, nullptr, "changepassword.xml"),
+ gcn::ActionListener(),
+ mOldPassField(new PasswordField(this)),
+ mFirstPassField(new PasswordField(this)),
+ mSecondPassField(new PasswordField(this)),
+ // TRANSLATORS: change password dialog button
+ mChangePassButton(new Button(this, _("Change Password"),
+ "change_password", this)),
+ // TRANSLATORS: change password dialog button
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ mWrongDataNoticeListener(new WrongDataNoticeListener),
+ mLoginData(data)
+{
+ Label *const accountLabel = new Label(this,
+ // TRANSLATORS: change password dialog label
+ strprintf(_("Account: %s"), mLoginData->username.c_str()));
+
+ place(0, 0, accountLabel, 3);
+ // TRANSLATORS: change password dialog label
+ place(0, 1, new Label(this, _("Password:")), 3);
+ place(0, 2, mOldPassField, 3).setPadding(1);
+ // TRANSLATORS: change password dialog label
+ place(0, 3, new Label(this, _("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 = nullptr;
+}
+
+void ChangePasswordDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "cancel")
+ {
+ client->setState(STATE_CHAR_SELECT);
+ }
+ else if (eventId == "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 errorMsg;
+ int error = 0;
+
+ const unsigned int min = Net::getLoginHandler()
+ ->getMinPasswordLength();
+ const unsigned int max = Net::getLoginHandler()
+ ->getMaxPasswordLength();
+
+ // Check old Password
+ if (oldPassword.empty())
+ {
+ // No old password
+ // TRANSLATORS: change password error
+ errorMsg << _("Enter the old password first.");
+ error = 1;
+ }
+ else if (newFirstPass.length() < min)
+ {
+ // First password too short
+ // TRANSLATORS: change password error
+ errorMsg << strprintf(_("The new password needs to be at least"
+ " %u characters long."), min);
+ error = 2;
+ }
+ else if (newFirstPass.length() > max)
+ {
+ // First password too long
+ // TRANSLATORS: change password error
+ errorMsg << strprintf(_("The new password needs to be less "
+ "than %u characters long."), max);
+ error = 2;
+ }
+ else if (newFirstPass != newSecondPass)
+ {
+ // Second Pass mismatch
+ // TRANSLATORS: change password error
+ errorMsg << _("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);
+
+ // TRANSLATORS: change password error header
+ OkDialog *const dlg = new OkDialog(_("Error"),
+ errorMsg.str(), DIALOG_ERROR);
+ 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/windows/changepassworddialog.h b/src/gui/windows/changepassworddialog.h
new file mode 100644
index 000000000..fd70348a6
--- /dev/null
+++ b/src/gui/windows/changepassworddialog.h
@@ -0,0 +1,75 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_CHANGEPASSWORDDIALOG_H
+#define GUI_CHANGEPASSWORDDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Button;
+class LoginData;
+class TextField;
+class WrongDataNoticeListener;
+
+/**
+ * The Change password dialog.
+ *
+ * \ingroup Interface
+ */
+class ChangePasswordDialog final : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ explicit ChangePasswordDialog(LoginData *const data);
+
+ A_DELETE_COPY(ChangePasswordDialog)
+
+ /**
+ * Destructor
+ */
+ ~ChangePasswordDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ private:
+ TextField *mOldPassField;
+ TextField *mFirstPassField;
+ TextField *mSecondPassField;
+
+ Button *mChangePassButton;
+ Button *mCancelButton;
+
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+
+ LoginData *mLoginData;
+};
+
+#endif // GUI_CHANGEPASSWORDDIALOG_H
diff --git a/src/gui/windows/charcreatedialog.cpp b/src/gui/windows/charcreatedialog.cpp
new file mode 100644
index 000000000..53f1a5bce
--- /dev/null
+++ b/src/gui/windows/charcreatedialog.cpp
@@ -0,0 +1,698 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/charcreatedialog.h"
+
+#include "main.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/windows/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/playerbox.h"
+#include "gui/widgets/radiobutton.h"
+#include "gui/widgets/slider.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/net.h"
+
+#include "resources/chardb.h"
+#include "resources/colordb.h"
+#include "resources/itemdb.h"
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+extern int serverVersion;
+
+static const Being::Action actions[] =
+{
+ Being::STAND, Being::SIT, Being::MOVE, Being::ATTACK, Being::DEAD
+};
+
+static const uint8_t directions[] =
+{
+ Being::DOWN, Being::RIGHT, Being::UP, Being::LEFT
+};
+
+CharCreateDialog::CharCreateDialog(CharSelectDialog *const parent,
+ const int slot) :
+ // TRANSLATORS: char create dialog name
+ Window(_("New Character"), true, parent, "charcreate.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mCharSelectDialog(parent),
+ mNameField(new TextField(this, "")),
+ // TRANSLATORS: char create dialog label
+ mNameLabel(new Label(this, _("Name:"))),
+ // TRANSLATORS: This is a narrow symbol used to denote 'next'.
+ // You may change this symbol if your language uses another.
+ // TRANSLATORS: char create dialog button
+ mNextHairColorButton(new Button(this, _(">"), "nextcolor", this)),
+ // TRANSLATORS: This is a narrow symbol used to denote 'previous'.
+ // You may change this symbol if your language uses another.
+ // TRANSLATORS: char create dialog button
+ mPrevHairColorButton(new Button(this, _("<"), "prevcolor", this)),
+ // TRANSLATORS: char create dialog label
+ mHairColorLabel(new Label(this, _("Hair color:"))),
+ mHairColorNameLabel(new Label(this, "")),
+ // TRANSLATORS: char create dialog button
+ mNextHairStyleButton(new Button(this, _(">"), "nextstyle", this)),
+ // TRANSLATORS: char create dialog button
+ mPrevHairStyleButton(new Button(this, _("<"), "prevstyle", this)),
+ // TRANSLATORS: char create dialog label
+ mHairStyleLabel(new Label(this, _("Hair style:"))),
+ mHairStyleNameLabel(new Label(this, "")),
+ mNextRaceButton(nullptr),
+ mPrevRaceButton(nullptr),
+ mRaceLabel(nullptr),
+ mRaceNameLabel(nullptr),
+ mNextLookButton(nullptr),
+ mPrevLookButton(nullptr),
+ mLookLabel(nullptr),
+ mLookNameLabel(nullptr),
+ // TRANSLATORS: char create dialog button
+ mActionButton(new Button(this, _("^"), "action", this)),
+ // TRANSLATORS: char create dialog button
+ mRotateButton(new Button(this, _(">"), "rotate", this)),
+ // TRANSLATORS: char create dialog button
+ mMale(new RadioButton(this, _("Male"), "gender")),
+ // TRANSLATORS: char create dialog button
+ mFemale(new RadioButton(this, _("Female"), "gender")),
+ // TRANSLATORS: char create dialog button
+ mOther(new RadioButton(this, _("Other"), "gender")),
+ mAttributeSlider(),
+ mAttributeLabel(),
+ mAttributeValue(),
+ mAttributesLeft(new Label(this,
+ // TRANSLATORS: char create dialog label
+ strprintf(_("Please distribute %d points"), 99))),
+ mMaxPoints(0),
+ mUsedPoints(0),
+ // TRANSLATORS: char create dialog button
+ mCreateButton(new Button(this, _("Create"), "create", this)),
+ // TRANSLATORS: char create dialog button
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ mRace(0),
+ mLook(0),
+ mMinLook(CharDB::getMinLook()),
+ mMaxLook(CharDB::getMaxLook()),
+ mPlayer(new Being(0, ActorSprite::PLAYER, static_cast<uint16_t>(mRace),
+ nullptr)),
+ mPlayerBox(new PlayerBox(mPlayer, "charcreate_playerbox.xml",
+ "charcreate_selectedplayerbox.xml")),
+ mHairStyle(0),
+ mHairColor(0),
+ mSlot(slot),
+ maxHairColor(CharDB::getMaxHairColor()),
+ minHairColor(CharDB::getMinHairColor()),
+ maxHairStyle(CharDB::getMaxHairStyle()),
+ minHairStyle(CharDB::getMinHairStyle()),
+ mAction(0),
+ mDirection(0)
+{
+ setStickyButtonLock(true);
+ setSticky(true);
+ setWindowName("NewCharacter");
+
+ mPlayer->setGender(GENDER_MALE);
+ const std::vector<int> &items = CharDB::getDefaultItems();
+ int i = 1;
+ for (std::vector<int>::const_iterator it = items.begin(),
+ it_end = items.end();
+ it != it_end; ++ it, i ++)
+ {
+ mPlayer->setSprite(i, *it);
+ }
+
+ if (!maxHairColor)
+ maxHairColor = ColorDB::getHairSize();
+ if (!maxHairStyle)
+ maxHairStyle = mPlayer->getNumOfHairstyles();
+
+ if (maxHairStyle)
+ mHairStyle = (rand() % maxHairStyle) + minHairStyle;
+ else
+ mHairStyle = 0;
+ if (maxHairColor)
+ mHairColor = (rand() % maxHairColor) + minHairColor;
+ else
+ mHairColor = 0;
+
+ mNameField->setMaximum(24);
+
+ if (serverVersion >= 2)
+ {
+ // TRANSLATORS: char create dialog button
+ mNextRaceButton = new Button(this, _(">"), "nextrace", this);
+ // TRANSLATORS: char create dialog button
+ mPrevRaceButton = new Button(this, _("<"), "prevrace", this);
+ // TRANSLATORS: char create dialog label
+ mRaceLabel = new Label(this, _("Race:"));
+ mRaceNameLabel = new Label(this, "");
+ }
+ if (serverVersion >= 9 && mMinLook < mMaxLook)
+ {
+ // TRANSLATORS: char create dialog button
+ mNextLookButton = new Button(this, _(">"), "nextlook", this);
+ // TRANSLATORS: char create dialog button
+ mPrevLookButton = new Button(this, _("<"), "prevlook", this);
+ // TRANSLATORS: char create dialog label
+ mLookLabel = new Label(this, _("Look:"));
+ mLookNameLabel = new Label(this, "");
+ }
+
+ // Default to a Male character
+ mMale->setSelected(true);
+
+ mMale->setActionEventId("gender");
+ mFemale->setActionEventId("gender");
+ mOther->setActionEventId("gender");
+
+ mMale->addActionListener(this);
+ mFemale->addActionListener(this);
+ mOther->addActionListener(this);
+
+ mPlayerBox->setWidth(74);
+
+ mNameField->setActionEventId("create");
+ mNameField->addActionListener(this);
+
+ const int w = 480;
+ const int h = 350;
+
+ setContentSize(w, h);
+ mPlayerBox->setDimension(gcn::Rectangle(360, 0, 110, 90));
+ mActionButton->setPosition(385, 100);
+ mRotateButton->setPosition(415, 100);
+
+ mNameLabel->setPosition(5, 2);
+ mNameField->setDimension(
+ gcn::Rectangle(60, 2, 300, mNameField->getHeight()));
+
+ const int leftX = 120;
+ const int rightX = 300;
+ const int labelX = 5;
+ const int nameX = 145;
+ int y = 30;
+ mPrevHairColorButton->setPosition(leftX, y);
+ mNextHairColorButton->setPosition(rightX, y);
+ y += 5;
+ mHairColorLabel->setPosition(labelX, y);
+ mHairColorNameLabel->setPosition(nameX, y);
+ y += 24;
+ mPrevHairStyleButton->setPosition(leftX, y);
+ mNextHairStyleButton->setPosition(rightX, y);
+ y += 5;
+ mHairStyleLabel->setPosition(labelX, y);
+ mHairStyleNameLabel->setPosition(nameX, y);
+
+ if (serverVersion >= 9 && mMinLook < mMaxLook)
+ {
+ y += 24;
+ mPrevLookButton->setPosition(leftX, y);
+ mNextLookButton->setPosition(rightX, y);
+ y += 5;
+ mLookLabel->setPosition(labelX, y);
+ mLookNameLabel->setPosition(nameX, y); // 93
+ }
+ if (serverVersion >= 2)
+ {
+ y += 24;
+ mPrevRaceButton->setPosition(leftX, y);
+ mNextRaceButton->setPosition(rightX, y);
+ y += 5;
+ mRaceLabel->setPosition(labelX, y);
+ mRaceNameLabel->setPosition(nameX, y);
+ }
+
+ updateSliders();
+ setButtonsPosition(w, h);
+
+ mMale->setPosition(30, 120);
+ mFemale->setPosition(100, 120);
+ mOther->setPosition(170, 120);
+
+ add(mPlayerBox);
+ add(mNameField);
+ add(mNameLabel);
+ add(mNextHairColorButton);
+ add(mPrevHairColorButton);
+ add(mHairColorLabel);
+ add(mHairColorNameLabel);
+ add(mNextHairStyleButton);
+ add(mPrevHairStyleButton);
+ add(mHairStyleLabel);
+ add(mHairStyleNameLabel);
+ add(mActionButton);
+ add(mRotateButton);
+
+ if (serverVersion >= 9 && mMinLook < mMaxLook)
+ {
+ add(mNextLookButton);
+ add(mPrevLookButton);
+ add(mLookLabel);
+ add(mLookNameLabel);
+ }
+
+ if (serverVersion >= 2)
+ {
+ add(mNextRaceButton);
+ add(mPrevRaceButton);
+ add(mRaceLabel);
+ add(mRaceNameLabel);
+ }
+
+ add(mAttributesLeft);
+ add(mCreateButton);
+ add(mCancelButton);
+
+ add(mMale);
+ add(mFemale);
+ add(mOther);
+
+ center();
+ setVisible(true);
+ mNameField->requestFocus();
+
+ updateHair();
+ if (serverVersion >= 2)
+ updateRace();
+ if (serverVersion >= 9 && mMinLook < mMaxLook)
+ updateLook();
+ updatePlayer();
+
+ addKeyListener(this);
+}
+
+CharCreateDialog::~CharCreateDialog()
+{
+ delete mPlayer;
+ mPlayer = nullptr;
+
+ if (Net::getCharServerHandler())
+ Net::getCharServerHandler()->setCharCreateDialog(nullptr);
+}
+
+void CharCreateDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string id = event.getId();
+ if (id == "create")
+ {
+ if (
+#ifdef MANASERV_SUPPORT
+ Net::getNetworkType() == ServerInfo::MANASERV ||
+#endif
+ getName().length() >= 4)
+ {
+ // Attempt to create the character
+ mCreateButton->setEnabled(false);
+
+ std::vector<int> atts;
+ for (size_t i = 0, sz = mAttributeSlider.size(); i < sz; i++)
+ {
+ atts.push_back(static_cast<int>(
+ mAttributeSlider[i]->getValue()));
+ }
+
+#ifdef MANASERV_SUPPORT
+ int characterSlot = mSlot;
+ // On Manaserv, the slots start at 1, so we offset them.
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ ++characterSlot;
+#else
+ const int characterSlot = mSlot;
+#endif
+
+ Net::getCharServerHandler()->newCharacter(getName(), characterSlot,
+ mFemale->isSelected(), mHairStyle, mHairColor,
+ static_cast<unsigned char>(mRace), mLook, atts);
+ }
+ else
+ {
+ // TRANSLATORS: char creation error
+ new OkDialog(_("Error"),
+ // TRANSLATORS: char creation error
+ _("Your name needs to be at least 4 characters."),
+ DIALOG_ERROR, true, this);
+ }
+ }
+ else if (id == "cancel")
+ {
+ scheduleDelete();
+ }
+ else if (id == "nextcolor")
+ {
+ mHairColor ++;
+ updateHair();
+ }
+ else if (id == "prevcolor")
+ {
+ mHairColor --;
+ updateHair();
+ }
+ else if (id == "nextstyle")
+ {
+ mHairStyle ++;
+ updateHair();
+ }
+ else if (id == "prevstyle")
+ {
+ mHairStyle --;
+ updateHair();
+ }
+ else if (id == "nextrace")
+ {
+ mRace ++;
+ updateRace();
+ }
+ else if (id == "prevrace")
+ {
+ mRace --;
+ updateRace();
+ }
+ else if (id == "nextlook")
+ {
+ mLook ++;
+ updateLook();
+ }
+ else if (id == "prevlook")
+ {
+ mLook --;
+ updateLook();
+ }
+ else if (id == "statslider")
+ {
+ updateSliders();
+ }
+ else if (id == "gender")
+ {
+ if (mMale->isSelected())
+ mPlayer->setGender(GENDER_MALE);
+ else
+ mPlayer->setGender(GENDER_FEMALE);
+ }
+ else if (id == "action")
+ {
+ mAction ++;
+ if (mAction >= 5)
+ mAction = 0;
+ updatePlayer();
+ }
+ else if (id == "rotate")
+ {
+ mDirection ++;
+ if (mDirection >= 4)
+ mDirection = 0;
+ updatePlayer();
+ }
+}
+
+std::string CharCreateDialog::getName() const
+{
+ std::string name = mNameField->getText();
+ trim(name);
+ return name;
+}
+
+void CharCreateDialog::updateSliders()
+{
+ for (size_t i = 0, sz = mAttributeSlider.size(); i < sz; i++)
+ {
+ // Update captions
+ mAttributeValue[i]->setCaption(
+ toString(static_cast<int>(mAttributeSlider[i]->getValue())));
+ mAttributeValue[i]->adjustSize();
+ }
+
+ // Update distributed points
+ const int pointsLeft = mMaxPoints - getDistributedPoints();
+ if (pointsLeft == 0)
+ {
+ // TRANSLATORS: char create dialog label
+ mAttributesLeft->setCaption(_("Character stats OK"));
+ mCreateButton->setEnabled(true);
+ }
+ else
+ {
+ mCreateButton->setEnabled(false);
+ if (pointsLeft > 0)
+ {
+ mAttributesLeft->setCaption(
+ // TRANSLATORS: char create dialog label
+ strprintf(_("Please distribute %d points"), pointsLeft));
+ }
+ else
+ {
+ mAttributesLeft->setCaption(
+ // TRANSLATORS: char create dialog label
+ strprintf(_("Please remove %d points"), -pointsLeft));
+ }
+ }
+
+ mAttributesLeft->adjustSize();
+}
+
+void CharCreateDialog::unlock()
+{
+ mCreateButton->setEnabled(true);
+}
+
+int CharCreateDialog::getDistributedPoints() const
+{
+ int points = 0;
+
+ for (size_t i = 0, sz = mAttributeSlider.size(); i < sz; i++)
+ points += static_cast<int>(mAttributeSlider[i]->getValue());
+ return points;
+}
+
+void CharCreateDialog::setAttributes(const StringVect &labels,
+ const int available,
+ const int min, const int max)
+{
+ mMaxPoints = available;
+
+ for (unsigned i = 0; i < mAttributeLabel.size(); i++)
+ {
+ remove(mAttributeLabel[i]);
+ delete mAttributeLabel[i];
+ mAttributeLabel[i] = nullptr;
+ remove(mAttributeSlider[i]);
+ delete mAttributeSlider[i];
+ mAttributeSlider[i] = nullptr;
+ remove(mAttributeValue[i]);
+ delete mAttributeValue[i];
+ mAttributeValue[i] = nullptr;
+ }
+
+ mAttributeLabel.resize(labels.size());
+ mAttributeSlider.resize(labels.size());
+ mAttributeValue.resize(labels.size());
+
+ const int w = 480;
+ const int h = 350;
+ const int y = 118 + 29;
+
+ for (unsigned i = 0, sz = static_cast<unsigned>(labels.size());
+ i < sz; i++)
+ {
+ mAttributeLabel[i] = new Label(this, labels[i]);
+ mAttributeLabel[i]->setWidth(70);
+ mAttributeLabel[i]->setPosition(5, y + i * 24);
+ mAttributeLabel[i]->adjustSize();
+ add(mAttributeLabel[i]);
+
+ mAttributeSlider[i] = new Slider(min, max);
+ mAttributeSlider[i]->setDimension(gcn::Rectangle(140, y + i * 24,
+ 150, 12));
+ mAttributeSlider[i]->setActionEventId("statslider");
+ mAttributeSlider[i]->addActionListener(this);
+ add(mAttributeSlider[i]);
+
+ mAttributeValue[i] = new Label(this, toString(min));
+ mAttributeValue[i]->setPosition(295, y + i * 24);
+ add(mAttributeValue[i]);
+ }
+
+ updateSliders();
+ setButtonsPosition(w, h);
+}
+
+void CharCreateDialog::setFixedGender(const bool fixed, const Gender gender)
+{
+ if (gender == GENDER_FEMALE)
+ {
+ mFemale->setSelected(true);
+ mMale->setSelected(false);
+ mOther->setSelected(false);
+ }
+ else if (gender == GENDER_MALE)
+ {
+ mFemale->setSelected(false);
+ mMale->setSelected(true);
+ mOther->setSelected(false);
+ }
+ else
+ {
+ mFemale->setSelected(false);
+ mMale->setSelected(false);
+ mOther->setSelected(true);
+ }
+
+ mPlayer->setGender(gender);
+
+ if (fixed)
+ {
+ mMale->setVisible(false);
+ mFemale->setVisible(false);
+ mOther->setVisible(false);
+ }
+}
+
+void CharCreateDialog::updateHair()
+{
+ if (mHairStyle <= 0)
+ mHairStyle = Being::getNumOfHairstyles() - 1;
+ else
+ mHairStyle %= Being::getNumOfHairstyles();
+ if (mHairStyle < static_cast<signed>(minHairStyle)
+ || mHairStyle > static_cast<signed>(maxHairStyle))
+ {
+ mHairStyle = minHairStyle;
+ }
+ const ItemInfo &item = ItemDB::get(-mHairStyle);
+ mHairStyleNameLabel->setCaption(item.getName());
+ mHairStyleNameLabel->adjustSize();
+
+ if (ColorDB::getHairSize())
+ mHairColor %= ColorDB::getHairSize();
+ else
+ mHairColor = 0;
+ if (mHairColor < 0)
+ mHairColor += ColorDB::getHairSize();
+ if (mHairColor < static_cast<signed>(minHairColor)
+ || mHairColor > static_cast<signed>(maxHairColor))
+ {
+ mHairColor = minHairColor;
+ }
+ mHairColorNameLabel->setCaption(ColorDB::getHairColorName(mHairColor));
+ mHairColorNameLabel->adjustSize();
+
+ mPlayer->setSprite(Net::getCharServerHandler()->hairSprite(),
+ mHairStyle * -1, item.getDyeColorsString(mHairColor));
+}
+
+void CharCreateDialog::updateRace()
+{
+ if (mRace < 0)
+ mRace = Being::getNumOfRaces() - 1;
+ else if (mRace >= Being::getNumOfRaces())
+ mRace = 0;
+
+ updateLook();
+}
+
+void CharCreateDialog::updateLook()
+{
+ const ItemInfo &item = ItemDB::get(-100 - mRace);
+ const int sz = item.getColorsSize();
+ if (sz > 0 && serverVersion >= 9)
+ {
+ if (mLook < 0)
+ mLook = sz - 1;
+ if (mLook > mMaxLook)
+ mLook = mMinLook;
+ if (mLook >= sz)
+ mLook = mMinLook;
+ }
+ else
+ {
+ mLook = 0;
+ }
+ mPlayer->setSubtype(static_cast<uint16_t>(mRace), mLook);
+ if (mRaceNameLabel)
+ {
+ mRaceNameLabel->setCaption(item.getName());
+ mRaceNameLabel->adjustSize();
+ }
+ if (mLookNameLabel)
+ {
+ mLookNameLabel->setCaption(item.getColorName(mLook));
+ mLookNameLabel->adjustSize();
+ }
+}
+
+void CharCreateDialog::logic()
+{
+ if (mPlayer)
+ mPlayer->logic();
+}
+
+void CharCreateDialog::updatePlayer()
+{
+ if (mPlayer)
+ {
+ mPlayer->setDirection(directions[mDirection]);
+ mPlayer->setAction(actions[mAction]);
+ }
+}
+
+void CharCreateDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ const int actionId = static_cast<KeyEvent*>(&keyEvent)->getActionId();
+ switch (actionId)
+ {
+ case Input::KEY_GUI_CANCEL:
+ keyEvent.consume();
+ action(gcn::ActionEvent(mCancelButton,
+ mCancelButton->getActionEventId()));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void CharCreateDialog::setButtonsPosition(const int w, const int h)
+{
+ if (mainGraphics->getHeight() < 480)
+ {
+ mCreateButton->setPosition(340, 150);
+ mCancelButton->setPosition(340, 160 + mCreateButton->getHeight());
+ }
+ else
+ {
+ mCancelButton->setPosition(
+ w / 2,
+ h - 5 - mCancelButton->getHeight());
+ mCreateButton->setPosition(
+ mCancelButton->getX() - 5 - mCreateButton->getWidth(),
+ h - 5 - mCancelButton->getHeight());
+ }
+ mAttributesLeft->setPosition(15, 260 + 29);
+}
diff --git a/src/gui/windows/charcreatedialog.h b/src/gui/windows/charcreatedialog.h
new file mode 100644
index 000000000..7bb96284e
--- /dev/null
+++ b/src/gui/windows/charcreatedialog.h
@@ -0,0 +1,165 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_CHARCREATEDIALOG_H
+#define GUI_CHARCREATEDIALOG_H
+
+#include "being/being.h"
+
+#include "gui/windows/charselectdialog.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+class Label;
+class PlayerBox;
+class RadioButton;
+class Slider;
+class TextField;
+
+/**
+ * Character creation dialog.
+ *
+ * \ingroup Interface
+ */
+class CharCreateDialog final : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ CharCreateDialog(CharSelectDialog *const parent, const int slot);
+
+ A_DELETE_COPY(CharCreateDialog)
+
+ /**
+ * Destructor.
+ */
+ ~CharCreateDialog();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Unlocks the dialog, enabling the create character button again.
+ */
+ void unlock();
+
+ void setAttributes(const StringVect &labels,
+ const int available,
+ const int min, const int max);
+
+ void setFixedGender(const bool fixed,
+ const Gender gender = GENDER_FEMALE);
+
+ void logic() override;
+
+ void updatePlayer();
+
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ private:
+ int getDistributedPoints() const A_WARN_UNUSED;
+
+ void updateSliders();
+
+ void setButtonsPosition(const int w, const int h);
+
+ /**
+ * Returns the name of the character to create.
+ */
+ std::string getName() const A_WARN_UNUSED;
+
+ /**
+ * Communicate character creation to the server.
+ */
+ void attemptCharCreate();
+
+ void updateHair();
+
+ void updateRace();
+
+ void updateLook();
+
+ CharSelectDialog *mCharSelectDialog;
+
+ TextField *mNameField;
+ Label *mNameLabel;
+ Button *mNextHairColorButton;
+ Button *mPrevHairColorButton;
+ Label *mHairColorLabel;
+ Label *mHairColorNameLabel;
+ Button *mNextHairStyleButton;
+ Button *mPrevHairStyleButton;
+ Label *mHairStyleLabel;
+ Label *mHairStyleNameLabel;
+ Button *mNextRaceButton;
+ Button *mPrevRaceButton;
+ Label *mRaceLabel;
+ Label *mRaceNameLabel;
+ Button *mNextLookButton;
+ Button *mPrevLookButton;
+ Label *mLookLabel;
+ Label *mLookNameLabel;
+
+ Button *mActionButton;
+ Button *mRotateButton;
+
+ RadioButton *mMale;
+ RadioButton *mFemale;
+ RadioButton *mOther;
+
+ std::vector<Slider*> mAttributeSlider;
+ std::vector<Label*> mAttributeLabel;
+ std::vector<Label*> mAttributeValue;
+ Label *mAttributesLeft;
+
+ int mMaxPoints;
+ int mUsedPoints;
+
+ Button *mCreateButton;
+ Button *mCancelButton;
+
+ int mRace;
+ int mLook;
+ int mMinLook;
+ int mMaxLook;
+
+ Being *mPlayer;
+ PlayerBox *mPlayerBox;
+
+ int mHairStyle;
+ int mHairColor;
+
+ int mSlot;
+
+ unsigned maxHairColor;
+ unsigned minHairColor;
+ unsigned maxHairStyle;
+ unsigned minHairStyle;
+
+ unsigned mAction;
+ unsigned mDirection;
+};
+
+#endif // GUI_CHARCREATEDIALOG_H
diff --git a/src/gui/windows/charselectdialog.cpp b/src/gui/windows/charselectdialog.cpp
new file mode 100644
index 000000000..f34f53d28
--- /dev/null
+++ b/src/gui/windows/charselectdialog.cpp
@@ -0,0 +1,587 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/charselectdialog.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "units.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/windows/charcreatedialog.h"
+#include "gui/windows/confirmdialog.h"
+#include "gui/windows/logindialog.h"
+#include "gui/windows/okdialog.h"
+#include "gui/windows/textdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/characterdisplay.h"
+#include "gui/widgets/characterviewnormal.h"
+#include "gui/widgets/characterviewsmall.h"
+#include "gui/widgets/layout.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+// Character slots per row in the dialog
+static const int SLOTS_PER_ROW = 5;
+
+/**
+ * Listener for confirming character deletion.
+ */
+class CharDeleteConfirm final : public ConfirmDialog
+{
+ public:
+ CharDeleteConfirm(CharSelectDialog *const m, const int index) :
+ // TRANSLATORS: char deletion message
+ ConfirmDialog(_("Confirm Character Delete"),
+ // TRANSLATORS: char deletion message
+ _("Are you sure you want to delete this character?"),
+ SOUND_REQUEST, false, false, m),
+ mMaster(m),
+ mIndex(index)
+ {
+ }
+
+ A_DELETE_COPY(CharDeleteConfirm)
+
+ void action(const gcn::ActionEvent &event)
+ {
+ if (event.getId() == "yes" && mMaster)
+ mMaster->askPasswordForDeletion(mIndex);
+
+ ConfirmDialog::action(event);
+ }
+
+ private:
+ CharSelectDialog *mMaster;
+ int mIndex;
+};
+
+CharSelectDialog::CharSelectDialog(LoginData *const data):
+ // TRANSLATORS: char select dialog name
+ Window(strprintf(_("Account %s (last login time %s)"),
+ data->username.c_str(), data->lastLogin.c_str()),
+ false, nullptr, "char.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mLoginData(data),
+ // TRANSLATORS: char select dialog. button.
+ mSwitchLoginButton(new Button(this, _("Switch Login"), "switch", this)),
+ // TRANSLATORS: char select dialog. button.
+ mChangePasswordButton(new Button(this, _("Change Password"),
+ "change_password", this)),
+ mUnregisterButton(nullptr),
+ mChangeEmailButton(nullptr),
+ // TRANSLATORS: char select dialog. button.
+ mPlayButton(new Button(this, _("Play"), "use", this)),
+ // TRANSLATORS: char select dialog. button.
+ mInfoButton(new Button(this, _("Info"), "info", this)),
+ // TRANSLATORS: char select dialog. button.
+ mDeleteButton(new Button(this, _("Delete"), "delete", this)),
+ mCharacterView(nullptr),
+ mCharacterEntries(0),
+ mCharServerHandler(Net::getCharServerHandler()),
+ mDeleteDialog(nullptr),
+ mDeleteIndex(-1),
+ mLocked(false),
+ mSmallScreen(mainGraphics->getWidth() < 470
+ || mainGraphics->getHeight() < 370)
+{
+ setCloseButton(true);
+ setFocusable(true);
+
+ const int optionalActions = Net::getLoginHandler()
+ ->supportedOptionalActions();
+
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+
+ placer(0, 0, mSwitchLoginButton);
+
+ int n = 1;
+ if (optionalActions & Net::LoginHandler::Unregister)
+ {
+ // TRANSLATORS: char select dialog. button.
+ mUnregisterButton = new Button(this, _("Unregister"),
+ "unregister", this);
+ placer(n, 0, mUnregisterButton);
+ n ++;
+ }
+
+ placer(n, 0, mChangePasswordButton);
+ n ++;
+
+ if (optionalActions & Net::LoginHandler::ChangeEmail)
+ {
+ // TRANSLATORS: char select dialog. button.
+ mChangeEmailButton = new Button(this, _("Change Email"),
+ "change_email", this);
+ placer(n, 0, mChangeEmailButton);
+ n ++;
+ }
+
+ placer(n, 0, mDeleteButton);
+ n ++;
+ placer(n, 0, mInfoButton);
+ n ++;
+
+ for (int i = 0; i < static_cast<int>(mLoginData->characterSlots); i++)
+ {
+ CharacterDisplay *const character = new CharacterDisplay(this, this);
+ character->setVisible(false);
+ mCharacterEntries.push_back(character);
+ }
+
+ placer(0, 2, mPlayButton);
+
+ if (!mSmallScreen)
+ {
+ mCharacterView = new CharacterViewNormal(
+ this, &mCharacterEntries, mPadding);
+ placer(0, 1, mCharacterView, 10);
+ int sz = 410 + 2 * mPadding;
+ if (config.getIntValue("fontSize") > 18)
+ sz = 500 + 2 * mPadding;
+ const int width = mCharacterView->getWidth() + 2 * mPadding;
+ if (sz < width)
+ sz = width;
+ if (sz > mainGraphics->getWidth())
+ sz = mainGraphics->getWidth();
+ reflowLayout(sz);
+ }
+ else
+ {
+ // TRANSLATORS: char select dialog name
+ setCaption(strprintf(_("Account %s"), mLoginData->username.c_str()));
+ mCharacterView = new CharacterViewSmall(
+ this, &mCharacterEntries, mPadding);
+ mCharacterView->setWidth(mainGraphics->getWidth()
+ - 2 * getPadding());
+ placer(0, 1, mCharacterView, 10);
+ reflowLayout();
+ }
+ addKeyListener(this);
+ center();
+ setVisible(true);
+ requestFocus();
+
+ Net::getCharServerHandler()->setCharSelectDialog(this);
+ mCharacterView->show(0);
+ updateState();
+}
+
+CharSelectDialog::~CharSelectDialog()
+{
+}
+
+void CharSelectDialog::action(const gcn::ActionEvent &event)
+{
+ // Check if a button of a character was pressed
+ const gcn::Widget *const sourceParent = event.getSource()->getParent();
+ int selected = -1;
+ for (unsigned int i = 0, sz = static_cast<unsigned int>(
+ mCharacterEntries.size()); i < sz; ++i)
+ {
+ if (mCharacterEntries[i] == sourceParent)
+ {
+ selected = i;
+ mCharacterView->show(i);
+ updateState();
+ break;
+ }
+ }
+ if (selected == -1)
+ selected = mCharacterView->getSelected();
+
+ const std::string &eventId = event.getId();
+
+ if (selected >= 0)
+ {
+ if (eventId == "use")
+ {
+ use(selected);
+ return;
+ }
+ else if (eventId == "delete"
+ && mCharacterEntries[selected]->getCharacter())
+ {
+ new CharDeleteConfirm(this, selected);
+ return;
+ }
+ else if (eventId == "info")
+ {
+ Net::Character *const character = mCharacterEntries[
+ selected]->getCharacter();
+ if (!character)
+ return;
+
+ const LocalPlayer *const data = character->dummy;
+ if (!data)
+ return;
+
+ const std::string msg = strprintf(
+ // TRANSLATORS: char select dialog. player info message.
+ _("Hp: %u/%u\nMp: %u/%u\nLevel: %u\n"
+ "Experience: %u\nMoney: %s"),
+ character->data.mAttributes[PlayerInfo::HP],
+ character->data.mAttributes[PlayerInfo::MAX_HP],
+ character->data.mAttributes[PlayerInfo::MP],
+ character->data.mAttributes[PlayerInfo::MAX_MP],
+ character->data.mAttributes[PlayerInfo::LEVEL],
+ character->data.mAttributes[PlayerInfo::EXP],
+ Units::formatCurrency(
+ character->data.mAttributes[PlayerInfo::MONEY]).c_str());
+ new OkDialog(data->getName(), msg, DIALOG_SILENCE);
+ }
+ }
+ if (eventId == "switch")
+ {
+ Net::getCharServerHandler()->clear();
+ close();
+ }
+ else if (eventId == "change_password")
+ {
+ client->setState(STATE_CHANGEPASSWORD);
+ }
+ else if (eventId == "change_email")
+ {
+ client->setState(STATE_CHANGEEMAIL);
+ }
+ else if (eventId == "unregister")
+ {
+ Net::getCharServerHandler()->clear();
+ client->setState(STATE_UNREGISTER);
+ }
+ else if (eventId == "try delete character")
+ {
+ if (mDeleteDialog && mDeleteIndex != -1 && mDeleteDialog->getText()
+ == LoginDialog::savedPassword)
+ {
+ attemptCharacterDelete(mDeleteIndex);
+ mDeleteDialog = nullptr;
+ }
+ else
+ {
+ // TRANSLATORS: error message
+ new OkDialog(_("Error"), _("Incorrect password"), DIALOG_ERROR);
+ }
+ mDeleteIndex = -1;
+ }
+}
+
+void CharSelectDialog::use(const int selected)
+{
+ if (mCharacterEntries[selected]
+ && mCharacterEntries[selected]->getCharacter())
+ {
+ attemptCharacterSelect(selected);
+ }
+ else
+ {
+ CharCreateDialog *const charCreateDialog =
+ new CharCreateDialog(this, selected);
+ mCharServerHandler->setCharCreateDialog(charCreateDialog);
+ }
+}
+
+void CharSelectDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ const int actionId = static_cast<KeyEvent*>(&keyEvent)->getActionId();
+ switch (actionId)
+ {
+ case Input::KEY_GUI_CANCEL:
+ keyEvent.consume();
+ action(gcn::ActionEvent(mSwitchLoginButton,
+ mSwitchLoginButton->getActionEventId()));
+ break;
+
+ case Input::KEY_GUI_RIGHT:
+ {
+ keyEvent.consume();
+ int idx = mCharacterView->getSelected();
+ if (idx >= 0)
+ {
+ idx ++;
+ if (idx == SLOTS_PER_ROW)
+ break;
+ mCharacterView->show(idx);
+ updateState();
+ }
+ break;
+ }
+
+ case Input::KEY_GUI_LEFT:
+ {
+ keyEvent.consume();
+ int idx = mCharacterView->getSelected();
+ if (idx >= 0)
+ {
+ if (!idx || idx == SLOTS_PER_ROW)
+ break;
+ idx --;
+ mCharacterView->show(idx);
+ updateState();
+ }
+ break;
+ }
+
+ case Input::KEY_GUI_UP:
+ {
+ keyEvent.consume();
+ int idx = mCharacterView->getSelected();
+ if (idx >= 0)
+ {
+ if (idx < SLOTS_PER_ROW)
+ break;
+ idx -= SLOTS_PER_ROW;
+ mCharacterView->show(idx);
+ updateState();
+ }
+ break;
+ }
+
+ case Input::KEY_GUI_DOWN:
+ {
+ keyEvent.consume();
+ int idx = mCharacterView->getSelected();
+ if (idx >= 0)
+ {
+ if (idx >= SLOTS_PER_ROW)
+ break;
+ idx += SLOTS_PER_ROW;
+ mCharacterView->show(idx);
+ updateState();
+ }
+ break;
+ }
+
+ case Input::KEY_GUI_DELETE:
+ {
+ keyEvent.consume();
+ const int idx = mCharacterView->getSelected();
+ if (idx >= 0 && mCharacterEntries[idx]
+ && mCharacterEntries[idx]->getCharacter())
+ {
+ new CharDeleteConfirm(this, idx);
+ }
+ break;
+ }
+
+ case Input::KEY_GUI_SELECT:
+ {
+ keyEvent.consume();
+ use(mCharacterView->getSelected());
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/**
+ * Communicate character deletion to the server.
+ */
+void CharSelectDialog::attemptCharacterDelete(const int index)
+{
+ if (mLocked)
+ return;
+
+ if (mCharacterEntries[index])
+ {
+ mCharServerHandler->deleteCharacter(
+ mCharacterEntries[index]->getCharacter());
+ }
+ lock();
+}
+
+void CharSelectDialog::askPasswordForDeletion(const int index)
+{
+ mDeleteIndex = index;
+ mDeleteDialog = new TextDialog(
+ // TRANSLATORS: char deletion question.
+ _("Enter password for deleting character"), _("Enter password:"),
+ this, true);
+ mDeleteDialog->setActionEventId("try delete character");
+ mDeleteDialog->addActionListener(this);
+}
+
+/**
+ * Communicate character selection to the server.
+ */
+void CharSelectDialog::attemptCharacterSelect(const int index)
+{
+ if (mLocked || !mCharacterEntries[index])
+ return;
+
+ setVisible(false);
+ if (mCharServerHandler)
+ {
+ mCharServerHandler->chooseCharacter(
+ mCharacterEntries[index]->getCharacter());
+ }
+ lock();
+}
+
+void CharSelectDialog::setCharacters(const Net::Characters &characters)
+{
+ // Reset previous characters
+ FOR_EACH (std::vector<CharacterDisplay*>::const_iterator,
+ iter, mCharacterEntries)
+ {
+ if (*iter)
+ (*iter)->setCharacter(nullptr);
+ }
+
+ FOR_EACH (Net::Characters::const_iterator, i, characters)
+ {
+ if (!*i)
+ continue;
+
+ Net::Character *const character = *i;
+
+ // Slots Number start at 1 for Manaserv, so we offset them by one.
+#ifdef MANASERV_SUPPORT
+ int characterSlot = character->slot;
+ if (Net::getNetworkType() == ServerInfo::MANASERV && characterSlot > 0)
+ --characterSlot;
+#else
+ const int characterSlot = character->slot;
+#endif
+
+ if (characterSlot >= static_cast<int>(mCharacterEntries.size()))
+ {
+ logger->log("Warning: slot out of range: %d", character->slot);
+ continue;
+ }
+
+ if (mCharacterEntries[characterSlot])
+ mCharacterEntries[characterSlot]->setCharacter(character);
+ }
+
+ updateState();
+}
+
+void CharSelectDialog::lock()
+{
+ if (!mLocked)
+ setLocked(true);
+}
+
+void CharSelectDialog::unlock()
+{
+ setLocked(false);
+}
+
+void CharSelectDialog::setLocked(const bool locked)
+{
+ mLocked = locked;
+
+ if (mSwitchLoginButton)
+ mSwitchLoginButton->setEnabled(!locked);
+ if (mChangePasswordButton)
+ mChangePasswordButton->setEnabled(!locked);
+ if (mUnregisterButton)
+ mUnregisterButton->setEnabled(!locked);
+ if (mChangeEmailButton)
+ mChangeEmailButton->setEnabled(!locked);
+ if (mPlayButton)
+ mPlayButton->setEnabled(!locked);
+ if (mDeleteButton)
+ mDeleteButton->setEnabled(!locked);
+
+ for (size_t i = 0, sz = mCharacterEntries.size(); i < sz; ++i)
+ {
+ if (mCharacterEntries[i])
+ mCharacterEntries[i]->setActive(!mLocked);
+ }
+}
+
+bool CharSelectDialog::selectByName(const std::string &name,
+ const SelectAction selAction)
+{
+ if (mLocked)
+ return false;
+
+ for (size_t i = 0, sz = mCharacterEntries.size(); i < sz; ++i)
+ {
+ if (mCharacterEntries[i])
+ {
+ const Net::Character *const character
+ = mCharacterEntries[i]->getCharacter();
+ if (character)
+ {
+ if (character->dummy && character->dummy->getName() == name)
+ {
+ mCharacterView->show(static_cast<int>(i));
+ updateState();
+ if (selAction == Choose)
+ attemptCharacterSelect(static_cast<int>(i));
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void CharSelectDialog::close()
+{
+ client->setState(STATE_SWITCH_LOGIN);
+ Window::close();
+}
+
+void CharSelectDialog::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+ if (mCharacterView)
+ mCharacterView->resize();
+}
+
+void CharSelectDialog::updateState()
+{
+ const int idx = mCharacterView->getSelected();
+ if (idx == -1)
+ {
+ mPlayButton->setEnabled(false);
+ return;
+ }
+ mPlayButton->setEnabled(true);
+
+ if (mCharacterEntries[idx] && mCharacterEntries[idx]->getCharacter())
+ {
+ // TRANSLATORS: char select dialog. button.
+ mPlayButton->setCaption(_("Play"));
+ }
+ else
+ {
+ // TRANSLATORS: char select dialog. button.
+ mPlayButton->setCaption(_("Create"));
+ }
+}
diff --git a/src/gui/windows/charselectdialog.h b/src/gui/windows/charselectdialog.h
new file mode 100644
index 000000000..9556ba633
--- /dev/null
+++ b/src/gui/windows/charselectdialog.h
@@ -0,0 +1,126 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_CHARSELECTDIALOG_H
+#define GUI_CHARSELECTDIALOG_H
+
+#include "main.h"
+
+#include "gui/widgets/window.h"
+
+#include "net/charserverhandler.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+class Button;
+class CharacterDisplay;
+class CharacterViewBase;
+class Label;
+class LoginData;
+class TextDialog;
+
+/**
+ * Character selection dialog.
+ *
+ * \ingroup Interface
+ */
+class CharSelectDialog final : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ friend class CharDeleteConfirm;
+ friend class Net::CharServerHandler;
+
+ /**
+ * Constructor.
+ */
+ explicit CharSelectDialog(LoginData *const data);
+
+ A_DELETE_COPY(CharSelectDialog)
+
+ ~CharSelectDialog();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ 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,
+ const SelectAction action = Focus);
+
+ void askPasswordForDeletion(const int index);
+
+ void close() override;
+
+ void widgetResized(const gcn::Event &event) override;
+
+ void updateState();
+
+ private:
+ void attemptCharacterDelete(const int index);
+
+ void attemptCharacterSelect(const int index);
+
+ void setCharacters(const Net::Characters &characters);
+
+ void use(const int selected);
+
+ void lock();
+ void unlock();
+ void setLocked(const bool locked);
+
+ LoginData *mLoginData;
+
+ Button *mSwitchLoginButton;
+ Button *mChangePasswordButton;
+ Button *mUnregisterButton;
+ Button *mChangeEmailButton;
+ Button *mPlayButton;
+ Button *mInfoButton;
+ Button *mDeleteButton;
+ CharacterViewBase *mCharacterView;
+
+ std::vector<CharacterDisplay*> mCharacterEntries;
+
+ Net::CharServerHandler *mCharServerHandler;
+ TextDialog *mDeleteDialog;
+ int mDeleteIndex;
+ bool mLocked;
+ bool mSmallScreen;
+};
+
+#endif // GUI_CHARSELECTDIALOG_H
diff --git a/src/gui/windows/chatwindow.cpp b/src/gui/windows/chatwindow.cpp
new file mode 100644
index 000000000..61f42a824
--- /dev/null
+++ b/src/gui/windows/chatwindow.cpp
@@ -0,0 +1,1795 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/chatwindow.h"
+
+#include "actorspritemanager.h"
+#include "client.h"
+#include "commandhandler.h"
+#include "configuration.h"
+#include "game.h"
+#include "guild.h"
+#include "party.h"
+#include "spellshortcut.h"
+
+#include "being/localplayer.h"
+#include "being/playerinfo.h"
+#include "being/playerrelations.h"
+
+#include "input/inputmanager.h"
+#include "input/keyevent.h"
+
+#include "gui/gui.h"
+#include "gui/sdlfont.h"
+#include "gui/sdlinput.h"
+#include "gui/viewport.h"
+
+#include "gui/windows/emotewindow.h"
+#include "gui/windows/setup.h"
+#include "gui/windows/whoisonline.h"
+
+#include "gui/widgets/battletab.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/itemlinkhandler.h"
+#include "gui/widgets/langtab.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/textfield.h"
+#include "gui/widgets/tradetab.h"
+#include "gui/widgets/whispertab.h"
+
+#include "net/chathandler.h"
+#include "net/playerhandler.h"
+#include "net/net.h"
+
+#include "utils/copynpaste.h"
+#include "utils/gettext.h"
+
+#include "resources/resourcemanager.h"
+
+#include <guichan/focushandler.hpp>
+
+#include <sstream>
+
+#include <sys/stat.h>
+
+#include "debug.h"
+
+/**
+ * The chat input hides when it loses focus. It is also invisible by default.
+ */
+class ChatInput final : public TextField
+{
+ public:
+ explicit ChatInput(ChatWindow *const window):
+ TextField(window, "", false),
+ mWindow(window),
+ mFocusGaining(false)
+ {
+ setVisible(false);
+ addFocusListener(this);
+ }
+
+ A_DELETE_COPY(ChatInput)
+
+ /**
+ * Called if the chat input loses focus. It will set itself to
+ * invisible as result.
+ */
+ void focusLost(const gcn::Event &event)
+ {
+ TextField::focusLost(event);
+ if (mFocusGaining || !config.getBoolValue("protectChatFocus"))
+ {
+ processVisible(false);
+ if (chatWindow)
+ chatWindow->updateVisibility();
+ mFocusGaining = false;
+ return;
+ }
+ mFocusGaining = true;
+ requestFocus();
+ mFocusGaining = false;
+ }
+
+ void processVisible(const bool n)
+ {
+ if (!mWindow || isVisible() == n)
+ return;
+
+ if (!n)
+ mFocusGaining = true;
+ setVisible(n);
+ if (config.getBoolValue("hideChatInput"))
+ mWindow->adjustTabSize();
+ if (emoteWindow)
+ emoteWindow->hide();
+ }
+
+ void unprotectFocus()
+ { mFocusGaining = true; }
+
+ void setVisible(bool visible)
+ {
+ TextField::setVisible(visible);
+ }
+
+ private:
+ ChatWindow *mWindow;
+ bool mFocusGaining;
+};
+
+const char *COLOR_NAME[14] =
+{
+ // TRANSLATORS: chat color
+ N_("default"),
+ // TRANSLATORS: chat color
+ N_("black"),
+ // TRANSLATORS: chat color
+ N_("red"),
+ // TRANSLATORS: chat color
+ N_("green"),
+ // TRANSLATORS: chat color
+ N_("blue"),
+ // TRANSLATORS: chat color
+ N_("gold"),
+ // TRANSLATORS: chat color
+ N_("yellow"),
+ // TRANSLATORS: chat color
+ N_("pink"),
+ // TRANSLATORS: chat color
+ N_("purple"),
+ // TRANSLATORS: chat color
+ N_("grey"),
+ // TRANSLATORS: chat color
+ N_("brown"),
+ // TRANSLATORS: chat color
+ N_("rainbow 1"),
+ // TRANSLATORS: chat color
+ N_("rainbow 2"),
+ // TRANSLATORS: chat color
+ N_("rainbow 3"),
+};
+
+
+class ColorListModel final : public gcn::ListModel
+{
+public:
+ ~ColorListModel()
+ { }
+
+ int getNumberOfElements()
+ {
+ return 14;
+ }
+
+ std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "???";
+ return gettext(COLOR_NAME[i]);
+ }
+};
+
+static const char *const ACTION_COLOR_PICKER = "color picker";
+
+ChatWindow::ChatWindow():
+ // TRANSLATORS: chat window name
+ Window(_("Chat"), false, nullptr, "chat.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mItemLinkHandler(new ItemLinkHandler),
+ mChatTabs(new TabbedArea(this)),
+ mChatInput(new ChatInput(this)),
+ mRainbowColor(0),
+ mWhispers(),
+ mHistory(),
+ mCurHist(),
+ mCommands(),
+ mCustomWords(),
+ mReturnToggles(config.getBoolValue("ReturnToggles")),
+ mTradeFilter(),
+ mColorListModel(new ColorListModel),
+ mColorPicker(new DropDown(this, mColorListModel)),
+ mChatColor(config.getIntValue("chatColor")),
+ mChatHistoryIndex(0),
+ mAwayLog(),
+ mHighlights(),
+ mGlobalsFilter(),
+ mGMLoaded(false),
+ mHaveMouse(false),
+ mAutoHide(config.getBoolValue("autohideChat")),
+ mShowBattleEvents(config.getBoolValue("showBattleEvents")),
+ mShowAllLang(serverConfig.getValue("showAllLang", 0)),
+ mTmpVisible(false)
+{
+ listen(CHANNEL_ATTRIBUTES);
+
+ setWindowName("Chat");
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ setShowTitle(false);
+ setResizable(true);
+ setDefaultVisible(true);
+ setSaveVisible(true);
+ setStickyButtonLock(true);
+
+ int w = 600;
+#ifdef ANDROID
+ if (mainGraphics->getWidth() < 710)
+ w = mainGraphics->getWidth() - 110;
+ if (w < 100)
+ w = 100;
+ if (mainGraphics->getHeight() < 480)
+ setDefaultSize(w, 90, ImageRect::UPPER_LEFT, -110, -35);
+ else
+ setDefaultSize(w, 123, ImageRect::UPPER_LEFT, -110, -35);
+#else
+ if (mainGraphics->getWidth() < 600)
+ w = mainGraphics->getWidth() - 10;
+ if (w < 100)
+ w = 100;
+ setDefaultSize(w, 123, ImageRect::LOWER_LEFT);
+#endif
+ setMinWidth(150);
+ setMinHeight(90);
+
+ setTitleBarHeight(getPadding() + getTitlePadding());
+
+ if (emoteWindow)
+ emoteWindow->addListeners(this);
+
+ mChatTabs->enableScrollButtons(true);
+ mChatTabs->setFollowDownScroll(true);
+ mChatTabs->setResizeHeight(false);
+
+ mChatInput->setActionEventId("chatinput");
+ mChatInput->addActionListener(this);
+
+ 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 * mPadding - 8 - 16, mPadding);
+
+ // Add key listener to chat input to be able to respond to up/down
+ mChatInput->addKeyListener(this);
+ mCurHist = mHistory.end();
+ mColorPicker->setVisible(config.getBoolValue("showChatColorsList"));
+
+ fillCommands();
+ if (player_node && player_node->isGM())
+ loadGMCommands();
+ initTradeFilter();
+ loadCustomList();
+ parseHighlights();
+ parseGlobalsFilter();
+
+ config.addListener("autohideChat", this);
+ config.addListener("showBattleEvents", this);
+ config.addListener("globalsFilter", this);
+
+ enableVisibleSound(true);
+}
+
+ChatWindow::~ChatWindow()
+{
+ config.removeListeners(this);
+ saveState();
+ config.setValue("ReturnToggles", mReturnToggles);
+ removeAllWhispers();
+ delete mItemLinkHandler;
+ mItemLinkHandler = nullptr;
+ delete mColorPicker;
+ mColorPicker = nullptr;
+ delete mColorListModel;
+ mColorListModel = nullptr;
+}
+
+void ChatWindow::loadCommandsFile(const std::string &name)
+{
+ StringVect list;
+ ResourceManager::loadTextFile(name, list);
+ StringVectCIter it = list.begin();
+ const StringVectCIter it_end = list.end();
+
+ while (it != it_end)
+ {
+ const std::string str = *it;
+ if (!str.empty())
+ mCommands.push_back(str);
+ ++ it;
+ }
+}
+
+void ChatWindow::fillCommands()
+{
+ loadCommandsFile("chatcommands.txt");
+ CommandHandler::addChatCommands(mCommands);
+}
+
+void ChatWindow::loadGMCommands()
+{
+ if (mGMLoaded)
+ return;
+
+ loadCommandsFile("gmcommands.txt");
+ mGMLoaded = true;
+}
+
+void ChatWindow::adjustTabSize()
+{
+ const gcn::Rectangle area = getChildrenArea();
+
+ const int aw = area.width;
+ const int ah = area.height;
+ const int frame = mChatInput->getFrameSize();
+ const int inputHeight = mChatInput->getHeight();
+ const int frame2 = 2 * frame;
+ const int awFrame2 = aw - frame2;
+ mChatInput->setPosition(frame, ah - inputHeight - frame);
+ mChatInput->setWidth(awFrame2);
+ mChatTabs->setWidth(awFrame2);
+ const int height = ah - frame2 - (inputHeight + frame2);
+ if (mChatInput->isVisible() || !config.getBoolValue("hideChatInput"))
+ mChatTabs->setHeight(height);
+ else
+ mChatTabs->setHeight(height + inputHeight);
+
+ const ChatTab *const tab = getFocused();
+ if (tab)
+ {
+ gcn::Widget *const content = tab->mScrollArea;
+ if (content)
+ {
+ const int contentFrame2 = 2 * content->getFrameSize();
+ content->setSize(mChatTabs->getWidth() - contentFrame2,
+ mChatTabs->getContainerHeight() - contentFrame2);
+ content->logic();
+ }
+ }
+
+ mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
+ - 2 * mPadding - 8 - 16, mPadding);
+
+ mChatTabs->adjustSize();
+}
+
+void ChatWindow::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+
+ adjustTabSize();
+}
+
+ChatTab *ChatWindow::getFocused() const
+{
+ return static_cast<ChatTab*>(mChatTabs->getSelectedTab());
+}
+
+void ChatWindow::clearTab(ChatTab *const tab) const
+{
+ if (tab)
+ tab->clearText();
+}
+
+void ChatWindow::clearTab() const
+{
+ clearTab(getFocused());
+}
+
+void ChatWindow::prevTab()
+{
+ if (!mChatTabs)
+ return;
+
+ int tab = mChatTabs->getSelectedTabIndex();
+
+ if (tab <= 0)
+ tab = mChatTabs->getNumberOfTabs();
+ tab--;
+
+ mChatTabs->setSelectedTabByPos(tab);
+}
+
+void ChatWindow::nextTab()
+{
+ if (!mChatTabs)
+ return;
+
+ int tab = mChatTabs->getSelectedTabIndex();
+
+ tab++;
+ if (tab == mChatTabs->getNumberOfTabs())
+ tab = 0;
+
+ mChatTabs->setSelectedTabByPos(tab);
+}
+
+void ChatWindow::closeTab() const
+{
+ if (!mChatTabs)
+ return;
+
+ Tab *const tab = mChatTabs->getTabByIndex(
+ mChatTabs->getSelectedTabIndex());
+ if (!tab)
+ return;
+ WhisperTab *const whisper = dynamic_cast<WhisperTab* const>(tab);
+ if (!whisper)
+ return;
+
+ whisper->handleCommand("close", "");
+}
+
+void ChatWindow::defaultTab()
+{
+ if (mChatTabs)
+ mChatTabs->setSelectedTabByPos(static_cast<unsigned>(0));
+}
+
+void ChatWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "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
+ mChatInput->unprotectFocus();
+ 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 (eventId == "emote")
+ {
+ if (emoteWindow)
+ {
+ const std::string str = emoteWindow->getSelectedEmote();
+ if (!str.empty())
+ {
+ addInputText(str, false);
+ emoteWindow->clearEmote();
+ }
+ }
+ }
+ else if (eventId == "color")
+ {
+ if (emoteWindow)
+ {
+ const std::string str = emoteWindow->getSelectedColor();
+ if (!str.empty())
+ {
+ addInputText(str, false);
+ emoteWindow->clearColor();
+ }
+ }
+ }
+ else if (eventId == "font")
+ {
+ if (emoteWindow)
+ {
+ const std::string str = emoteWindow->getSelectedFont();
+ if (!str.empty())
+ {
+ addInputText(str, false);
+ emoteWindow->clearFont();
+ }
+ }
+ }
+ else if (eventId == ACTION_COLOR_PICKER)
+ {
+ if (mColorPicker)
+ {
+ 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 (!isWindowVisible())
+ {
+ 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->processVisible(true);
+ unHideWindow();
+ mChatInput->requestFocus();
+ return true;
+}
+
+bool ChatWindow::isInputFocused() const
+{
+ return mChatInput->isFocused();
+}
+
+void ChatWindow::removeTab(ChatTab *const tab)
+{
+ mChatTabs->removeTab(tab);
+}
+
+void ChatWindow::addTab(ChatTab *const tab)
+{
+ if (!tab)
+ return;
+
+ mChatTabs->addTab(tab, tab->mScrollArea);
+ logic();
+}
+
+void ChatWindow::removeWhisper(const std::string &nick)
+{
+ std::string tempNick = nick;
+ toLower(tempNick);
+ mWhispers.erase(tempNick);
+}
+
+void ChatWindow::removeAllWhispers()
+{
+ std::list<ChatTab*> tabs;
+
+ FOR_EACH (TabMap::iterator, iter, mWhispers)
+ tabs.push_back(iter->second);
+
+ for (std::list<ChatTab*>::iterator it = tabs.begin();
+ it != tabs.end(); ++it)
+ {
+ delete *it;
+ }
+
+ mWhispers.clear();
+}
+
+void ChatWindow::ignoreAllWhispers()
+{
+ for (TabMap::iterator iter = mWhispers.begin();
+ iter != mWhispers.end();
+ ++ iter)
+ {
+ const WhisperTab *const tab = dynamic_cast<const WhisperTab* const>(
+ iter->second);
+ if (tab && player_relations.getRelation(tab->getNick())
+ != PlayerRelation::IGNORED)
+ {
+ player_relations.setRelation(tab->getNick(),
+ PlayerRelation::IGNORED);
+ }
+
+ delete (iter->second);
+ iter->second = nullptr;
+ }
+}
+
+void ChatWindow::chatInput(const std::string &message) const
+{
+ ChatTab *tab = nullptr;
+ 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) const
+{
+ if (localChatTab)
+ localChatTab->chatInput(msg);
+ else
+ chatInput(msg);
+}
+
+void ChatWindow::doPresent() const
+{
+ if (!actorSpriteManager)
+ return;
+
+ const ActorSprites &actors = actorSpriteManager->getAll();
+ std::string response;
+ int playercount = 0;
+
+ FOR_EACH (ActorSpritesConstIterator, it, actors)
+ {
+ if ((*it)->getType() == ActorSprite::PLAYER)
+ {
+ if (!response.empty())
+ response.append(", ");
+ response.append(static_cast<Being*>(*it)->getName());
+ playercount ++;
+ }
+ }
+
+ const std::string log = strprintf(
+ // TRANSLATORS: chat message
+ _("Present: %s; %d players are present."),
+ response.c_str(), playercount);
+
+ if (getFocused())
+ getFocused()->chatLog(log, BY_SERVER);
+}
+
+void ChatWindow::scroll(const int amount) const
+{
+ if (!isWindowVisible())
+ return;
+
+ ChatTab *const tab = getFocused();
+ if (tab)
+ tab->scroll(amount);
+}
+
+void ChatWindow::mousePressed(gcn::MouseEvent &event)
+{
+ if (event.isConsumed())
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (viewport)
+ {
+ Tab *const tab = mChatTabs->getSelectedTab();
+ if (tab)
+ {
+ if (inputManager.isActionActive(static_cast<int>(
+ Input::KEY_CHAT_MOD)))
+ {
+ ChatTab *const wTab = dynamic_cast<WhisperTab*>(tab);
+ if (wTab)
+ wTab->handleCommand("close", "");
+ }
+ else
+ {
+ ChatTab *const cTab = dynamic_cast<ChatTab*>(tab);
+ if (cTab)
+ viewport->showChatPopup(cTab);
+ }
+ }
+ }
+ }
+
+ Window::mousePressed(event);
+
+ if (event.isConsumed())
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ const ChatTab *const 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 (canMove() && isMovable() && mMoved)
+ {
+ int newX = std::max(0, getX() + event.getX() - mDragOffsetX);
+ int newY = std::max(0, getY() + event.getY() - mDragOffsetY);
+ newX = std::min(mainGraphics->mWidth - getWidth(), newX);
+ newY = std::min(mainGraphics->mHeight - getHeight(), newY);
+ setPosition(newX, newY);
+ }
+}
+
+#define caseKey(key, str) case key:\
+ temp = str; \
+ break
+
+void ChatWindow::keyPressed(gcn::KeyEvent &event)
+{
+ const int key = event.getKey().getValue();
+ const int actionId = static_cast<KeyEvent*>(&event)->getActionId();
+ if (actionId == static_cast<int>(Input::KEY_GUI_DOWN))
+ {
+ if (mCurHist != mHistory.end())
+ {
+ // Move forward through the history
+ const 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().empty())
+ {
+ mChatInput->setText("");
+ }
+ }
+ else if (actionId == static_cast<int>(Input::KEY_GUI_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 (actionId == static_cast<int>(Input::KEY_GUI_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 (actionId == static_cast<int>(Input::KEY_GUI_TAB) &&
+ !mChatInput->getText().empty())
+ {
+ autoComplete();
+ return;
+ }
+ else if (actionId == static_cast<int>(Input::KEY_GUI_CANCEL) &&
+ mChatInput->isVisible())
+ {
+ mChatInput->processVisible(false);
+ }
+ else if (actionId == static_cast<int>(Input::KEY_CHAT_PREV_HISTORY) &&
+ mChatInput->isVisible())
+ {
+ const ChatTab *const tab = getFocused();
+ if (tab && tab->hasRows())
+ {
+ if (!mChatHistoryIndex)
+ {
+ mChatHistoryIndex = static_cast<unsigned>(
+ tab->getRows().size());
+
+ mChatInput->setText("");
+ mChatInput->setCaretPosition(0);
+ return;
+ }
+ else
+ {
+ mChatHistoryIndex --;
+ }
+
+ unsigned int f = 0;
+ const std::list<std::string> &rows = tab->getRows();
+ for (std::list<std::string>::const_iterator it = rows.begin(),
+ it_end = rows.end(); it != it_end; ++ it, f ++)
+ {
+ if (f == mChatHistoryIndex)
+ mChatInput->setText(*it);
+ }
+ mChatInput->setCaretPosition(static_cast<unsigned>(
+ mChatInput->getText().length()));
+ }
+ }
+ else if (actionId == static_cast<int>(Input::KEY_CHAT_NEXT_HISTORY) &&
+ mChatInput->isVisible())
+ {
+ const ChatTab *const tab = getFocused();
+ if (tab && tab->hasRows())
+ {
+ const std::list<std::string> &rows = tab->getRows();
+ const size_t &tabSize = rows.size();
+ if (mChatHistoryIndex + 1 < tabSize)
+ {
+ mChatHistoryIndex ++;
+ }
+ else if (mChatHistoryIndex < tabSize)
+ {
+ mChatHistoryIndex ++;
+ mChatInput->setText("");
+ mChatInput->setCaretPosition(0);
+ return;
+ }
+ else
+ {
+ mChatHistoryIndex = 0;
+ }
+
+ unsigned int f = 0;
+ for (std::list<std::string>::const_iterator
+ it = rows.begin(), it_end = rows.end();
+ it != it_end; ++it, f++)
+ {
+ if (f == mChatHistoryIndex)
+ mChatInput->setText(*it);
+ }
+ mChatInput->setCaretPosition(static_cast<unsigned>(
+ mChatInput->getText().length()));
+ }
+ }
+
+ std::string temp;
+ switch (key)
+ {
+ case Key::F1:
+ if (emoteWindow)
+ emoteWindow->show();
+ break;
+ caseKey(Key::F2, "\u2318");
+ caseKey(Key::F3, "\u263A");
+ caseKey(Key::F4, "\u2665");
+ caseKey(Key::F5, "\u266A");
+ caseKey(Key::F6, "\u266B");
+ caseKey(Key::F7, "\u26A0");
+ caseKey(Key::F8, "\u2622");
+ caseKey(Key::F9, "\u262E");
+ caseKey(Key::F10, "\u2605");
+ caseKey(Key::F11, "\u2618");
+ caseKey(Key::F12, "\u2592");
+ default:
+ break;
+ }
+
+ if (!temp.empty())
+ addInputText(temp, false);
+}
+
+void ChatWindow::processEvent(const Channels channel,
+ const DepricatedEvent &event)
+{
+ if (channel == CHANNEL_ATTRIBUTES)
+ {
+ if (!mShowBattleEvents)
+ return;
+
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ switch (event.getInt("id"))
+ {
+ case PlayerInfo::EXP:
+ {
+ if (event.getInt("oldValue") > event.getInt("newValue"))
+ break;
+
+ const int change = event.getInt("newValue")
+ - event.getInt("oldValue");
+
+ if (change != 0)
+ {
+ battleChatLog(std::string("+").append(toString(
+ change)).append(" xp"));
+ }
+ break;
+ }
+ case PlayerInfo::LEVEL:
+ battleChatLog(std::string("Level: ").append(toString(
+ event.getInt("newValue"))));
+ break;
+ default:
+ break;
+ };
+ }
+ else if (event.getName() == EVENT_UPDATESTAT)
+ {
+ if (!config.getBoolValue("showJobExp"))
+ return;
+
+ const int id = event.getInt("id");
+ if (id == Net::getPlayerHandler()->getJobLocation())
+ {
+ const std::pair<int, int> exp
+ = PlayerInfo::getStatExperience(id);
+ if (event.getInt("oldValue1") > exp.first
+ || !event.getInt("oldValue2"))
+ {
+ return;
+ }
+
+ const int change = exp.first - event.getInt("oldValue1");
+ if (change != 0)
+ {
+ battleChatLog(std::string("+").append(
+ toString(change)).append(" job"));
+ }
+ }
+ }
+ }
+}
+
+void ChatWindow::addInputText(const std::string &text, const bool space)
+{
+ const 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 visible)
+{
+ Window::setVisible(visible);
+
+ /*
+ * For whatever reason, if setVisible is called, the mTmpVisible effect
+ * should be disabled.
+ */
+ mTmpVisible = false;
+}
+
+void ChatWindow::addWhisper(const std::string &nick,
+ const std::string &mes, const 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;
+
+ WhisperTab *tab = nullptr;
+ const TabMap::const_iterator i = mWhispers.find(tempNick);
+
+ if (i != mWhispers.end())
+ {
+ tab = i->second;
+ }
+ else if (config.getBoolValue("whispertab"))
+ {
+ tab = addWhisperTab(nick);
+ if (tab)
+ saveState();
+ }
+
+ if (tab)
+ {
+ if (own == BY_PLAYER)
+ {
+ tab->chatInput(mes);
+ }
+ else if (own == BY_SERVER)
+ {
+ tab->chatLog(mes);
+ }
+ else
+ {
+ if (tab->getRemoveNames())
+ {
+ std::string msg = mes;
+ const size_t idx = mes.find(":");
+ if (idx != std::string::npos && idx > 0)
+ {
+ std::string nick2 = msg.substr(0, idx);
+ msg = msg.substr(idx + 1);
+ nick2 = removeColors(nick2);
+ nick2 = trim(nick2);
+ if (config.getBoolValue("removeColors"))
+ msg = removeColors(msg);
+ msg = trim(msg);
+ tab->chatLog(nick2, msg);
+ }
+ else
+ {
+ if (config.getBoolValue("removeColors"))
+ msg = removeColors(msg);
+ tab->chatLog(msg, BY_SERVER);
+ }
+ }
+ else
+ {
+ tab->chatLog(nick, mes);
+ }
+ player_node->afkRespond(tab, nick);
+ }
+ }
+ else if (localChatTab)
+ {
+ if (own == BY_PLAYER)
+ {
+ Net::getChatHandler()->privateMessage(nick, mes);
+
+ // TRANSLATORS: chat message
+ localChatTab->chatLog(strprintf(_("Whispering to %s: %s"),
+ nick.c_str(), mes.c_str()), BY_PLAYER);
+ }
+ else
+ {
+ localChatTab->chatLog(std::string(nick).append(
+ " : ").append(mes), ACT_WHISPER, false);
+ if (player_node)
+ player_node->afkRespond(nullptr, nick);
+ }
+ }
+}
+
+WhisperTab *ChatWindow::addWhisperTab(const std::string &nick,
+ const bool switchTo)
+{
+ if (!player_node)
+ return nullptr;
+
+ std::string playerName = player_node->getName();
+ std::string tempNick = nick;
+
+ toLower(playerName);
+ toLower(tempNick);
+
+ const TabMap::const_iterator i = mWhispers.find(tempNick);
+ WhisperTab *ret;
+
+ if (tempNick.compare(playerName) == 0)
+ return nullptr;
+
+ if (i != mWhispers.end())
+ {
+ ret = i->second;
+ }
+ else
+ {
+ ret = new WhisperTab(this, nick);
+ if (gui && !player_relations.isGoodName(nick))
+ ret->setLabelFont(gui->getSecureFont());
+ mWhispers[tempNick] = ret;
+ if (config.getBoolValue("showChatHistory"))
+ ret->loadFromLogFile(nick);
+ }
+
+ if (switchTo)
+ mChatTabs->setSelectedTab(ret);
+
+ return ret;
+}
+
+WhisperTab *ChatWindow::getWhisperTab(const std::string &nick) const
+{
+ if (!player_node)
+ return nullptr;
+
+ std::string playerName = player_node->getName();
+ std::string tempNick = nick;
+
+ toLower(playerName);
+ toLower(tempNick);
+
+ const TabMap::const_iterator i = mWhispers.find(tempNick);
+ WhisperTab *ret = nullptr;
+
+ if (tempNick.compare(playerName) == 0)
+ return nullptr;
+
+ 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("");
+ const 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 std::string("##").append(toString(mChatColor - 1)).append(msg);
+}
+
+void ChatWindow::autoComplete()
+{
+ const int caretPos = mChatInput->getCaretPosition();
+ int startName = 0;
+ const std::string inputText = mChatInput->getText();
+ bool needSecure(false);
+ std::string name = inputText.substr(0, caretPos);
+
+ 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;
+
+ const ChatTab *const cTab = static_cast<ChatTab*>(
+ mChatTabs->getSelectedTab());
+ StringVect nameList;
+
+ if (cTab)
+ cTab->getAutoCompleteList(nameList);
+ std::string newName = autoComplete(nameList, name);
+ if (!newName.empty())
+ needSecure = true;
+
+ if (newName.empty() && actorSpriteManager)
+ {
+ actorSpriteManager->getPlayerNames(nameList, true);
+ newName = autoComplete(nameList, name);
+ if (!newName.empty())
+ needSecure = true;
+ }
+ if (newName.empty())
+ newName = autoCompleteHistory(name);
+ if (newName.empty() && spellManager)
+ newName = spellManager->autoComplete(name);
+ if (newName.empty())
+ newName = autoComplete(name, &mCommands);
+ if (newName.empty() && actorSpriteManager)
+ {
+ actorSpriteManager->getMobNames(nameList);
+ newName = autoComplete(nameList, name);
+ }
+ if (newName.empty())
+ newName = autoComplete(name, &mCustomWords);
+ if (newName.empty())
+ {
+ whoIsOnline->getPlayerNames(nameList);
+ newName = autoComplete(nameList, name);
+ }
+
+ if (!newName.empty())
+ {
+ if (!startName && needSecure && (newName[0] == '/'
+ || newName[0] == '@' || newName[0] == '#'))
+ {
+ newName = "_" + newName;
+ }
+ mChatInput->setText(inputText.substr(0, startName).append(newName)
+ .append(inputText.substr(caretPos,
+ inputText.length() - caretPos)));
+
+ const 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(StringVect &names,
+ std::string partName) const
+{
+ StringVectCIter i = names.begin();
+ const StringVectCIter i_end = names.end();
+ toLower(partName);
+ std::string newName;
+
+ while (i != i_end)
+ {
+ if (!i->empty())
+ {
+ std::string name = *i;
+ toLower(name);
+
+ const size_t pos = name.find(partName, 0);
+ if (pos == 0)
+ {
+ if (!newName.empty())
+ newName = findSameSubstringI(*i, newName);
+ else
+ newName = *i;
+ }
+ }
+ ++i;
+ }
+
+ return newName;
+}
+
+std::string ChatWindow::autoComplete(const std::string &partName,
+ History *const words) const
+{
+ if (!words)
+ return "";
+
+ ChatCommands::const_iterator i = words->begin();
+ const ChatCommands::const_iterator i_end = words->end();
+ StringVect nameList;
+
+ while (i != i_end)
+ {
+ const std::string line = *i;
+ if (line.find(partName, 0) == 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(const std::string &partName) const
+{
+ History::const_iterator i = mHistory.begin();
+ const History::const_iterator i_end = mHistory.end();
+ StringVect nameList;
+
+ while (i != i_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,
+ const std::string &channel,
+ const bool ignoreRecord,
+ const bool tryRemoveColors)
+{
+ if (own == -1)
+ own = BY_SERVER;
+
+ std::string prefix;
+ if (!channel.empty())
+ prefix = std::string("##3").append(channel).append("##0");
+
+ if (tradeChatTab)
+ {
+ if (findI(line, mTradeFilter) != std::string::npos)
+ {
+ tradeChatTab->chatLog(prefix + line, own,
+ ignoreRecord, tryRemoveColors);
+ return;
+ }
+
+ size_t idx2 = line.find(": ");
+ if (idx2 != std::string::npos)
+ {
+ const size_t idx = line.find(": \302\202");
+ if (idx == idx2)
+ {
+ // ignore special message formats.
+ if (line.find(": \302\202\302") != std::string::npos)
+ return;
+ line = line.erase(idx + 2, 2);
+ tradeChatTab->chatLog(prefix + line, own, ignoreRecord,
+ tryRemoveColors);
+ return;
+ }
+ }
+
+ const size_t idx1 = line.find("@@");
+ if (idx1 != std::string::npos)
+ {
+ idx2 = line.find("|", idx1);
+ if (idx2 != std::string::npos)
+ {
+ const size_t idx3 = line.find("@@", idx2);
+ if (idx3 != std::string::npos)
+ {
+ if (line.find("http", idx1) != idx1 + 2)
+ {
+ tradeChatTab->chatLog(prefix + line, own,
+ ignoreRecord, tryRemoveColors);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ if (langChatTab && !channel.empty())
+ {
+ if (langChatTab->getChannelName() == channel)
+ {
+ langChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+ }
+ else if (mShowAllLang)
+ {
+ if (langChatTab)
+ {
+ langChatTab->chatLog(prefix + line, own,
+ ignoreRecord, tryRemoveColors);
+ }
+ else if (localChatTab)
+ {
+ localChatTab->chatLog(prefix + line, own,
+ ignoreRecord, tryRemoveColors);
+ }
+ }
+ }
+ else if (localChatTab && channel.empty())
+ {
+ localChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
+ }
+}
+
+void ChatWindow::battleChatLog(const std::string &line, Own own,
+ const bool ignoreRecord,
+ const 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()
+{
+ const 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);
+ if (tradeFile.is_open())
+ {
+ char line[100];
+ while (tradeFile.getline(line, 100))
+ {
+ const std::string str = line;
+ if (!str.empty())
+ mTradeFilter.push_back(str);
+ }
+ }
+ tradeFile.close();
+ }
+}
+
+void ChatWindow::updateOnline(std::set<std::string> &onlinePlayers) const
+{
+ const Party *party = nullptr;
+ const Guild *guild = nullptr;
+ if (player_node)
+ {
+ party = player_node->getParty();
+ guild = player_node->getGuild();
+ }
+ FOR_EACH (TabMap::const_iterator, iter, mWhispers)
+ {
+ if (!iter->second)
+ return;
+
+ WhisperTab *const tab = static_cast<WhisperTab*>(iter->second);
+ if (!tab)
+ continue;
+
+ if (onlinePlayers.find(tab->getNick()) != onlinePlayers.end())
+ {
+ tab->setWhisperTabColors();
+ }
+ else
+ {
+ const std::string nick = tab->getNick();
+ if (actorSpriteManager)
+ {
+ const Being *const being = actorSpriteManager->findBeingByName(
+ nick, ActorSprite::PLAYER);
+ if (being)
+ {
+ tab->setWhisperTabColors();
+ continue;
+ }
+ }
+ if (party)
+ {
+ const PartyMember *const pm = party->getMember(nick);
+ if (pm && pm->getOnline())
+ {
+ tab->setWhisperTabColors();
+ continue;
+ }
+ }
+ if (guild)
+ {
+ const GuildMember *const gm = guild->getMember(nick);
+ if (gm && gm->getOnline())
+ {
+ tab->setWhisperTabColors();
+ continue;
+ }
+ }
+ tab->setWhisperTabOfflineColors();
+ }
+ }
+}
+
+void ChatWindow::loadState()
+{
+ int num = 0;
+ while (num < 50)
+ {
+ const std::string nick = serverConfig.getValue(
+ "chatWhisper" + toString(num), "");
+
+ if (nick.empty())
+ break;
+ const int flags = serverConfig.getValue(
+ "chatWhisperFlags" + toString(num), 1);
+
+ ChatTab *const tab = addWhisperTab(nick);
+ if (tab)
+ {
+ tab->setAllowHighlight(flags & 1);
+ tab->setRemoveNames((flags & 2) / 2);
+ tab->setNoAway((flags & 4) / 4);
+ }
+ num ++;
+ }
+}
+
+void ChatWindow::saveState() const
+{
+ int num = 0;
+ for (TabMap::const_iterator iter = mWhispers.begin(),
+ iter_end = mWhispers.end(); iter != iter_end && num < 50; ++iter)
+ {
+ if (!iter->second)
+ return;
+
+ const WhisperTab *const tab = static_cast<WhisperTab*>(iter->second);
+
+ if (!tab)
+ continue;
+
+ serverConfig.setValue("chatWhisper" + toString(num),
+ tab->getNick());
+
+ serverConfig.setValue("chatWhisperFlags" + toString(num),
+ static_cast<int>(tab->getAllowHighlight())
+ + (2 * static_cast<int>(tab->getRemoveNames()))
+ + (4 * static_cast<int>(tab->getNoAway())));
+
+ num ++;
+ }
+
+ while (num < 50)
+ {
+ serverConfig.deleteKey("chatWhisper" + toString(num));
+ serverConfig.deleteKey("chatWhisperFlags" + toString(num));
+ num ++;
+ }
+}
+
+std::string ChatWindow::doReplace(const std::string &msg) const
+{
+ std::string str = msg;
+ replaceSpecialChars(str);
+ return str;
+}
+
+void ChatWindow::loadCustomList()
+{
+ std::ifstream listFile;
+ struct stat statbuf;
+
+ std::string listName = client->getServerConfigDirectory()
+ + "/customwords.txt";
+
+ if (!stat(listName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode))
+ {
+ listFile.open(listName.c_str(), std::ios::in);
+ if (listFile.is_open())
+ {
+ char line[101];
+ while (listFile.getline(line, 100))
+ {
+ std::string str = line;
+ if (!str.empty())
+ mCustomWords.push_back(str);
+ }
+ }
+ listFile.close();
+ }
+}
+
+void ChatWindow::addToAwayLog(const std::string &line)
+{
+ if (!player_node || !player_node->getAway())
+ return;
+
+ if (mAwayLog.size() > 20)
+ mAwayLog.pop_front();
+
+ if (findI(line, mHighlights) != std::string::npos)
+ mAwayLog.push_back("##9away:" + line);
+}
+
+void ChatWindow::displayAwayLog() const
+{
+ if (!localChatTab)
+ return;
+
+ std::list<std::string>::const_iterator i = mAwayLog.begin();
+ const std::list<std::string>::const_iterator i_end = mAwayLog.end();
+
+ while (i != i_end)
+ {
+ std::string str = *i;
+ localChatTab->addNewRow(str);
+ ++i;
+ }
+}
+
+void ChatWindow::parseHighlights()
+{
+ mHighlights.clear();
+ if (!player_node)
+ return;
+
+ splitToStringVector(mHighlights, config.getStringValue(
+ "highlightWords"), ',');
+
+ mHighlights.push_back(player_node->getName());
+}
+
+void ChatWindow::parseGlobalsFilter()
+{
+ mGlobalsFilter.clear();
+ if (!player_node)
+ return;
+
+ splitToStringVector(mGlobalsFilter, config.getStringValue(
+ "globalsFilter"), ',');
+
+ mHighlights.push_back(player_node->getName());
+}
+
+bool ChatWindow::findHighlight(const std::string &str)
+{
+ return findI(str, mHighlights) != std::string::npos;
+}
+
+void ChatWindow::copyToClipboard(const int x, const int y) const
+{
+ const ChatTab *const tab = getFocused();
+ if (!tab)
+ return;
+
+ const BrowserBox *const text = tab->mTextOutput;
+ if (!text)
+ return;
+
+ std::string str = text->getTextAtPos(x, y);
+ sendBuffer(str);
+}
+
+void ChatWindow::optionChanged(const std::string &name)
+{
+ if (name == "autohideChat")
+ mAutoHide = config.getBoolValue("autohideChat");
+ else if (name == "showBattleEvents")
+ mShowBattleEvents = config.getBoolValue("showBattleEvents");
+ else if (name == "globalsFilter")
+ parseGlobalsFilter();
+}
+
+void ChatWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ mHaveMouse = true;
+ Window::mouseMoved(event);
+}
+
+void ChatWindow::mouseEntered(gcn::MouseEvent& mouseEvent)
+{
+ mHaveMouse = true;
+ Window::mouseEntered(mouseEvent);
+}
+
+void ChatWindow::mouseExited(gcn::MouseEvent& mouseEvent)
+{
+ updateVisibility();
+ Window::mouseExited(mouseEvent);
+}
+
+void ChatWindow::draw(gcn::Graphics* graphics)
+{
+ BLOCK_START("ChatWindow::draw")
+ if (!mAutoHide || mHaveMouse)
+ Window::draw(graphics);
+ BLOCK_END("ChatWindow::draw")
+}
+
+void ChatWindow::updateVisibility()
+{
+ int mouseX = 0;
+ int mouseY = 0;
+ int x = 0;
+ int y = 0;
+ SDL_GetMouseState(&mouseX, &mouseY);
+ getAbsolutePosition(x, y);
+ if (mChatInput->isVisible())
+ {
+ mHaveMouse = true;
+ }
+ else
+ {
+ mHaveMouse = mouseX >= x && mouseX <= x + getWidth()
+ && mouseY >= y && mouseY <= y + getHeight();
+ }
+}
+
+void ChatWindow::unHideWindow()
+{
+ mHaveMouse = true;
+}
+
+#ifdef USE_PROFILER
+void ChatWindow::logicChildren()
+{
+ BLOCK_START("ChatWindow::logicChildren")
+ BasicContainer::logicChildren();
+ BLOCK_END("ChatWindow::logicChildren")
+}
+#endif
+
+void ChatWindow::addGlobalMessage(const std::string &line)
+{
+ if (debugChatTab && findI(line, mGlobalsFilter) != std::string::npos)
+ debugChatTab->chatLog(line, BY_OTHER);
+ else
+ localChatTab->chatLog(line, BY_GM);
+}
diff --git a/src/gui/windows/chatwindow.h b/src/gui/windows/chatwindow.h
new file mode 100644
index 000000000..7137af08f
--- /dev/null
+++ b/src/gui/windows/chatwindow.h
@@ -0,0 +1,378 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_CHATWINDOW_H
+#define GUI_CHATWINDOW_H
+
+#include "depricatedlistener.h"
+
+#include "configlistener.h"
+
+#include "utils/stringvector.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+#include <list>
+#include <map>
+#include <set>
+
+class ChatTab;
+class ChatInput;
+class ColorListModel;
+class DropDown;
+class TabbedArea;
+class ItemLinkHandler;
+class WhisperTab;
+
+const int DEFAULT_CHAT_WINDOW_SCROLL = 7;
+
+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 final
+{
+ CHATLOG() :
+ nick(),
+ text(),
+ own(BY_UNKNOWN)
+ {
+ }
+
+ A_DELETE_COPY(CHATLOG)
+
+ std::string nick;
+ std::string text;
+ Own own;
+};
+
+/**
+ * The chat window.
+ *
+ * \ingroup Interface
+ */
+class ChatWindow final : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener,
+ public DepricatedListener,
+ public ConfigListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ChatWindow();
+
+ A_DELETE_COPY(ChatWindow)
+
+ /**
+ * Destructor: used to write back values to the config file
+ */
+ ~ChatWindow();
+
+ /**
+ * Gets the focused tab.
+ */
+ ChatTab *getFocused() const A_WARN_UNUSED;
+
+ /**
+ * Clear the given tab.
+ */
+ void clearTab(ChatTab *const tab) const;
+
+ /**
+ * Clear the current tab.
+ */
+ void clearTab() const;
+
+ /**
+ * Switch to the previous tab in order
+ */
+ void prevTab();
+
+ /**
+ * Switch to the next tab in order
+ */
+ void nextTab();
+
+ /**
+ * Close current chat tab
+ */
+ void closeTab() const;
+
+ /**
+ * Switch to the default tab
+ */
+ void defaultTab();
+
+ /**
+ * Performs action.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * 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 A_WARN_UNUSED;
+
+ /**
+ * 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) const;
+
+ /**
+ * 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) const;
+
+ /** Called when key is pressed */
+ void keyPressed(gcn::KeyEvent &event) override;
+
+ /** 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, const 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) override;
+
+ /**
+ * Handles mouse when pressed.
+ */
+ void mousePressed(gcn::MouseEvent &event) override;
+
+ void processEvent(const Channels channel,
+ const DepricatedEvent &event) override;
+
+ /**
+ * 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(const int amount) const;
+
+ /**
+ * 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 A_WARN_UNUSED
+ { return mReturnToggles; }
+
+ void setReturnTogglesChat(const bool toggles)
+ { mReturnToggles = toggles; }
+
+ void doPresent() const;
+
+ void addWhisper(const std::string &nick, const std::string &mes,
+ const Own own = BY_OTHER);
+
+ WhisperTab *addWhisperTab(const std::string &nick,
+ const bool switchTo = false) A_WARN_UNUSED;
+
+ WhisperTab *getWhisperTab(const std::string &nick) const A_WARN_UNUSED;
+
+ void removeAllWhispers();
+
+ void ignoreAllWhispers();
+
+ void resortChatLog(std::string line, Own own,
+ const std::string &channel,
+ const bool ignoreRecord,
+ const bool tryRemoveColors);
+
+ static void battleChatLog(const std::string &line,
+ Own own = BY_UNKNOWN,
+ const bool ignoreRecord = false,
+ const bool tryRemoveColors = true);
+
+ void updateOnline(std::set<std::string> &onlinePlayers) const;
+
+ void loadState();
+
+ void saveState() const;
+
+ void loadCustomList();
+
+ void loadGMCommands();
+
+ std::string doReplace(const std::string &msg) const A_WARN_UNUSED;
+
+ void adjustTabSize();
+
+ void addToAwayLog(const std::string &line);
+
+ void displayAwayLog() const;
+
+ void clearAwayLog()
+ { mAwayLog.clear(); }
+
+ void parseHighlights();
+
+ void parseGlobalsFilter();
+
+ bool findHighlight(const std::string &str) A_WARN_UNUSED;
+
+ void copyToClipboard(const int x, const int y) const;
+
+ void optionChanged(const std::string &name) override;
+
+ void mouseEntered(gcn::MouseEvent& mouseEvent) override;
+
+ void mouseMoved(gcn::MouseEvent &event) override;
+
+ void mouseExited(gcn::MouseEvent& mouseEvent A_UNUSED) override;
+
+ void draw(gcn::Graphics* graphics) override;
+
+ void updateVisibility();
+
+ void unHideWindow();
+
+ void widgetResized(const gcn::Event &event) override;
+
+ void addGlobalMessage(const std::string &line);
+
+#ifdef USE_PROFILER
+ void logicChildren();
+#endif
+
+ protected:
+ friend class ChatTab;
+ friend class WhisperTab;
+ friend class PopupMenu;
+
+ typedef std::list<std::string> History;
+
+ /** Remove the given tab from the window */
+ void removeTab(ChatTab *const tab);
+
+ /** Add the tab to the window */
+ void addTab(ChatTab *const tab);
+
+ void removeWhisper(const std::string &nick);
+
+ void autoComplete();
+
+ std::string addColors(std::string &msg);
+
+ std::string autoCompleteHistory(const std::string &partName) const;
+
+ std::string autoComplete(const std::string &partName,
+ History *const words) const;
+
+ std::string autoComplete(StringVect &names,
+ std::string partName) const;
+
+ /** Used for showing item popup on clicking links **/
+ ItemLinkHandler *mItemLinkHandler;
+
+ /** Tabbed area for holding each channel. */
+ TabbedArea *mChatTabs;
+
+ /** Input box for typing chat messages. */
+ ChatInput *mChatInput;
+
+ void initTradeFilter();
+
+ int mRainbowColor;
+
+ private:
+ void fillCommands();
+
+ void loadCommandsFile(const std::string &name);
+
+
+ typedef std::map<const std::string, WhisperTab*> TabMap;
+ /** Manage whisper tabs */
+ TabMap mWhispers;
+
+ typedef History::iterator HistoryIterator;
+ History mHistory; /**< Command history. */
+ HistoryIterator mCurHist; /**< History iterator. */
+
+ typedef std::list<std::string> ChatCommands;
+ typedef ChatCommands::iterator ChatCommandsIterator;
+ History mCommands; /**< Command list. */
+ History mCustomWords;
+
+ bool mReturnToggles; // Marks whether <Return> toggles the chat log
+ // or not
+
+ StringVect mTradeFilter;
+
+ ColorListModel *mColorListModel;
+ DropDown *mColorPicker;
+ int mChatColor;
+ unsigned int mChatHistoryIndex;
+ std::list<std::string> mAwayLog;
+ StringVect mHighlights;
+ StringVect mGlobalsFilter;
+ bool mGMLoaded;
+ bool mHaveMouse;
+ bool mAutoHide;
+ bool mShowBattleEvents;
+ bool mShowAllLang;
+ bool mTmpVisible;
+};
+
+extern ChatWindow *chatWindow;
+
+#endif // GUI_CHATWINDOW_H
diff --git a/src/gui/windows/confirmdialog.cpp b/src/gui/windows/confirmdialog.cpp
new file mode 100644
index 000000000..34c54582c
--- /dev/null
+++ b/src/gui/windows/confirmdialog.cpp
@@ -0,0 +1,109 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/confirmdialog.h"
+
+#include "soundmanager.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/textbox.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include "debug.h"
+
+ConfirmDialog::ConfirmDialog(const std::string &title, const std::string &msg,
+ const std::string &soundEvent, const bool ignore,
+ const bool modal, Window *const parent):
+ Window(title, modal, parent, "confirm.xml"),
+ gcn::ActionListener(),
+ mTextBox(new TextBox(this))
+{
+ mTextBox->setEditable(false);
+ mTextBox->setOpaque(false);
+ mTextBox->setTextWrapped(msg, 260);
+
+ // TRANSLATORS: confirm dialog button
+ Button *const yesButton = new Button(this, _("Yes"), "yes", this);
+ // TRANSLATORS: confirm dialog button
+ Button *const noButton = new Button(this, _("No"), "no", this);
+ Button *const ignoreButton = ignore ? new Button(
+ // TRANSLATORS: confirm dialog button
+ this, _("Ignore"), "ignore", this) : nullptr;
+
+ const int numRows = mTextBox->getNumberOfRows();
+ int inWidth = yesButton->getWidth() + noButton->getWidth() +
+ (2 * mPadding);
+
+ 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(mPadding, mPadding);
+
+ // 8 is the padding that GUIChan adds to button widgets
+ // (top and bottom combined)
+ const int buttonPadding = getOption("buttonPadding", 8);
+ yesButton->setPosition((width - inWidth) / 2, height + buttonPadding);
+ noButton->setPosition(yesButton->getX() + yesButton->getWidth()
+ + (2 * mPadding), height + buttonPadding);
+ if (ignoreButton)
+ {
+ ignoreButton->setPosition(noButton->getX() + noButton->getWidth()
+ + (2 * mPadding), height + buttonPadding);
+ }
+
+ add(mTextBox);
+ add(yesButton);
+ add(noButton);
+
+ if (ignore && ignoreButton)
+ add(ignoreButton);
+
+ if (getParent())
+ {
+ center();
+ getParent()->moveToTop(this);
+ }
+ setVisible(true);
+ yesButton->requestFocus();
+ soundManager.playGuiSound(soundEvent);
+}
+
+void ConfirmDialog::action(const gcn::ActionEvent &event)
+{
+ setActionEventId(event.getId());
+ distributeActionEvent();
+ scheduleDelete();
+}
diff --git a/src/gui/windows/confirmdialog.h b/src/gui/windows/confirmdialog.h
new file mode 100644
index 000000000..76e3c2d18
--- /dev/null
+++ b/src/gui/windows/confirmdialog.h
@@ -0,0 +1,65 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_CONFIRMDIALOG_H
+#define GUI_CONFIRMDIALOG_H
+
+#include "localconsts.h"
+
+#include "soundconsts.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,
+ const std::string &soundEvent = SOUND_REQUEST,
+ const bool ignore = false, const bool modal = false,
+ Window *const parent = nullptr);
+
+ A_DELETE_COPY(ConfirmDialog)
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ private:
+ TextBox *mTextBox;
+};
+
+#endif // GUI_CONFIRMDIALOG_H
diff --git a/src/gui/windows/connectiondialog.cpp b/src/gui/windows/connectiondialog.cpp
new file mode 100644
index 000000000..cb03b5bbc
--- /dev/null
+++ b/src/gui/windows/connectiondialog.cpp
@@ -0,0 +1,71 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/connectiondialog.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"
+
+#include "debug.h"
+
+ConnectionDialog::ConnectionDialog(const std::string &text,
+ const State cancelState):
+ Window(""),
+ gcn::ActionListener(),
+ mCancelState(cancelState)
+{
+ setTitleBarHeight(0);
+ setMovable(false);
+ setMinWidth(0);
+
+ ProgressIndicator *const progressIndicator = new ProgressIndicator;
+ Label *const label = new Label(this, text);
+ Button *const cancelButton = new Button(
+ // TRANSLATORS: connection dialog button
+ this, _("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)
+{
+ BLOCK_START("ConnectionDialog::draw")
+ // Don't draw the window background, only draw the children
+ drawChildren(graphics);
+ BLOCK_END("ConnectionDialog::draw")
+}
diff --git a/src/gui/windows/connectiondialog.h b/src/gui/windows/connectiondialog.h
new file mode 100644
index 000000000..21f29712c
--- /dev/null
+++ b/src/gui/windows/connectiondialog.h
@@ -0,0 +1,64 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_CONNECTIONDIALOG_H
+#define GUI_CONNECTIONDIALOG_H
+
+#include "client.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+/**
+ * The connection dialog.
+ *
+ * \ingroup Interface
+ */
+class ConnectionDialog final : public Window, private 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, const State cancelState);
+
+ A_DELETE_COPY(ConnectionDialog)
+
+ /**
+ * Called when the user presses Cancel. Restores the global state to
+ * the previous one.
+ */
+ void action(const gcn::ActionEvent &) override;
+
+ void draw(gcn::Graphics *graphics) override;
+
+ private:
+ State mCancelState;
+};
+
+#endif // GUI_CONNECTIONDIALOG_H
diff --git a/src/gui/windows/debugwindow.cpp b/src/gui/windows/debugwindow.cpp
new file mode 100644
index 000000000..0104af792
--- /dev/null
+++ b/src/gui/windows/debugwindow.cpp
@@ -0,0 +1,537 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/debugwindow.h"
+
+#include "client.h"
+#include "game.h"
+#include "main.h"
+
+#include "being/localplayer.h"
+
+#include "particle/particle.h"
+
+#include "gui/viewport.h"
+
+#include "gui/windows/setup.h"
+
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/layouthelper.h"
+
+#include "resources/imagehelper.h"
+
+#include "net/packetcounters.h"
+
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+DebugWindow::DebugWindow() :
+ // TRANSLATORS: debug window name
+ Window(_("Debug"), false, nullptr, "debug.xml"),
+ mTabs(new TabbedArea(this)),
+ mMapWidget(new MapDebugTab(this)),
+ mTargetWidget(new TargetDebugTab(this)),
+ mNetWidget(new NetDebugTab(this))
+{
+ setWindowName("Debug");
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ setResizable(true);
+ setCloseButton(true);
+ setSaveVisible(true);
+ setStickyButtonLock(true);
+
+ setDefaultSize(400, 300, ImageRect::CENTER);
+
+ // TRANSLATORS: debug window tab
+ mTabs->addTab(std::string(_("Map")), mMapWidget);
+ // TRANSLATORS: debug window tab
+ mTabs->addTab(std::string(_("Target")), mTargetWidget);
+ // TRANSLATORS: debug window tab
+ mTabs->addTab(std::string(_("Net")), mNetWidget);
+
+ mTabs->setDimension(gcn::Rectangle(0, 0, 600, 300));
+ add(mTabs);
+
+ const int w = mDimension.width;
+ const int h = mDimension.height;
+ mMapWidget->resize(w, h);
+ mTargetWidget->resize(w, h);
+ mNetWidget->resize(w, h);
+ loadWindowState();
+ enableVisibleSound(true);
+}
+
+DebugWindow::~DebugWindow()
+{
+ delete mMapWidget;
+ mMapWidget = nullptr;
+ delete mTargetWidget;
+ mTargetWidget = nullptr;
+ delete mNetWidget;
+ mNetWidget = nullptr;
+}
+
+void DebugWindow::slowLogic()
+{
+ BLOCK_START("DebugWindow::slowLogic")
+ if (!isWindowVisible() || !mTabs)
+ {
+ BLOCK_END("DebugWindow::slowLogic")
+ return;
+ }
+
+ switch (mTabs->getSelectedTabIndex())
+ {
+ default:
+ case 0:
+ mMapWidget->logic();
+ break;
+ case 1:
+ mTargetWidget->logic();
+ break;
+ case 2:
+ mNetWidget->logic();
+ break;
+ }
+
+ if (player_node)
+ player_node->tryPingRequest();
+ BLOCK_END("DebugWindow::slowLogic")
+}
+
+void DebugWindow::draw(gcn::Graphics *g)
+{
+ BLOCK_START("DebugWindow::draw")
+ Window::draw(g);
+
+ if (player_node)
+ {
+ const Being *const target = player_node->getTarget();
+ if (target)
+ {
+ Graphics *const g2 = static_cast<Graphics*>(g);
+ target->draw(g2, -target->getPixelX() + 16 + mDimension.width / 2,
+ -target->getPixelY() + 32 + mDimension.height / 2);
+ }
+ }
+ BLOCK_END("DebugWindow::draw")
+}
+
+void DebugWindow::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+
+ mTabs->setDimension(gcn::Rectangle(0, 0,
+ mDimension.width, mDimension.height));
+}
+
+#ifdef USE_PROFILER
+void DebugWindow::logicChildren()
+{
+ BLOCK_START("DebugWindow::logicChildren")
+ BasicContainer::logicChildren();
+ BLOCK_END("DebugWindow::logicChildren")
+}
+#endif
+
+MapDebugTab::MapDebugTab(const Widget2 *const widget) :
+ DebugTab(widget),
+ // TRANSLATORS: debug window label
+ mMusicFileLabel(new Label(this, strprintf(_("Music:")))),
+ // TRANSLATORS: debug window label
+ mMapLabel(new Label(this, strprintf(_("Map:")))),
+ // TRANSLATORS: debug window label
+ mMinimapLabel(new Label(this, strprintf(_("Minimap:")))),
+ mTileMouseLabel(new Label(this, strprintf("%s (%d, %d)",
+ // TRANSLATORS: debug window label
+ _("Cursor:"), 0, 0))),
+ mParticleCountLabel(new Label(this, strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Particle count:"), 88888))),
+ mMapActorCountLabel(new Label(this, strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Map actors count:"), 88888))),
+ // TRANSLATORS: debug window label
+ mXYLabel(new Label(this, strprintf("%s (?,?)", _("Player Position:")))),
+ mTexturesLabel(nullptr),
+ mUpdateTime(0),
+#ifdef DEBUG_DRAW_CALLS
+ mDrawCallsLabel(new Label(this, strprintf("%s %s",
+ // TRANSLATORS: debug window label
+ _("Draw calls:"), "?"))),
+#endif
+#ifdef DEBUG_BIND_TEXTURE
+ mBindsLabel(new Label(this, strprintf("%s %s",
+ // TRANSLATORS: debug window label
+ _("Texture binds:"), "?"))),
+#endif
+ // TRANSLATORS: debug window label, frames per second
+ mFPSLabel(new Label(this, strprintf(_("%d FPS"), 0))),
+ // TRANSLATORS: debug window label, logic per second
+ mLPSLabel(new Label(this, strprintf(_("%d LPS"), 0))),
+ mFPSText()
+{
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+#ifdef USE_OPENGL
+ switch (imageHelper->useOpenGL())
+ {
+ case RENDER_SOFTWARE:
+ // TRANSLATORS: debug window label
+ mFPSText = _("%d FPS (Software)");
+ break;
+ case RENDER_NORMAL_OPENGL:
+ case RENDER_NULL:
+ case RENDER_LAST:
+ default:
+ // TRANSLATORS: debug window label
+ mFPSText = _("%d FPS (normal OpenGL)");
+ break;
+ case RENDER_SAFE_OPENGL:
+ // TRANSLATORS: debug window label
+ mFPSText = _("%d FPS (safe OpenGL)");
+ break;
+ case RENDER_GLES_OPENGL:
+ // TRANSLATORS: debug window label
+ mFPSText = _("%d FPS (mobile OpenGL)");
+ break;
+ case RENDER_SDL2_DEFAULT:
+ // TRANSLATORS: debug window label
+ mFPSText = _("%d FPS (SDL2 default)");
+ break;
+ };
+#else
+ // TRANSLATORS: debug window label
+ mFPSText = _("%d FPS (Software)");
+#endif
+
+ place(0, 0, mFPSLabel, 2);
+ place(0, 1, mLPSLabel, 2);
+ place(0, 2, mMusicFileLabel, 2);
+ place(0, 3, mMapLabel, 2);
+ place(0, 4, mMinimapLabel, 2);
+ place(0, 5, mXYLabel, 2);
+ place(0, 6, mTileMouseLabel, 2);
+ place(0, 7, mParticleCountLabel, 2);
+ place(0, 8, mMapActorCountLabel, 2);
+#ifdef USE_OPENGL
+#if defined (DEBUG_OPENGL_LEAKS) || defined(DEBUG_DRAW_CALLS) \
+ || defined(DEBUG_BIND_TEXTURE)
+ int n = 9;
+#endif
+#ifdef DEBUG_OPENGL_LEAKS
+ mTexturesLabel = new Label(this, strprintf("%s %s",
+ // TRANSLATORS: debug window label
+ _("Textures count:"), "?"));
+ place(0, n, mTexturesLabel, 2);
+ n ++;
+#endif
+#ifdef DEBUG_DRAW_CALLS
+ place(0, n, mDrawCallsLabel, 2);
+ n ++;
+#endif
+#ifdef DEBUG_BIND_TEXTURE
+ place(0, n, mBindsLabel, 2);
+#endif
+#endif
+ place.getCell().matchColWidth(0, 0);
+ place = h.getPlacer(0, 1);
+ setDimension(gcn::Rectangle(0, 0, 600, 300));
+}
+
+void MapDebugTab::logic()
+{
+ if (player_node)
+ {
+ // TRANSLATORS: debug window label
+ mXYLabel->setCaption(strprintf("%s (%d, %d)", _("Player Position:"),
+ player_node->getTileX(), player_node->getTileY()));
+ }
+ else
+ {
+ // TRANSLATORS: debug window label
+ mXYLabel->setCaption(strprintf("%s (?, ?)", _("Player Position:")));
+ }
+
+ const Map *const map = Game::instance()->getCurrentMap();
+ if (map && viewport)
+ {
+ // Get the current mouse position
+ const int mouseTileX = (viewport->getMouseX() + viewport->getCameraX())
+ / map->getTileWidth();
+ const int mouseTileY = (viewport->getMouseY() + viewport->getCameraY())
+ / map->getTileHeight();
+ mTileMouseLabel->setCaption(strprintf("%s (%d, %d)",
+ // TRANSLATORS: debug window label
+ _("Cursor:"), mouseTileX, mouseTileY));
+
+ // TRANSLATORS: debug window label
+ mMusicFileLabel->setCaption(strprintf("%s %s", _("Music:"),
+ map->getProperty("music").c_str()));
+ // TRANSLATORS: debug window label
+ mMinimapLabel->setCaption(strprintf("%s %s", _("Minimap:"),
+ map->getProperty("minimap").c_str()));
+ // TRANSLATORS: debug window label
+ mMapLabel->setCaption(strprintf("%s %s", _("Map:"),
+ map->getProperty("_realfilename").c_str()));
+
+
+ if (mUpdateTime != cur_time)
+ {
+ mUpdateTime = cur_time;
+ // TRANSLATORS: debug window label
+ mParticleCountLabel->setCaption(strprintf(_("Particle count: %d"),
+ Particle::particleCount));
+
+ mMapActorCountLabel->setCaption(
+ // TRANSLATORS: debug window label
+ strprintf("%s %d", _("Map actors count:"),
+ map->getActorsCount()));
+#ifdef USE_OPENGL
+#ifdef DEBUG_OPENGL_LEAKS
+ mTexturesLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Textures count:"), textures_count));
+#endif
+#ifdef DEBUG_DRAW_CALLS
+ if (mainGraphics)
+ {
+ mDrawCallsLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Draw calls:"), mainGraphics->getDrawCalls()));
+ }
+#endif
+#ifdef DEBUG_BIND_TEXTURE
+ if (mainGraphics)
+ {
+ mBindsLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Texture binds:"), mainGraphics->getBinds()));
+ }
+#endif
+#endif
+ }
+ }
+ else
+ {
+ // TRANSLATORS: debug window label
+ mTileMouseLabel->setCaption(strprintf("%s (?, ?)", _("Cursor:")));
+ // TRANSLATORS: debug window label
+ mMusicFileLabel->setCaption(strprintf("%s ?", _("Music:")));
+ // TRANSLATORS: debug window label
+ mMinimapLabel->setCaption(strprintf("%s ?", _("Minimap:")));
+ // TRANSLATORS: debug window label
+ mMapLabel->setCaption(strprintf("%s ?", _("Map:")));
+
+ mMapActorCountLabel->setCaption(
+ // TRANSLATORS: debug window label
+ strprintf("%s ?", _("Map actors count:")));
+ }
+
+ mMapActorCountLabel->adjustSize();
+ mParticleCountLabel->adjustSize();
+
+ mFPSLabel->setCaption(strprintf(mFPSText.c_str(), fps));
+ // TRANSLATORS: debug window label, logic per second
+ mLPSLabel->setCaption(strprintf(_("%d LPS"), lps));
+}
+
+TargetDebugTab::TargetDebugTab(const Widget2 *const widget) :
+ DebugTab(widget),
+ // TRANSLATORS: debug window label
+ mTargetLabel(new Label(this, strprintf("%s ?", _("Target:")))),
+ // TRANSLATORS: debug window label
+ mTargetIdLabel(new Label(this, strprintf("%s ? ", _("Target Id:")))),
+ mTargetTypeLabel(new Label(this, strprintf(
+ // TRANSLATORS: debug window label
+ "%s ? ", _("Target type:")))),
+ // TRANSLATORS: debug window label
+ mTargetLevelLabel(new Label(this, strprintf("%s ?", _("Target level:")))),
+ // TRANSLATORS: debug window label
+ mTargetRaceLabel(new Label(this, strprintf("%s ?", _("Target race:")))),
+ // TRANSLATORS: debug window label
+ mTargetPartyLabel(new Label(this, strprintf("%s ?", _("Target party:")))),
+ // TRANSLATORS: debug window label
+ mTargetGuildLabel(new Label(this, strprintf("%s ?", _("Target guild:")))),
+ // TRANSLATORS: debug window label
+ mAttackDelayLabel(new Label(this, strprintf("%s ?", _("Attack delay:")))),
+ // TRANSLATORS: debug window label
+ mMinHitLabel(new Label(this, strprintf("%s ?", _("Minimal hit:")))),
+ // TRANSLATORS: debug window label
+ mMaxHitLabel(new Label(this, strprintf("%s ?", _("Maximum hit:")))),
+ // TRANSLATORS: debug window label
+ mCriticalHitLabel(new Label(this, strprintf("%s ?", _("Critical hit:"))))
+{
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mTargetLabel, 2);
+ place(0, 1, mTargetIdLabel, 2);
+ place(0, 2, mTargetTypeLabel, 2);
+ place(0, 3, mTargetLevelLabel, 2);
+ place(0, 4, mTargetRaceLabel, 2);
+ place(0, 5, mAttackDelayLabel, 2);
+ place(0, 6, mTargetPartyLabel, 2);
+ place(0, 7, mTargetGuildLabel, 2);
+ place(0, 8, mMinHitLabel, 2);
+ place(0, 9, mMaxHitLabel, 2);
+ place(0, 10, mCriticalHitLabel, 2);
+
+ place.getCell().matchColWidth(0, 0);
+ place = h.getPlacer(0, 1);
+ setDimension(gcn::Rectangle(0, 0, 600, 300));
+}
+
+void TargetDebugTab::logic()
+{
+ if (player_node && player_node->getTarget())
+ {
+ const Being *const target = player_node->getTarget();
+
+ // TRANSLATORS: debug window label
+ mTargetLabel->setCaption(strprintf("%s %s (%d, %d)", _("Target:"),
+ target->getName().c_str(), target->getTileX(),
+ target->getTileY()));
+
+ mTargetIdLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Target Id:"), target->getId()));
+ mTargetTypeLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Target type:"), target->getSubType()));
+ if (target->getLevel())
+ {
+ mTargetLevelLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Target Level:"), target->getLevel()));
+ }
+ else
+ {
+ mTargetLevelLabel->setCaption(strprintf("%s ?",
+ // TRANSLATORS: debug window label
+ _("Target Level:")));
+ }
+
+ mTargetRaceLabel->setCaption(strprintf("%s %s",
+ // TRANSLATORS: debug window label
+ _("Target race:"), target->getRaceName().c_str()));
+
+ // TRANSLATORS: debug window label
+ mTargetPartyLabel->setCaption(strprintf("%s %s", _("Target Party:"),
+ target->getPartyName().c_str()));
+
+ // TRANSLATORS: debug window label
+ mTargetGuildLabel->setCaption(strprintf("%s %s", _("Target Guild:"),
+ target->getGuildName().c_str()));
+
+ mMinHitLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Minimal hit:"), target->getMinHit()));
+ mMaxHitLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Maximum hit:"), target->getMaxHit()));
+ mCriticalHitLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Critical hit:"), target->getCriticalHit()));
+
+ const int delay = target->getAttackDelay();
+ if (delay)
+ {
+ mAttackDelayLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: debug window label
+ _("Attack delay:"), delay));
+ }
+ else
+ {
+ mAttackDelayLabel->setCaption(strprintf(
+ // TRANSLATORS: debug window label
+ "%s ?", _("Attack delay:")));
+ }
+ }
+ else
+ {
+ // TRANSLATORS: debug window label
+ mTargetLabel->setCaption(strprintf("%s ?", _("Target:")));
+ // TRANSLATORS: debug window label
+ mTargetIdLabel->setCaption(strprintf("%s ?", _("Target Id:")));
+ // TRANSLATORS: debug window label
+ mTargetTypeLabel->setCaption(strprintf("%s ?", _("Target type:")));
+ // TRANSLATORS: debug window label
+ mTargetLevelLabel->setCaption(strprintf("%s ?", _("Target Level:")));
+ // TRANSLATORS: debug window label
+ mTargetPartyLabel->setCaption(strprintf("%s ?", _("Target Party:")));
+ // TRANSLATORS: debug window label
+ mTargetGuildLabel->setCaption(strprintf("%s ?", _("Target Guild:")));
+ // TRANSLATORS: debug window label
+ mAttackDelayLabel->setCaption(strprintf("%s ?", _("Attack delay:")));
+ // TRANSLATORS: debug window label
+ mMinHitLabel->setCaption(strprintf("%s ?", _("Minimal hit:")));
+ // TRANSLATORS: debug window label
+ mMaxHitLabel->setCaption(strprintf("%s ?", _("Maximum hit:")));
+ // TRANSLATORS: debug window label
+ mCriticalHitLabel->setCaption(strprintf("%s ?", _("Critical hit:")));
+ }
+
+ mTargetLabel->adjustSize();
+ mTargetIdLabel->adjustSize();
+ mTargetTypeLabel->adjustSize();
+ mTargetLevelLabel->adjustSize();
+ mTargetPartyLabel->adjustSize();
+ mTargetGuildLabel->adjustSize();
+ mAttackDelayLabel->adjustSize();
+}
+
+NetDebugTab::NetDebugTab(const Widget2 *const widget) :
+ DebugTab(widget),
+ mPingLabel(new Label(this, " ")),
+ mInPackets1Label(new Label(this, " ")),
+ mOutPackets1Label(new Label(this, " "))
+{
+ LayoutHelper h(this);
+ ContainerPlacer place = h.getPlacer(0, 0);
+
+ place(0, 0, mPingLabel, 2);
+ place(0, 1, mInPackets1Label, 2);
+ place(0, 2, mOutPackets1Label, 2);
+
+ place.getCell().matchColWidth(0, 0);
+ place = h.getPlacer(0, 1);
+ setDimension(gcn::Rectangle(0, 0, 600, 300));
+}
+
+void NetDebugTab::logic()
+{
+ // TRANSLATORS: debug window label
+ mPingLabel->setCaption(strprintf(_("Ping: %s ms"),
+ player_node->getPingTime().c_str()));
+ // TRANSLATORS: debug window label
+ mInPackets1Label->setCaption(strprintf(_("In: %d bytes/s"),
+ PacketCounters::getInBytes()));
+ // TRANSLATORS: debug window label
+ mOutPackets1Label->setCaption(strprintf(_("Out: %d bytes/s"),
+ PacketCounters::getOutBytes()));
+}
diff --git a/src/gui/windows/debugwindow.h b/src/gui/windows/debugwindow.h
new file mode 100644
index 000000000..ef67df432
--- /dev/null
+++ b/src/gui/windows/debugwindow.h
@@ -0,0 +1,165 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_DEBUGWINDOW_H
+#define GUI_DEBUGWINDOW_H
+
+#include "gui/widgets/container.h"
+#include "gui/widgets/window.h"
+
+class Label;
+class TabbedArea;
+
+class DebugTab : public Container
+{
+ friend class DebugWindow;
+
+ public:
+ A_DELETE_COPY(DebugTab)
+
+ void logic() override = 0;
+
+ void resize(const int x, const int y)
+ { setDimension(gcn::Rectangle(0, 0, x, y)); }
+
+ protected:
+ explicit DebugTab(const Widget2 *const widget) :
+ Container(widget)
+ { }
+};
+
+class MapDebugTab final : public DebugTab
+{
+ friend class DebugWindow;
+
+ public:
+ explicit MapDebugTab(const Widget2 *const widget);
+
+ A_DELETE_COPY(MapDebugTab)
+
+ void logic() override;
+
+ private:
+ Label *mMusicFileLabel;
+ Label *mMapLabel;
+ Label *mMinimapLabel;
+ Label *mTileMouseLabel;
+ Label *mParticleCountLabel;
+ Label *mMapActorCountLabel;
+ Label *mXYLabel;
+ Label *mTexturesLabel;
+ int mUpdateTime;
+#ifdef DEBUG_DRAW_CALLS
+ Label *mDrawCallsLabel;
+#endif
+#ifdef DEBUG_BIND_TEXTURE
+ Label *mBindsLabel;
+#endif
+ Label *mFPSLabel;
+ Label *mLPSLabel;
+ std::string mFPSText;
+};
+
+class TargetDebugTab final : public DebugTab
+{
+ friend class DebugWindow;
+
+ public:
+ explicit TargetDebugTab(const Widget2 *const widget);
+
+ A_DELETE_COPY(TargetDebugTab)
+
+ void logic() override;
+
+ private:
+ Label *mTargetLabel;
+ Label *mTargetIdLabel;
+ Label *mTargetTypeLabel;
+ Label *mTargetLevelLabel;
+ Label *mTargetRaceLabel;
+ Label *mTargetPartyLabel;
+ Label *mTargetGuildLabel;
+ Label *mAttackDelayLabel;
+ Label *mMinHitLabel;
+ Label *mMaxHitLabel;
+ Label *mCriticalHitLabel;
+};
+
+class NetDebugTab final : public DebugTab
+{
+ friend class DebugWindow;
+
+ public:
+ explicit NetDebugTab(const Widget2 *const widget);
+
+ A_DELETE_COPY(NetDebugTab)
+
+ void logic() override;
+
+ private:
+ Label *mPingLabel;
+ Label *mInPackets1Label;
+ Label *mOutPackets1Label;
+};
+
+/**
+ * The debug window.
+ *
+ * \ingroup Interface
+ */
+class DebugWindow final : public Window
+{
+ public:
+ /**
+ * Constructor.
+ */
+ DebugWindow();
+
+ A_DELETE_COPY(DebugWindow)
+
+ ~DebugWindow();
+
+ /**
+ * Logic (updates components' size and infos)
+ */
+ void slowLogic();
+
+ void draw(gcn::Graphics *g) override;
+
+ void setPing(int pingTime);
+
+ void widgetResized(const gcn::Event &event) override;
+
+#ifdef USE_PROFILER
+ void logicChildren();
+#endif
+
+ private:
+ TabbedArea *mTabs;
+ MapDebugTab *mMapWidget;
+ TargetDebugTab *mTargetWidget;
+ NetDebugTab *mNetWidget;
+};
+
+extern DebugWindow *debugWindow;
+
+#endif // GUI_DEBUGWINDOW_H
diff --git a/src/gui/windows/didyouknowwindow.cpp b/src/gui/windows/didyouknowwindow.cpp
new file mode 100644
index 000000000..8c900d119
--- /dev/null
+++ b/src/gui/windows/didyouknowwindow.cpp
@@ -0,0 +1,177 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/didyouknowwindow.h"
+
+#include "configuration.h"
+
+#include "gui/gui.h"
+#include "gui/sdlfont.h"
+
+#include "gui/windows/setup.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "utils/gettext.h"
+#include "utils/process.h"
+
+#include "utils/translation/podict.h"
+#include "utils/translation/translationmanager.h"
+
+#include "debug.h"
+
+static const int minTip = 1;
+static const int maxTip = 18;
+
+DidYouKnowWindow::DidYouKnowWindow() :
+ // TRANSLATORS: did you know window name
+ Window(_("Did You Know?"), false, nullptr, "didyouknow.xml"),
+ gcn::ActionListener(),
+ mBrowserBox(new BrowserBox(this)),
+ mScrollArea(new ScrollArea(mBrowserBox,
+ true, "didyouknow_background.xml")),
+ // TRANSLATORS: did you know window button
+ mButtonPrev(new Button(this, _("< Previous"), "prev", this)),
+ // TRANSLATORS: did you know window button
+ mButtonNext(new Button(this, _("Next >"), "next", this)),
+ // TRANSLATORS: did you know window checkbox
+ mOpenAgainCheckBox(new CheckBox(this, _("Auto open this window"),
+ config.getBoolValue("showDidYouKnow"), this, "openagain"))
+{
+ setMinWidth(300);
+ setMinHeight(220);
+ setContentSize(455, 350);
+ setWindowName("DidYouKnow");
+ setCloseButton(true);
+ setResizable(true);
+ setStickyButtonLock(true);
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+ setDefaultSize(500, 400, ImageRect::CENTER);
+
+ mBrowserBox->setOpaque(false);
+ // TRANSLATORS: did you know window button
+ Button *const okButton = new Button(this, _("Close"), "close", this);
+
+ mBrowserBox->setLinkHandler(this);
+ mBrowserBox->setFont(gui->getHelpFont());
+ mBrowserBox->setProcessVersion(true);
+ mBrowserBox->setEnableImages(true);
+ mBrowserBox->setEnableKeys(true);
+ mBrowserBox->setEnableTabs(true);
+
+ place(0, 0, mScrollArea, 5, 3).setPadding(3);
+ place(0, 3, mOpenAgainCheckBox, 5);
+ place(1, 4, mButtonPrev, 1);
+ place(2, 4, mButtonNext, 1);
+ place(4, 4, okButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ loadWindowState();
+ enableVisibleSound(true);
+ widgetResized(gcn::Event(nullptr));
+}
+
+void DidYouKnowWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "close")
+ {
+ setVisible(false);
+ }
+ else
+ {
+ const unsigned num = config.getIntValue("currentTip");
+ if (eventId == "prev")
+ {
+ loadData(num - 1);
+ }
+ else if (eventId == "next")
+ {
+ loadData(num + 1);
+ }
+ else if (eventId == "openagain")
+ {
+ config.setValue("showDidYouKnow",
+ mOpenAgainCheckBox->isSelected());
+ }
+ }
+}
+
+void DidYouKnowWindow::handleLink(const std::string &link,
+ gcn::MouseEvent *event A_UNUSED)
+{
+ if (strStartWith(link, "http://") || strStartWith(link, "https://"))
+ openBrowser(link);
+}
+
+void DidYouKnowWindow::loadData(int num)
+{
+ mBrowserBox->clearRows();
+ if (!num)
+ {
+ const int curTip = config.getIntValue("currentTip");
+ if (curTip == 1)
+ num = maxTip;
+ else
+ num = curTip + 1;
+ }
+
+ if (num < minTip || num > maxTip)
+ num = minTip;
+
+ config.setValue("currentTip", num);
+
+ loadFile(num);
+
+ mScrollArea->setVerticalScrollAmount(0);
+}
+
+void DidYouKnowWindow::loadFile(const int num)
+{
+ const std::string file = strprintf("tips/%d", num);
+ std::string helpPath = branding.getStringValue("helpPath");
+ if (helpPath.empty())
+ helpPath = paths.getStringValue("help");
+
+ StringVect lines;
+ TranslationManager::translateFile(helpPath.append(file).append(".txt"),
+ translator, lines);
+
+ for (size_t i = 0, sz = lines.size(); i < sz; ++i)
+ mBrowserBox->addRow(lines[i]);
+}
+
+void DidYouKnowWindow::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (visible || isWindowVisible())
+ loadData();
+}
diff --git a/src/gui/windows/didyouknowwindow.h b/src/gui/windows/didyouknowwindow.h
new file mode 100644
index 000000000..49cb07a8b
--- /dev/null
+++ b/src/gui/windows/didyouknowwindow.h
@@ -0,0 +1,78 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_DIDYOUKNOWWINDOW_H
+#define GUI_DIDYOUKNOWWINDOW_H
+
+#include "gui/widgets/linkhandler.h"
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Button;
+class BrowserBox;
+class CheckBox;
+class ScrollArea;
+
+/**
+ * The help dialog.
+ */
+class DidYouKnowWindow final : public Window,
+ public LinkHandler,
+ public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ DidYouKnowWindow();
+
+ A_DELETE_COPY(DidYouKnowWindow)
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Handles link action.
+ */
+ void handleLink(const std::string &link,
+ gcn::MouseEvent *event) override;
+
+ void loadData(int num = 0);
+
+ void setVisible(bool visible);
+
+ private:
+ void loadFile(const int num);
+
+ BrowserBox *mBrowserBox;
+ ScrollArea *mScrollArea;
+ Button *mButtonPrev;
+ Button *mButtonNext;
+ CheckBox *mOpenAgainCheckBox;
+};
+
+extern DidYouKnowWindow *didYouKnowWindow;
+
+#endif // GUI_DIDYOUKNOWWINDOW_H
diff --git a/src/gui/windows/editdialog.cpp b/src/gui/windows/editdialog.cpp
new file mode 100644
index 000000000..acd5d9f72
--- /dev/null
+++ b/src/gui/windows/editdialog.cpp
@@ -0,0 +1,72 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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/windows/editdialog.h"
+
+#include "gui/widgets/button.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include "debug.h"
+
+EditDialog::EditDialog(const std::string &title, const std::string &msg,
+ const std::string &eventOk, const int width,
+ Window *const parent, const bool modal):
+ Window(title, modal, parent, "edit.xml"),
+ gcn::ActionListener(),
+ mEventOk(eventOk),
+ mTextField(new TextField(this))
+{
+ mTextField->setText(msg);
+ // TRANSLATORS: edit dialog label
+ Button *const okButton = new Button(this, _("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 + getOption("buttonPadding", 8));
+
+ add(mTextField);
+ add(okButton);
+
+ center();
+ setVisible(true);
+ okButton->requestFocus();
+}
+
+void EditDialog::action(const gcn::ActionEvent &event)
+{
+ // Proxy button events to our listeners
+ FOR_EACH (ActionListenerIterator, i, mActionListeners)
+ (*i)->action(event);
+
+ if (event.getId() == mEventOk)
+ scheduleDelete();
+}
diff --git a/src/gui/windows/editdialog.h b/src/gui/windows/editdialog.h
new file mode 100644
index 000000000..6d3d191bc
--- /dev/null
+++ b/src/gui/windows/editdialog.h
@@ -0,0 +1,69 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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_EDITDIALOG_H
+#define GUI_EDITDIALOG_H
+
+#include "localconsts.h"
+
+#include "gui/widgets/window.h"
+#include "gui/widgets/textfield.h"
+
+#include <guichan/actionlistener.hpp>
+
+#define ACTION_EDIT_OK "edit ok"
+
+/**
+ * An 'Ok' button dialog.
+ *
+ * \ingroup GUI
+ */
+class EditDialog final : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ EditDialog(const std::string &title, const std::string &msg,
+ const std::string &eventOk = ACTION_EDIT_OK,
+ const int width = 300, Window *const parent = nullptr,
+ const bool modal = true);
+
+ A_DELETE_COPY(EditDialog)
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ std::string getMsg() const A_WARN_UNUSED
+ { return mTextField->getText(); }
+
+ private:
+ std::string mEventOk;
+
+ TextField *mTextField;
+};
+
+#endif // GUI_EDITDIALOG_H
diff --git a/src/gui/windows/editserverdialog.cpp b/src/gui/windows/editserverdialog.cpp
new file mode 100644
index 000000000..2a6814525
--- /dev/null
+++ b/src/gui/windows/editserverdialog.cpp
@@ -0,0 +1,300 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2011-2012 The Mana Developers
+ * Copyright (C) 2012-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/editserverdialog.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/windows/okdialog.h"
+#include "gui/windows/serverdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/textfield.h"
+
+#include "utils/gettext.h"
+
+std::string TypeListModel::getElementAt(int elementIndex)
+{
+ if (elementIndex == 0)
+ return "TmwAthena";
+ else if (elementIndex == 1)
+ return "Evol";
+#ifdef EATHENA_SUPPORT
+ else if (elementIndex == 2)
+ return "eAthena";
+#ifdef MANASERV_SUPPORT
+ else if (elementIndex == 3)
+ return "ManaServ";
+#endif
+#else
+#ifdef MANASERV_SUPPORT
+ else if (elementIndex == 2)
+ return "ManaServ";
+#endif
+#endif
+ else
+ return "Unknown";
+}
+
+EditServerDialog::EditServerDialog(ServerDialog *const parent,
+ ServerInfo server,
+ const int index) :
+ // TRANSLATORS: edit server dialog name
+ Window(_("Edit Server"), true, parent),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mServerAddressField(new TextField(this, std::string())),
+ mPortField(new TextField(this, std::string())),
+ mNameField(new TextField(this, std::string())),
+ mDescriptionField(new TextField(this, std::string())),
+ mOnlineListUrlField(new TextField(this, std::string())),
+ // TRANSLATORS: edit server dialog button
+ mConnectButton(new Button(this, _("Connect"), "connect", this)),
+ // TRANSLATORS: edit server dialog button
+ mOkButton(new Button(this, _("OK"), "addServer", this)),
+ // TRANSLATORS: edit server dialog button
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ mTypeListModel(new TypeListModel),
+ mTypeField(new DropDown(this, mTypeListModel, false, true)),
+ mServerDialog(parent),
+ mServer(server),
+ mIndex(index)
+{
+ setWindowName("EditServerDialog");
+
+ // TRANSLATORS: edit server dialog label
+ Label *const nameLabel = new Label(this, _("Name:"));
+ // TRANSLATORS: edit server dialog label
+ Label *const serverAdressLabel = new Label(this, _("Address:"));
+ // TRANSLATORS: edit server dialog label
+ Label *const portLabel = new Label(this, _("Port:"));
+ // TRANSLATORS: edit server dialog label
+ Label *const typeLabel = new Label(this, _("Server type:"));
+ // TRANSLATORS: edit server dialog label
+ Label *const descriptionLabel = new Label(this, _("Description:"));
+ // TRANSLATORS: edit server dialog label
+ Label *const onlineListUrlLabel = new Label(this, _("Online list url:"));
+ mPortField->setNumeric(true);
+ mPortField->setRange(1, 65535);
+
+ mTypeField->setSelected(0); // TmwAthena by default
+
+ mServerAddressField->addActionListener(this);
+ mPortField->addActionListener(this);
+
+ place(0, 0, nameLabel);
+ place(1, 0, mNameField, 4).setPadding(3);
+ place(0, 1, serverAdressLabel);
+ place(1, 1, mServerAddressField, 4).setPadding(3);
+ place(0, 2, portLabel);
+ place(1, 2, mPortField, 4).setPadding(3);
+ place(0, 3, typeLabel);
+ place(1, 3, mTypeField).setPadding(3);
+ place(0, 4, descriptionLabel);
+ place(1, 4, mDescriptionField, 4).setPadding(3);
+ place(0, 5, onlineListUrlLabel);
+ place(1, 5, mOnlineListUrlField, 4).setPadding(3);
+ place(0, 6, mConnectButton);
+ place(4, 6, mOkButton);
+ place(3, 6, mCancelButton);
+
+ // Do this manually instead of calling reflowLayout so we can enforce a
+ // minimum width.
+ int width = 0;
+ int height = 0;
+ getLayout().reflow(width, height);
+ if (width < 300)
+ {
+ width = 300;
+ getLayout().reflow(width, height);
+ }
+ if (height < 120)
+ {
+ height = 120;
+ getLayout().reflow(width, height);
+ }
+
+ setContentSize(width, height);
+
+ setMinWidth(getWidth());
+ setMinHeight(getHeight());
+ setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER);
+
+ setResizable(false);
+ addKeyListener(this);
+
+ loadWindowState();
+
+ mNameField->setText(mServer.name);
+ mDescriptionField->setText(mServer.description);
+ mOnlineListUrlField->setText(mServer.onlineListUrl);
+ mServerAddressField->setText(mServer.hostname);
+ mPortField->setText(toString(mServer.port));
+
+ switch (mServer.type)
+ {
+#ifdef EATHENA_SUPPORT
+ case ServerInfo::EATHENA:
+ mTypeField->setSelected(2);
+ break;
+ case ServerInfo::MANASERV:
+#ifdef MANASERV_SUPPORT
+ mTypeField->setSelected(3);
+ break;
+#endif
+#else
+ case ServerInfo::MANASERV:
+#ifdef MANASERV_SUPPORT
+ mTypeField->setSelected(2);
+ break;
+#endif
+#endif
+ default:
+ case ServerInfo::UNKNOWN:
+ case ServerInfo::TMWATHENA:
+#ifndef EATHENA_SUPPORT
+ case ServerInfo::EATHENA:
+#endif
+ mTypeField->setSelected(0);
+ break;
+ case ServerInfo::EVOL:
+ mTypeField->setSelected(1);
+ break;
+ }
+
+ setLocationRelativeTo(getParentWindow());
+ setVisible(true);
+
+ mNameField->requestFocus();
+}
+
+EditServerDialog::~EditServerDialog()
+{
+ delete mTypeListModel;
+}
+
+void EditServerDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+
+ if (eventId == "ok")
+ {
+ // Give focus back to the server dialog.
+ mServerAddressField->requestFocus();
+ }
+ if (eventId == "addServer" || eventId == "connect")
+ {
+ // Check the given information
+ if (mServerAddressField->getText().empty()
+ || mPortField->getText().empty())
+ {
+ // TRANSLATORS: edit server dialog error header
+ OkDialog *const dlg = new OkDialog(_("Error"),
+ // TRANSLATORS: edit server dialog error message
+ _("Please at least type both the address and the port "
+ "of the server."), DIALOG_ERROR);
+ dlg->addActionListener(this);
+ }
+ else
+ {
+ mCancelButton->setEnabled(false);
+ mOkButton->setEnabled(false);
+
+ mServer.name = mNameField->getText();
+ mServer.description = mDescriptionField->getText();
+ mServer.onlineListUrl = mOnlineListUrlField->getText();
+ mServer.hostname = mServerAddressField->getText();
+ mServer.port = static_cast<int16_t>(atoi(
+ mPortField->getText().c_str()));
+
+ if (mTypeField)
+ {
+ switch (mTypeField->getSelected())
+ {
+ case 0:
+ mServer.type = ServerInfo::TMWATHENA;
+ break;
+ case 1:
+ mServer.type = ServerInfo::EVOL;
+ break;
+#ifdef EATHENA_SUPPORT
+ case 2:
+ mServer.type = ServerInfo::EATHENA;
+ break;
+#ifdef MANASERV_SUPPORT
+ case 3:
+ mServer.type = ServerInfo::MANASERV;
+ break;
+#endif
+#else
+#ifdef MANASERV_SUPPORT
+ case 2:
+ mServer.type = ServerInfo::MANASERV;
+ break;
+#endif
+#endif
+ default:
+ mServer.type = ServerInfo::UNKNOWN;
+ }
+ }
+ else
+ {
+ mServer.type = ServerInfo::TMWATHENA;
+ }
+
+ // Tell the server has to be saved
+ mServer.save = true;
+
+ // Add server
+ mServerDialog->updateServer(mServer, mIndex);
+ if (eventId == "connect")
+ mServerDialog->connectToSelectedServer();
+ scheduleDelete();
+ }
+ }
+ else if (eventId == "cancel")
+ {
+ scheduleDelete();
+ }
+}
+
+void EditServerDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ if (keyEvent.isConsumed())
+ return;
+
+ const int actionId = static_cast<KeyEvent*>(
+ &keyEvent)->getActionId();
+
+ if (actionId == static_cast<int>(Input::KEY_GUI_CANCEL))
+ {
+ scheduleDelete();
+ }
+ else if (actionId == static_cast<int>(Input::KEY_GUI_SELECT)
+ || actionId == static_cast<int>(Input::KEY_GUI_SELECT2))
+ {
+ action(gcn::ActionEvent(nullptr, mOkButton->getActionEventId()));
+ }
+}
diff --git a/src/gui/windows/editserverdialog.h b/src/gui/windows/editserverdialog.h
new file mode 100644
index 000000000..531bb009a
--- /dev/null
+++ b/src/gui/windows/editserverdialog.h
@@ -0,0 +1,113 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2011-2012 The Mana Developers
+ * Copyright (C) 2012-2013 The ManaPlus 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_EDITSERVERDIALOG_H
+#define GUI_EDITSERVERDIALOG_H
+
+class Button;
+class TextField;
+class DropDown;
+class ServerDialog;
+
+#include "gui/widgets/window.h"
+
+#include "net/serverinfo.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/listmodel.hpp>
+
+/**
+ * Server Type List Model
+ */
+class TypeListModel : public gcn::ListModel
+{
+ public:
+ TypeListModel()
+ { }
+
+ /**
+ * Used to get number of line in the list
+ */
+ int getNumberOfElements() override A_WARN_UNUSED
+#ifdef EATHENA_SUPPORT
+#ifdef MANASERV_SUPPORT
+ { return 4; }
+#else
+ { return 3; }
+#endif
+#else
+#ifdef MANASERV_SUPPORT
+ { return 3; }
+#else
+ { return 2; }
+#endif
+#endif
+
+ /**
+ * Used to get an element from the list
+ */
+ std::string getElementAt(int elementIndex) override A_WARN_UNUSED;
+};
+
+/**
+ * The custom server addition dialog.
+ *
+ * \ingroup Interface
+ */
+class EditServerDialog final : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ EditServerDialog(ServerDialog *const parent, ServerInfo server,
+ const int index);
+
+ A_DELETE_COPY(EditServerDialog)
+
+ ~EditServerDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ private:
+ TextField *mServerAddressField;
+ TextField *mPortField;
+ TextField *mNameField;
+ TextField *mDescriptionField;
+ TextField *mOnlineListUrlField;
+ Button *mConnectButton;
+ Button *mOkButton;
+ Button *mCancelButton;
+
+ TypeListModel *mTypeListModel;
+ DropDown *mTypeField;
+
+ ServerDialog *mServerDialog;
+ ServerInfo mServer;
+ int mIndex;
+};
+
+#endif // GUI_EDITSERVERDIALOG_H
diff --git a/src/gui/windows/emotewindow.cpp b/src/gui/windows/emotewindow.cpp
new file mode 100644
index 000000000..cdff2d31d
--- /dev/null
+++ b/src/gui/windows/emotewindow.cpp
@@ -0,0 +1,229 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/emotewindow.h"
+
+#include "gui/widgets/colormodel.h"
+#include "gui/widgets/colorpage.h"
+#include "gui/widgets/emotepage.h"
+#include "gui/widgets/namesmodel.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/tabbedarea.h"
+
+#include "utils/gettext.h"
+
+#include "resources/image.h"
+#include "resources/imageset.h"
+
+#include "debug.h"
+
+static const int fontSizeListSize = 2;
+
+static const char *const fontSizeList[] =
+{
+ // TRANSLATORS: font size
+ N_("Normal font"),
+ // TRANSLATORS: font size
+ N_("Bold font"),
+};
+
+EmoteWindow::EmoteWindow() :
+ // TRANSLATORS: emotes window name
+ Window(_("Emotes"), false, nullptr, "emotes.xml"),
+ mTabs(new TabbedArea(this)),
+ mEmotePage(new EmotePage(this)),
+ mColorModel(ColorModel::createDefault(this)),
+ mColorPage(new ColorPage(this, mColorModel, "colorpage.xml")),
+ mScrollColorPage(new ScrollArea(mColorPage, false, "emotepage.xml")),
+ mFontModel(new NamesModel),
+ mFontPage(new ListBox(this, mFontModel, "")),
+ mScrollFontPage(new ScrollArea(mFontPage, false, "fontpage.xml")),
+ mImageSet(Theme::getImageSetFromThemeXml("emotetabs.xml", "", 17, 16))
+{
+ setShowTitle(false);
+ setResizable(true);
+
+ addMouseListener(this);
+ const int pad2 = mPadding * 2;
+ const int width = 200;
+ const int height = 150;
+ setWidth(width + pad2);
+ setHeight(height + pad2);
+ add(mTabs);
+ mTabs->setPosition(mPadding, mPadding);
+ mTabs->setWidth(width);
+ mTabs->setHeight(height);
+ center();
+
+ setTitleBarHeight(getPadding() + getTitlePadding());
+ mScrollColorPage->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
+ mScrollColorPage->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ mScrollFontPage->setVerticalScrollPolicy(ScrollArea::SHOW_NEVER);
+ mScrollFontPage->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+
+ mFontModel->fillFromArray(&fontSizeList[0], fontSizeListSize);
+ mFontPage->setCenter(true);
+
+ if (mImageSet && mImageSet->size() >= 3)
+ {
+ for (int f = 0; f < 3; f ++)
+ {
+ Image *const image = mImageSet->get(f);
+ if (image)
+ image->incRef();
+ }
+
+ mTabs->addTab(mImageSet->get(0), mEmotePage);
+ mTabs->addTab(mImageSet->get(2), mScrollColorPage);
+ mTabs->addTab(mImageSet->get(1), mScrollFontPage);
+ }
+ else
+ {
+ // TRANSLATORS: emotes tab name
+ mTabs->addTab(_("Emotes"), mEmotePage);
+ // TRANSLATORS: emotes tab name
+ mTabs->addTab(_("Colors"), mScrollColorPage);
+ // TRANSLATORS: emotes tab name
+ mTabs->addTab(_("Fonts"), mScrollFontPage);
+ }
+
+ mEmotePage->setActionEventId("emote");
+ mColorPage->setActionEventId("color");
+ mFontPage->setActionEventId("font");
+}
+
+EmoteWindow::~EmoteWindow()
+{
+ mTabs->removeAll(false);
+ mTabs->removeTab(mTabs->getTabByIndex(0));
+ delete mEmotePage;
+ mEmotePage = nullptr;
+ delete mColorPage;
+ mColorPage = nullptr;
+ delete mColorModel;
+ mColorModel = nullptr;
+ delete mScrollColorPage;
+ mScrollColorPage = nullptr;
+ delete mFontPage;
+ mFontPage = nullptr;
+ delete mFontModel;
+ mFontModel = nullptr;
+ delete mScrollFontPage;
+ mScrollFontPage = nullptr;
+ if (mImageSet)
+ {
+ mImageSet->decRef();
+ mImageSet = nullptr;
+ }
+}
+
+void EmoteWindow::show()
+{
+ setVisible(true);
+}
+
+void EmoteWindow::hide()
+{
+ setVisible(false);
+}
+
+std::string EmoteWindow::getSelectedEmote() const
+{
+ const int index = mEmotePage->getSelectedIndex();
+ if (index < 0)
+ return std::string();
+
+ char chr[2];
+ chr[0] = '0' + index;
+ chr[1] = 0;
+ return std::string("%%").append(&chr[0]);
+}
+
+void EmoteWindow::clearEmote()
+{
+ const int index = mEmotePage->getSelectedIndex();
+ mEmotePage->resetAction();
+ if (index >= 0)
+ setVisible(false);
+}
+
+std::string EmoteWindow::getSelectedColor() const
+{
+ const int index = mColorPage->getSelected();
+ if (index < 0)
+ return std::string();
+
+ char chr[2];
+ chr[0] = '0' + index;
+ chr[1] = 0;
+ return std::string("##").append(&chr[0]);
+}
+
+void EmoteWindow::clearColor()
+{
+ mColorPage->resetAction();
+ setVisible(false);
+}
+
+std::string EmoteWindow::getSelectedFont() const
+{
+ const int index = mFontPage->getSelected();
+ if (index < 0)
+ return std::string();
+
+ if (!index)
+ return "##b";
+ else
+ return "##B";
+}
+
+void EmoteWindow::clearFont()
+{
+ mFontPage->setSelected(-1);
+ setVisible(false);
+}
+
+void EmoteWindow::addListeners(gcn::ActionListener *const listener)
+{
+ mEmotePage->addActionListener(listener);
+ mColorPage->addActionListener(listener);
+ mFontPage->addActionListener(listener);
+}
+
+void EmoteWindow::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+ const int pad2 = mPadding * 2;
+ const int width = mDimension.width;
+ const int height = mDimension.height;
+
+ mTabs->setSize(width - pad2, height - pad2);
+ mTabs->adjustWidget(mEmotePage);
+ mTabs->adjustWidget(mScrollColorPage);
+ mColorPage->setSize(mScrollColorPage->getWidth(),
+ mScrollColorPage->getHeight());
+ mEmotePage->widgetResized(event);
+}
+
+void EmoteWindow::widgetMoved(const gcn::Event &event)
+{
+ Window::widgetMoved(event);
+ mEmotePage->widgetResized(event);
+}
diff --git a/src/gui/windows/emotewindow.h b/src/gui/windows/emotewindow.h
new file mode 100644
index 000000000..b65c0c13c
--- /dev/null
+++ b/src/gui/windows/emotewindow.h
@@ -0,0 +1,80 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_EMOTEWINDOW_H
+#define GUI_EMOTEWINDOW_H
+
+#include "gui/widgets/window.h"
+
+class ColorModel;
+class ColorPage;
+class EmotePage;
+class ImageSet;
+class ListBox;
+class NamesModel;
+class ScrollArea;
+class TabbedArea;
+
+class EmoteWindow final : public Window
+{
+ public:
+ EmoteWindow();
+
+ A_DELETE_COPY(EmoteWindow)
+
+ ~EmoteWindow();
+
+ void show();
+
+ void hide();
+
+ std::string getSelectedEmote() const;
+
+ void clearEmote();
+
+ std::string getSelectedColor() const;
+
+ void clearColor();
+
+ std::string getSelectedFont() const;
+
+ void clearFont();
+
+ void addListeners(gcn::ActionListener *const listener);
+
+ void widgetResized(const gcn::Event &event) override;
+
+ void widgetMoved(const gcn::Event &event) override;
+
+ private:
+ TabbedArea *mTabs;
+ EmotePage *mEmotePage;
+ ColorModel *mColorModel;
+ ColorPage *mColorPage;
+ ScrollArea *mScrollColorPage;
+ NamesModel *mFontModel;
+ ListBox *mFontPage;
+ ScrollArea *mScrollFontPage;
+ ImageSet *mImageSet;
+};
+
+extern EmoteWindow *emoteWindow;
+
+#endif // GUI_EMOTEWINDOW_H
diff --git a/src/gui/windows/equipmentwindow.cpp b/src/gui/windows/equipmentwindow.cpp
new file mode 100644
index 000000000..0402115df
--- /dev/null
+++ b/src/gui/windows/equipmentwindow.cpp
@@ -0,0 +1,669 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/equipmentwindow.h"
+
+#include "configuration.h"
+#include "dragdrop.h"
+#include "graphicsvertexes.h"
+#include "inventory.h"
+#include "item.h"
+
+#include "being/being.h"
+#include "being/localplayer.h"
+#include "being/playerinfo.h"
+
+#include "gui/itempopup.h"
+#include "gui/viewport.h"
+
+#include "gui/windows/setup.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/playerbox.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+
+#include "resources/imageset.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include <SDL_mouse.h>
+
+#include "debug.h"
+
+static const int BOX_COUNT = 13;
+
+EquipmentWindow::EquipmentWindow(Equipment *const equipment,
+ Being *const being,
+ const bool foring):
+ // TRANSLATORS: equipment window name
+ Window(_("Equipment"), false, nullptr, "equipment.xml"),
+ gcn::ActionListener(),
+ mEquipment(equipment),
+ mItemPopup(new ItemPopup),
+ mPlayerBox(new PlayerBox("equipment_playerbox.xml",
+ "equipment_selectedplayerbox.xml")),
+ // TRANSLATORS: equipment window button
+ mUnequip(new Button(this, _("Unequip"), "unequip", this)),
+ mSelected(-1),
+ mForing(foring),
+ mImageSet(nullptr),
+ mBeing(being),
+ mBoxes(),
+ mHighlightColor(getThemeColor(Theme::HIGHLIGHT)),
+ mBorderColor(getThemeColor(Theme::BORDER)),
+ mLabelsColor(getThemeColor(Theme::LABEL)),
+ mLabelsColor2(getThemeColor(Theme::LABEL_OUTLINE)),
+ mSlotBackground(),
+ mSlotHighlightedBackground(),
+ mVertexes(new ImageCollection),
+ mItemPadding(getOption("itemPadding")),
+ mBoxSize(getOption("boxSize")),
+ mButtonPadding(getOption("buttonPadding", 5)),
+ mMinX(180),
+ mMinY(345),
+ mMaxX(0),
+ mMaxY(0)
+{
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ if (!mBoxSize)
+ mBoxSize = 36;
+
+ // Control that shows the Player
+ mPlayerBox->setDimension(gcn::Rectangle(50, 80, 74, 168));
+ mPlayerBox->setPlayer(being);
+
+ if (foring)
+ setWindowName("Being equipment");
+ else
+ setWindowName("Equipment");
+
+ setCloseButton(true);
+ setSaveVisible(true);
+ setStickyButtonLock(true);
+
+ mBoxes.reserve(BOX_COUNT);
+ for (int f = 0; f < BOX_COUNT; f ++)
+ mBoxes.push_back(nullptr);
+
+ fillBoxes();
+ recalcSize();
+
+ loadWindowState();
+
+ const gcn::Rectangle &area = getChildrenArea();
+ mUnequip->setPosition(area.width - mUnequip->getWidth() - mButtonPadding,
+ area.height - mUnequip->getHeight() - mButtonPadding);
+ mUnequip->setEnabled(false);
+
+ ImageRect rect;
+ Theme::instance()->loadRect(rect, "equipment_background.xml", "", 0, 1);
+ mSlotBackground = rect.grid[0];
+ mSlotHighlightedBackground = rect.grid[1];
+ add(mPlayerBox);
+ add(mUnequip);
+ enableVisibleSound(true);
+}
+
+EquipmentWindow::~EquipmentWindow()
+{
+ delete mItemPopup;
+ mItemPopup = nullptr;
+ if (this == beingEquipmentWindow)
+ {
+ if (mEquipment)
+ delete mEquipment->getBackend();
+ delete mEquipment;
+ mEquipment = nullptr;
+ }
+ delete_all(mBoxes);
+ mBoxes.clear();
+ if (mImageSet)
+ {
+ mImageSet->decRef();
+ mImageSet = nullptr;
+ }
+ if (mSlotBackground)
+ mSlotBackground->decRef();
+ if (mSlotHighlightedBackground)
+ mSlotHighlightedBackground->decRef();
+ delete mVertexes;
+ mVertexes = nullptr;
+}
+
+void EquipmentWindow::draw(gcn::Graphics *graphics)
+{
+ BLOCK_START("EquipmentWindow::draw")
+ // Draw window graphics
+ Window::draw(graphics);
+ Graphics *const g = static_cast<Graphics*>(graphics);
+
+ int i = 0;
+ gcn::Font *const font = getFont();
+ const int fontHeight = font->getHeight();
+
+ if (openGLMode != RENDER_SAFE_OPENGL)
+ {
+ if (mLastRedraw)
+ {
+ mVertexes->clear();
+ FOR_EACH (std::vector<EquipmentBox*>::const_iterator, it, mBoxes)
+ {
+ const EquipmentBox *const box = *it;
+ if (!box)
+ continue;
+ if (i == mSelected)
+ {
+ g->calcTile(mVertexes, mSlotHighlightedBackground,
+ box->x, box->y);
+ }
+ else
+ {
+ g->calcTile(mVertexes, mSlotBackground, box->x, box->y);
+ }
+ }
+ }
+ g->drawTile(mVertexes);
+ }
+ else
+ {
+ for (std::vector<EquipmentBox*>::const_iterator it = mBoxes.begin(),
+ it_end = mBoxes.end(); it != it_end; ++ it, ++ i)
+ {
+ const EquipmentBox *const box = *it;
+ if (!box)
+ continue;
+ if (i == mSelected)
+ g->drawImage(mSlotHighlightedBackground, box->x, box->y);
+ else
+ g->drawImage(mSlotBackground, box->x, box->y);
+ }
+ }
+
+ if (!mEquipment)
+ {
+ BLOCK_END("EquipmentWindow::draw")
+ return;
+ }
+
+ i = 0;
+ for (std::vector<EquipmentBox*>::const_iterator it = mBoxes.begin(),
+ it_end = mBoxes.end(); it != it_end; ++ it, ++ i)
+ {
+ const EquipmentBox *const box = *it;
+ if (!box)
+ continue;
+ const Item *const item = mEquipment->getEquipment(i);
+ if (item)
+ {
+ // Draw Item.
+ Image *const image = item->getImage();
+ if (image)
+ {
+ image->setAlpha(1.0F); // Ensure the image is drawn
+ // with maximum opacity
+ g->drawImage(image, box->x + mItemPadding,
+ box->y + mItemPadding);
+ if (i == EQUIP_PROJECTILE_SLOT)
+ {
+ g->setColorAll(mLabelsColor, mLabelsColor2);
+ const std::string str = toString(item->getQuantity());
+ font->drawString(g, str,
+ box->x + (mBoxSize - font->getWidth(str)) / 2,
+ box->y - fontHeight);
+ }
+ }
+ }
+ else if (box->image)
+ {
+ g->drawImage(box->image, box->x + mItemPadding,
+ box->y + mItemPadding);
+ }
+ }
+ BLOCK_END("EquipmentWindow::draw")
+}
+
+void EquipmentWindow::action(const gcn::ActionEvent &event)
+{
+ if (!mEquipment)
+ return;
+
+ if (event.getId() == "unequip" && mSelected > -1)
+ {
+ const Item *const item = mEquipment->getEquipment(mSelected);
+ Net::getInventoryHandler()->unequipItem(item);
+ setSelected(-1);
+ }
+}
+
+Item *EquipmentWindow::getItem(const int x, const int y) const
+{
+ if (!mEquipment)
+ return nullptr;
+
+ int i = 0;
+
+ for (std::vector<EquipmentBox*>::const_iterator it = mBoxes.begin(),
+ it_end = mBoxes.end(); it != it_end; ++ it, ++ i)
+ {
+ const EquipmentBox *const box = *it;
+ if (!box)
+ continue;
+ const gcn::Rectangle tRect(box->x, box->y, mBoxSize, mBoxSize);
+
+ if (tRect.isPointInRect(x, y))
+ return mEquipment->getEquipment(i);
+ }
+ return nullptr;
+}
+
+void EquipmentWindow::mousePressed(gcn::MouseEvent& mouseEvent)
+{
+ if (!mEquipment)
+ {
+ Window::mousePressed(mouseEvent);
+ return;
+ }
+
+ const int x = mouseEvent.getX();
+ const int y = mouseEvent.getY();
+
+ if (mouseEvent.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (mForing)
+ {
+ Window::mousePressed(mouseEvent);
+ return;
+ }
+ // Checks if any of the presses were in the equip boxes.
+ int i = 0;
+
+ bool inBox(false);
+
+ for (std::vector<EquipmentBox*>::const_iterator it = mBoxes.begin(),
+ it_end = mBoxes.end(); it != it_end; ++ it, ++ i)
+ {
+ const EquipmentBox *const box = *it;
+ if (!box)
+ continue;
+ const Item *const item = mEquipment->getEquipment(i);
+ const gcn::Rectangle tRect(box->x, box->y, mBoxSize, mBoxSize);
+
+ if (tRect.isPointInRect(x, y))
+ {
+ inBox = true;
+ if (item)
+ {
+ setSelected(i);
+ dragDrop.dragItem(item, DRAGDROP_SOURCE_EQUIPMENT);
+ return;
+ }
+ }
+ if (inBox)
+ return;
+ }
+ }
+ else if (mouseEvent.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (Item *const item = getItem(x, y))
+ {
+ if (mItemPopup)
+ mItemPopup->setVisible(false);
+
+ /* Convert relative to the window coordinates to absolute screen
+ * coordinates.
+ */
+ const int mx = x + getX();
+ const int my = y + getY();
+ if (viewport)
+ {
+ if (mForing)
+ viewport->showUndressPopup(mx, my, mBeing, item);
+ else
+ viewport->showPopup(this, mx, my, item, true);
+ }
+ }
+ }
+ Window::mousePressed(mouseEvent);
+}
+
+void EquipmentWindow::mouseReleased(gcn::MouseEvent &mouseEvent)
+{
+ Window::mouseReleased(mouseEvent);
+ const DragDropSource src = dragDrop.getSource();
+ if (dragDrop.isEmpty() || (src != DRAGDROP_SOURCE_INVENTORY
+ && src != DRAGDROP_SOURCE_EQUIPMENT))
+ {
+ return;
+ }
+ Inventory *const inventory = player_node
+ ? PlayerInfo::getInventory() : nullptr;
+ if (!inventory)
+ return;
+
+ Item *const item = inventory->findItem(dragDrop.getItem(),
+ dragDrop.getItemColor());
+ if (!item)
+ return;
+
+ if (dragDrop.getSource() == DRAGDROP_SOURCE_INVENTORY)
+ {
+ if (item->isEquipment())
+ {
+ if (!item->isEquipped())
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ }
+ else if (dragDrop.getSource() == DRAGDROP_SOURCE_EQUIPMENT)
+ {
+ if (item->isEquipment())
+ {
+ const int x = mouseEvent.getX();
+ const int y = mouseEvent.getY();
+ int i = 0;
+ for (std::vector<EquipmentBox*>::const_iterator
+ it = mBoxes.begin(), it_end = mBoxes.end();
+ it != it_end; ++ it, ++ i)
+ {
+ const EquipmentBox *const box = *it;
+ if (!box)
+ continue;
+ const gcn::Rectangle tRect(box->x, box->y, mBoxSize, mBoxSize);
+
+ if (tRect.isPointInRect(x, y))
+ return;
+ }
+
+ if (item->isEquipped())
+ Net::getInventoryHandler()->unequipItem(item);
+ }
+ }
+ dragDrop.clear();
+ dragDrop.deselect();
+}
+
+// Show ItemTooltip
+void EquipmentWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ Window::mouseMoved(event);
+
+ if (!mItemPopup)
+ return;
+
+ const int x = event.getX();
+ const int y = event.getY();
+
+ const Item *const 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 A_UNUSED)
+{
+ if (mItemPopup)
+ mItemPopup->setVisible(false);
+}
+
+void EquipmentWindow::setSelected(const int index)
+{
+ mSelected = index;
+ mRedraw = true;
+ if (mUnequip)
+ mUnequip->setEnabled(mSelected != -1);
+ if (mItemPopup)
+ mItemPopup->setVisible(false);
+}
+
+void EquipmentWindow::setBeing(Being *const being)
+{
+ mPlayerBox->setPlayer(being);
+ mBeing = being;
+ if (mEquipment)
+ delete mEquipment->getBackend();
+ delete mEquipment;
+ if (!being)
+ {
+ mEquipment = nullptr;
+ return;
+ }
+ mEquipment = being->getEquipment();
+}
+
+void EquipmentWindow::updateBeing(Being *const being)
+{
+ if (being == mBeing)
+ setBeing(being);
+}
+
+void EquipmentWindow::resetBeing(const Being *const being)
+{
+ if (being == mBeing)
+ setBeing(nullptr);
+}
+
+void EquipmentWindow::fillBoxes()
+{
+ XML::Document *const doc = new XML::Document(
+ paths.getStringValue("equipmentWindowFile"));
+ const XmlNodePtr root = doc->rootNode();
+ if (!root)
+ {
+ delete doc;
+ fillDefault();
+ return;
+ }
+
+ if (mImageSet)
+ mImageSet->decRef();
+
+ mImageSet = Theme::getImageSetFromTheme(XML::getProperty(
+ root, "image", "equipmentbox.png"), 32, 32);
+
+ for_each_xml_child_node(node, root)
+ {
+ if (xmlNameEqual(node, "playerbox"))
+ loadPlayerBox(node);
+ else if (xmlNameEqual(node, "slot"))
+ loadSlot(node, mImageSet);
+ }
+ delete doc;
+}
+
+void EquipmentWindow::loadPlayerBox(const XmlNodePtr playerBoxNode)
+{
+ mPlayerBox->setDimension(gcn::Rectangle(
+ XML::getProperty(playerBoxNode, "x", 50),
+ XML::getProperty(playerBoxNode, "y", 80),
+ XML::getProperty(playerBoxNode, "width", 74),
+ XML::getProperty(playerBoxNode, "height", 168)));
+}
+
+void EquipmentWindow::loadSlot(const XmlNodePtr slotNode,
+ const ImageSet *const imageset)
+{
+ const int slot = parseSlotName(XML::getProperty(slotNode, "name", ""));
+ if (slot < 0)
+ return;
+
+ const int x = XML::getProperty(slotNode, "x", 0) + getPadding();
+ const int y = XML::getProperty(slotNode, "y", 0) + getTitleBarHeight();
+ const int imageIndex = XML::getProperty(slotNode, "image", -1);
+ Image *image = nullptr;
+
+ if (imageset && imageIndex >= 0 && imageIndex
+ < static_cast<signed>(imageset->size()))
+ {
+ image = imageset->get(imageIndex);
+ }
+
+ if (mBoxes[slot])
+ {
+ EquipmentBox *const box = mBoxes[slot];
+ box->x = x;
+ box->y = y;
+ box->image = image;
+ }
+ else
+ {
+ mBoxes[slot] = new EquipmentBox(x, y, image);
+ }
+ if (x < mMinX)
+ mMinX = x;
+ if (y < mMinY)
+ mMinY = y;
+ if (x + mBoxSize > mMaxX)
+ mMaxX = x + mBoxSize;
+ if (y + mBoxSize > mMaxY)
+ mMaxY = y + mBoxSize;
+}
+
+int EquipmentWindow::parseSlotName(const std::string &name) const
+{
+ int id = -1;
+ if (name == "shoes" || name == "boot" || name == "boots")
+ {
+ id = 4;
+ }
+ else if (name == "bottomclothes" || name == "bottom" || name == "pants")
+ {
+ id = 3;
+ }
+ else if (name == "topclothes" || name == "top"
+ || name == "torso" || name == "body")
+ {
+ id = 0;
+ }
+ else if (name == "misc1" || name == "cape")
+ {
+ id = 5;
+ }
+ else if (name == "misc2" || name == "scarf" || name == "scarfs")
+ {
+ id = 7;
+ }
+ else if (name == "hat" || name == "hats")
+ {
+ id = 2;
+ }
+ else if (name == "wings")
+ {
+ id = 6;
+ }
+ else if (name == "glove" || name == "gloves")
+ {
+ id = 1;
+ }
+ else if (name == "weapon" || name == "weapons")
+ {
+ id = 8;
+ }
+ else if (name == "shield" || name == "shields")
+ {
+ id = 9;
+ }
+ else if (name == "amulet" || name == "amulets")
+ {
+ id = 11;
+ }
+ else if (name == "ring" || name == "rings")
+ {
+ id = 12;
+ }
+ else if (name == "arrow" || name == "arrows" || name == "ammo")
+ {
+ id = 10;
+ }
+
+ return id;
+}
+
+void EquipmentWindow::fillDefault()
+{
+ if (mImageSet)
+ mImageSet->decRef();
+
+ mImageSet = Theme::getImageSetFromTheme(
+ "equipmentbox.png", 32, 32);
+
+ addBox(0, 90, 40, 0); // torso
+ addBox(1, 8, 78, 1); // gloves
+ addBox(2, 70, 0, 2); // hat
+ addBox(3, 50, 253, 3); // pants
+ addBox(4, 90, 253, 4); // boots
+ addBox(5, 8, 213, 5); // FREE
+ addBox(6, 129, 213, 6); // wings
+ addBox(7, 50, 40, 5); // scarf
+ addBox(8, 8, 168, 7); // weapon
+ addBox(9, 129, 168, 8); // shield
+ addBox(10, 129, 78, 9); // ammo
+ addBox(11, 8, 123, 5); // amulet
+ addBox(12, 129, 123, 5); // ring
+}
+
+void EquipmentWindow::addBox(const int idx, int x, int y, const int imageIndex)
+{
+ Image *image = nullptr;
+
+ if (mImageSet && imageIndex >= 0 && imageIndex
+ < static_cast<signed>(mImageSet->size()))
+ {
+ image = mImageSet->get(imageIndex);
+ }
+
+ x += getPadding();
+ y += getTitleBarHeight();
+ mBoxes[idx] = new EquipmentBox(x, y, image);
+
+ if (x < mMinX)
+ mMinX = x;
+ if (y < mMinY)
+ mMinY = y;
+ if (x + mBoxSize > mMaxX)
+ mMaxX = x + mBoxSize;
+ if (y + mBoxSize > mMaxY)
+ mMaxY = y + mBoxSize;
+}
+
+void EquipmentWindow::recalcSize()
+{
+ mMaxX += mMinX;
+ mMaxY += mMinY + mUnequip->getHeight() + mButtonPadding;
+ setDefaultSize(mMaxX, mMaxY, ImageRect::CENTER);
+}
diff --git a/src/gui/windows/equipmentwindow.h b/src/gui/windows/equipmentwindow.h
new file mode 100644
index 000000000..5fd69c23a
--- /dev/null
+++ b/src/gui/windows/equipmentwindow.h
@@ -0,0 +1,155 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_EQUIPMENTWINDOW_H
+#define GUI_EQUIPMENTWINDOW_H
+
+#include "equipment.h"
+#include "localconsts.h"
+
+#include "gui/widgets/window.h"
+
+#include "utils/xml.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <vector>
+
+class Being;
+class Button;
+class Image;
+class ImageSet;
+class Item;
+class ItemPopup;
+class PlayerBox;
+
+struct EquipmentBox final
+{
+ EquipmentBox(const int x0, const int y0, Image *const img) :
+ x(x0), y(y0), image(img)
+ { }
+
+ A_DELETE_COPY(EquipmentBox)
+
+ int x;
+ int y;
+ Image *image;
+};
+
+/**
+ * Equipment dialog.
+ *
+ * \ingroup Interface
+ */
+class EquipmentWindow final : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ EquipmentWindow(Equipment *const equipment, Being *const being,
+ const bool foring = false);
+
+ A_DELETE_COPY(EquipmentWindow)
+
+ /**
+ * Destructor.
+ */
+ ~EquipmentWindow();
+
+ /**
+ * Draws the equipment window.
+ */
+ void draw(gcn::Graphics *graphics) override;
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void mousePressed(gcn::MouseEvent& mouseEvent) override;
+
+ const Item* getEquipment(const int i) const A_WARN_UNUSED
+ { return mEquipment ? mEquipment->getEquipment(i) : nullptr; }
+
+ void setBeing(Being *const being);
+
+ void updateBeing(Being *const being);
+
+ void resetBeing(const Being *const being);
+
+ void mouseExited(gcn::MouseEvent &event) override;
+
+ void mouseMoved(gcn::MouseEvent &event) override;
+
+ void mouseReleased(gcn::MouseEvent &event) override;
+
+ void recalcSize();
+
+ private:
+ Item *getItem(const int x, const int y) const A_WARN_UNUSED;
+
+ void setSelected(const int index);
+
+ void fillBoxes();
+
+ void fillDefault();
+
+ void addBox(const int idx, int x, int y, const int imageIndex);
+
+ void loadWindow(const XmlNodePtr windowNode);
+
+ void loadPlayerBox(const XmlNodePtr playerBoxNode);
+
+ void loadSlot(const XmlNodePtr slotNode,
+ const ImageSet *const imageset);
+
+ int parseSlotName(const std::string &name) const A_WARN_UNUSED;
+
+ Equipment *mEquipment;
+
+ ItemPopup *mItemPopup;
+ PlayerBox *mPlayerBox;
+ Button *mUnequip;
+
+ int mSelected; /**< Index of selected item. */
+ bool mForing;
+ ImageSet *mImageSet;
+ Being *mBeing;
+ std::vector<EquipmentBox*> mBoxes;
+ gcn::Color mHighlightColor;
+ gcn::Color mBorderColor;
+ gcn::Color mLabelsColor;
+ gcn::Color mLabelsColor2;
+ Image *mSlotBackground;
+ Image *mSlotHighlightedBackground;
+ ImageCollection *mVertexes;
+ int mItemPadding;
+ int mBoxSize;
+ int mButtonPadding;
+ int mMinX;
+ int mMinY;
+ int mMaxX;
+ int mMaxY;
+};
+
+extern EquipmentWindow *equipmentWindow;
+extern EquipmentWindow *beingEquipmentWindow;
+
+#endif // GUI_EQUIPMENTWINDOW_H
diff --git a/src/gui/windows/helpwindow.cpp b/src/gui/windows/helpwindow.cpp
new file mode 100644
index 000000000..db85ea0bd
--- /dev/null
+++ b/src/gui/windows/helpwindow.cpp
@@ -0,0 +1,196 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/helpwindow.h"
+
+#include "configuration.h"
+
+#include "gui/gui.h"
+#include "gui/sdlfont.h"
+
+#include "gui/windows/didyouknowwindow.h"
+#include "gui/windows/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 "utils/gettext.h"
+#include "utils/paths.h"
+#include "utils/process.h"
+
+#include "utils/translation/podict.h"
+#include "utils/translation/translationmanager.h"
+
+#include "debug.h"
+
+HelpWindow::HelpWindow() :
+ // TRANSLATORS: help window name
+ Window(_("Help"), false, nullptr, "help.xml"),
+ gcn::ActionListener(),
+ // TRANSLATORS: help window. button.
+ mDYKButton(new Button(this, _("Did you know..."), "DYK", this)),
+ mBrowserBox(new BrowserBox(this)),
+ mScrollArea(new ScrollArea(mBrowserBox, true, "help_background.xml")),
+ mTagFileMap()
+{
+ setMinWidth(300);
+ setMinHeight(220);
+ setContentSize(455, 350);
+ setWindowName("Help");
+ setCloseButton(true);
+ setResizable(true);
+ setStickyButtonLock(true);
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ setDefaultSize(500, 400, ImageRect::CENTER);
+
+ mBrowserBox->setOpaque(false);
+
+ mBrowserBox->setLinkHandler(this);
+ mBrowserBox->setFont(gui->getHelpFont());
+ mBrowserBox->setProcessVersion(true);
+ mBrowserBox->setEnableImages(true);
+ mBrowserBox->setEnableKeys(true);
+ mBrowserBox->setEnableTabs(true);
+
+ place(4, 3, mDYKButton);
+ place(0, 0, mScrollArea, 5, 3).setPadding(3);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ loadWindowState();
+ loadTags();
+ enableVisibleSound(true);
+}
+
+void HelpWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "DYK")
+ {
+ if (didYouKnowWindow)
+ {
+ didYouKnowWindow->setVisible(!didYouKnowWindow->isWindowVisible());
+ if (didYouKnowWindow->isWindowVisible())
+ didYouKnowWindow->requestMoveToTop();
+ }
+ }
+}
+
+void HelpWindow::handleLink(const std::string &link,
+ gcn::MouseEvent *event A_UNUSED)
+{
+ if (!strStartWith(link, "http://") && !strStartWith(link, "https://"))
+ {
+ std::string helpFile = link;
+ loadHelp(helpFile);
+ }
+ else
+ {
+ openBrowser(link);
+ }
+}
+
+void HelpWindow::loadHelp(const std::string &helpFile)
+{
+ if (!checkPath(helpFile))
+ return;
+ mBrowserBox->clearRows();
+ loadFile("header");
+ loadFile(helpFile);
+ loadFile("footer");
+ mScrollArea->setVerticalScrollAmount(0);
+ setVisible(true);
+}
+
+void HelpWindow::loadFile(std::string file)
+{
+ trim(file);
+ std::string helpPath = branding.getStringValue("helpPath");
+ if (helpPath.empty())
+ helpPath = paths.getStringValue("help");
+
+ StringVect lines;
+ TranslationManager::translateFile(helpPath.append(file).append(".txt"),
+ translator, lines);
+
+ for (size_t i = 0, sz = lines.size(); i < sz; ++i)
+ mBrowserBox->addRow(lines[i]);
+}
+
+void HelpWindow::loadTags()
+{
+ std::string helpPath = branding.getStringValue("helpPath");
+ if (helpPath.empty())
+ helpPath = paths.getStringValue("help");
+ StringVect lines;
+ ResourceManager::loadTextFile(helpPath.append("tags.idx"), lines);
+ FOR_EACH (StringVectCIter, it, lines)
+ {
+ const std::string &str = *it;
+ const size_t idx = str.find('|');
+ if (idx != std::string::npos)
+ mTagFileMap[str.substr(idx + 1)].insert(str.substr(0, idx));
+ }
+}
+
+void HelpWindow::search(const std::string &text0)
+{
+ std::string text = text0;
+ trim(text);
+ toLower(text);
+ if (mTagFileMap.find(text) == mTagFileMap.end())
+ {
+ loadHelp("searchnotfound");
+ }
+ else
+ {
+ const HelpNames &names = mTagFileMap[text];
+ if (names.size() == 1)
+ {
+ loadHelp(*names.begin());
+ }
+ else
+ {
+ if (!translator)
+ return;
+ mBrowserBox->clearRows();
+ loadFile("header");
+ loadFile("searchmany");
+ FOR_EACH (HelpNamesCIter, it, names)
+ {
+ const char *const str = (*it).c_str();
+ mBrowserBox->addRow(strprintf(" -> @@%s|%s@@", str,
+ translator->getChar(str)));
+ }
+ loadFile("footer");
+ mScrollArea->setVerticalScrollAmount(0);
+ setVisible(true);
+ }
+ }
+}
diff --git a/src/gui/windows/helpwindow.h b/src/gui/windows/helpwindow.h
new file mode 100644
index 000000000..1419e0a10
--- /dev/null
+++ b/src/gui/windows/helpwindow.h
@@ -0,0 +1,93 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_HELPWINDOW_H
+#define GUI_HELPWINDOW_H
+
+#include "gui/widgets/linkhandler.h"
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include "localconsts.h"
+
+#include <map>
+#include <set>
+
+class Button;
+class BrowserBox;
+class ScrollArea;
+
+typedef std::set<std::string> HelpNames;
+typedef HelpNames::const_iterator HelpNamesCIter;
+typedef std::map<std::string, HelpNames> HelpTagsMap;
+
+/**
+ * The help window.
+ */
+class HelpWindow final : public Window, public LinkHandler,
+ public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ HelpWindow();
+
+ A_DELETE_COPY(HelpWindow)
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Handles link action.
+ */
+ void handleLink(const std::string &link,
+ gcn::MouseEvent *event A_UNUSED) override;
+
+ /**
+ * Loads help in the dialog.
+ */
+ void loadHelp(const std::string &helpFile);
+
+ /**
+ * Seach for given text in tags.
+ */
+ void search(const std::string &text);
+
+ private:
+ void loadTags();
+
+ void loadFile(std::string file);
+
+ Button *mDYKButton;
+
+ BrowserBox *mBrowserBox;
+ ScrollArea *mScrollArea;
+ HelpTagsMap mTagFileMap;
+};
+
+extern HelpWindow *helpWindow;
+
+#endif // GUI_HELPWINDOW_H
diff --git a/src/gui/windows/inventorywindow.cpp b/src/gui/windows/inventorywindow.cpp
new file mode 100644
index 000000000..b57a7ea21
--- /dev/null
+++ b/src/gui/windows/inventorywindow.cpp
@@ -0,0 +1,835 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/inventorywindow.h"
+
+#include "configuration.h"
+#include "item.h"
+#include "units.h"
+
+#include "being/playerinfo.h"
+
+#include "input/inputmanager.h"
+#include "input/keyevent.h"
+
+#include "gui/gui.h"
+#include "gui/textpopup.h"
+#include "gui/viewport.h"
+
+#include "gui/windows/equipmentwindow.h"
+#include "gui/windows/itemamountwindow.h"
+#include "gui/windows/outfitwindow.h"
+#include "gui/windows/setup.h"
+#include "gui/windows/shopwindow.h"
+#include "gui/windows/tradewindow.h"
+
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/itemcontainer.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/progressbar.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/tabstrip.h"
+#include "gui/widgets/textfield.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+
+#include "resources/itemdb.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include <string>
+
+#include "debug.h"
+
+static const char *const SORT_NAME_INVENTORY[6] =
+{
+ // TRANSLATORS: inventory sort mode
+ N_("default"),
+ // TRANSLATORS: inventory sort mode
+ N_("by name"),
+ // TRANSLATORS: inventory sort mode
+ N_("by id"),
+ // TRANSLATORS: inventory sort mode
+ N_("by weight"),
+ // TRANSLATORS: inventory sort mode
+ N_("by amount"),
+ // TRANSLATORS: inventory sort mode
+ N_("by type")
+};
+
+class SortListModelInv final : public gcn::ListModel
+{
+public:
+ ~SortListModelInv()
+ { }
+
+ int getNumberOfElements() override
+ { return 6; }
+
+ std::string getElementAt(int i) override
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "???";
+
+ return gettext(SORT_NAME_INVENTORY[i]);
+ }
+};
+
+InventoryWindow::WindowList InventoryWindow::invInstances;
+
+InventoryWindow::InventoryWindow(Inventory *const inventory):
+ Window("Inventory", false, nullptr, "inventory.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ gcn::SelectionListener(),
+ InventoryListener(),
+ mInventory(inventory),
+ mItems(new ItemContainer(this, mInventory)),
+ mWeight(),
+ mSlots(),
+ mUseButton(nullptr),
+ mDropButton(nullptr),
+ mSplitButton(nullptr),
+ mOutfitButton(nullptr),
+ mShopButton(nullptr),
+ mEquipmentButton(nullptr),
+ mStoreButton(nullptr),
+ mRetrieveButton(nullptr),
+ mInvCloseButton(nullptr),
+ mWeightBar(nullptr),
+ mSlotsBar(new ProgressBar(this, 0.0F, 100, 0, Theme::PROG_INVY_SLOTS)),
+ mFilter(nullptr),
+ mSortModel(new SortListModelInv),
+ mSortDropDown(new DropDown(this, mSortModel, false, false, this, "sort")),
+ mNameFilter(new TextField(this, "", true, this, "namefilter", true)),
+ mSortDropDownCell(nullptr),
+ mNameFilterCell(nullptr),
+ mFilterCell(nullptr),
+ mSlotsBarCell(nullptr),
+ mTextPopup(new TextPopup),
+ mSplit(false),
+ mCompactMode(false)
+{
+ if (inventory)
+ {
+ setCaption(gettext(inventory->getName().c_str()));
+ setWindowName(inventory->getName());
+ }
+ else
+ {
+ // TRANSLATORS: inventory window name
+ setCaption(_("Inventory"));
+ setWindowName("Inventory");
+ }
+
+ listen(CHANNEL_ATTRIBUTES);
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ setResizable(true);
+ setCloseButton(true);
+ setSaveVisible(true);
+ setStickyButtonLock(true);
+
+ if (mainGraphics->mWidth > 600)
+ setDefaultSize(450, 310, ImageRect::CENTER);
+ else
+ setDefaultSize(387, 307, ImageRect::CENTER);
+ setMinWidth(310);
+ setMinHeight(179);
+ addKeyListener(this);
+
+ mItems->addSelectionListener(this);
+
+ gcn::ScrollArea *const invenScroll = new ScrollArea(
+ mItems, getOptionBool("showbackground"), "inventory_background.xml");
+ invenScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ const int size = config.getIntValue("fontSize");
+ mFilter = new TabStrip(this, "filter_" + getWindowName(), size + 16);
+ mFilter->addActionListener(this);
+ mFilter->setActionEventId("tag_");
+
+ mSortDropDown->setSelected(0);
+
+ StringVect tags = ItemDB::getTags();
+ const size_t sz = tags.size();
+ for (size_t f = 0; f < sz; f ++)
+ mFilter->addButton(tags[f]);
+
+ if (isMainInventory())
+ {
+ // TRANSLATORS: inventory button
+ const std::string equip = _("Equip");
+ // TRANSLATORS: inventory button
+ const std::string use = _("Use");
+ // TRANSLATORS: inventory button
+ const 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(this, longestUseString, "use", this);
+ // TRANSLATORS: inventory button
+ mDropButton = new Button(this, _("Drop..."), "drop", this);
+ // TRANSLATORS: inventory button
+ mSplitButton = new Button(this, _("Split"), "split", this);
+ // TRANSLATORS: inventory button
+ mOutfitButton = new Button(this, _("Outfits"), "outfit", this);
+ // TRANSLATORS: inventory button
+ mShopButton = new Button(this, _("Shop"), "shop", this);
+ // TRANSLATORS: inventory button
+ mEquipmentButton = new Button(this, _("Equipment"), "equipment", this);
+ mWeightBar = new ProgressBar(this, 0.0F, 100, 0, Theme::PROG_WEIGHT);
+
+ place(0, 0, mWeightBar, 4);
+ mSlotsBarCell = &place(4, 0, mSlotsBar, 5);
+ mSortDropDownCell = &place(9, 0, mSortDropDown, 2);
+
+ mFilterCell = &place(0, 1, mFilter, 10).setPadding(3);
+ mNameFilterCell = &place(9, 1, mNameFilter, 2);
+
+ place(0, 2, invenScroll, 11).setPadding(3);
+ place(0, 3, mUseButton);
+ place(1, 3, mDropButton);
+ place(8, 2, mSplitButton);
+ place(8, 3, mShopButton);
+ place(9, 3, mOutfitButton);
+ place(10, 3, mEquipmentButton);
+
+ updateWeight();
+ }
+ else
+ {
+ // TRANSLATORS: storage button
+ mStoreButton = new Button(this, _("Store"), "store", this);
+ // TRANSLATORS: storage button
+ mRetrieveButton = new Button(this, _("Retrieve"), "retrieve", this);
+ // TRANSLATORS: storage button
+ mInvCloseButton = new Button(this, _("Close"), "close", this);
+
+ mSlotsBarCell = &place(0, 0, mSlotsBar, 6);
+ mSortDropDownCell = &place(6, 0, mSortDropDown, 1);
+
+ mFilterCell = &place(0, 1, mFilter, 7).setPadding(3);
+ mNameFilterCell = &place(6, 1, mNameFilter, 1);
+
+ place(0, 2, invenScroll, 7, 4);
+ place(0, 6, mStoreButton);
+ place(1, 6, mRetrieveButton);
+ place(6, 6, mInvCloseButton);
+ }
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(2, Layout::AUTO_SET);
+
+ mInventory->addInventoyListener(this);
+
+ invInstances.push_back(this);
+
+ if (inventory && inventory->isMainInventory())
+ {
+ updateDropButton();
+ }
+ else
+ {
+ if (!invInstances.empty())
+ invInstances.front()->updateDropButton();
+ }
+
+ loadWindowState();
+ enableVisibleSound(true);
+ slotsChanged(mInventory);
+
+ widgetResized(gcn::Event(nullptr));
+ if (!isMainInventory())
+ setVisible(true);
+}
+
+InventoryWindow::~InventoryWindow()
+{
+ invInstances.remove(this);
+ mInventory->removeInventoyListener(this);
+ if (!invInstances.empty())
+ invInstances.front()->updateDropButton();
+
+ mSortDropDown->hideDrop(false);
+ delete mSortModel;
+ mSortModel = nullptr;
+ mTextPopup = nullptr;
+}
+
+void InventoryWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "outfit")
+ {
+ if (outfitWindow)
+ {
+ outfitWindow->setVisible(!outfitWindow->isWindowVisible());
+ if (outfitWindow->isWindowVisible())
+ outfitWindow->requestMoveToTop();
+ }
+ }
+ else if (eventId == "shop")
+ {
+ if (shopWindow)
+ {
+ shopWindow->setVisible(!shopWindow->isWindowVisible());
+ if (shopWindow->isWindowVisible())
+ shopWindow->requestMoveToTop();
+ }
+ }
+ else if (eventId == "equipment")
+ {
+ if (equipmentWindow)
+ {
+ equipmentWindow->setVisible(!equipmentWindow->isWindowVisible());
+ if (equipmentWindow->isWindowVisible())
+ equipmentWindow->requestMoveToTop();
+ }
+ }
+ else if (eventId == "close")
+ {
+ close();
+ }
+ else if (eventId == "store")
+ {
+ if (!inventoryWindow || !inventoryWindow->isWindowVisible())
+ return;
+
+ Item *const item = inventoryWindow->getSelectedItem();
+
+ if (!item)
+ return;
+
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd, this, item);
+ }
+ else if (eventId == "sort")
+ {
+ mItems->setSortType(mSortDropDown->getSelected());
+ return;
+ }
+ else if (eventId == "namefilter")
+ {
+ mItems->setName(mNameFilter->getText());
+ mItems->updateMatrix();
+ }
+ else if (!eventId.find("tag_"))
+ {
+ std::string tagName = event.getId().substr(4);
+ mItems->setFilter(ItemDB::getTagId(tagName));
+ return;
+ }
+
+ Item *const item = mItems->getSelectedItem();
+
+ if (!item)
+ return;
+
+ if (eventId == "use")
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ Net::getInventoryHandler()->unequipItem(item);
+ else
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ else
+ {
+ if (PlayerInfo::isItemProtected(item->getId()))
+ return;
+ Net::getInventoryHandler()->useItem(item);
+ }
+ }
+ if (eventId == "equip")
+ {
+ if (!item->isEquipment())
+ {
+ if (item->isEquipped())
+ Net::getInventoryHandler()->unequipItem(item);
+ else
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ else
+ {
+ if (PlayerInfo::isItemProtected(item->getId()))
+ return;
+ Net::getInventoryHandler()->useItem(item);
+ }
+ }
+ else if (eventId == "drop")
+ {
+ if (PlayerInfo::isItemProtected(item->getId()))
+ return;
+
+ if (isStorageActive())
+ {
+ Net::getInventoryHandler()->moveItem2(Inventory::INVENTORY,
+ item->getInvIndex(), item->getQuantity(),
+ Inventory::STORAGE);
+ }
+ else
+ {
+ if (inputManager.isActionActive(static_cast<int>(Input::KEY_MOD)))
+ {
+ Net::getInventoryHandler()->dropItem(
+ item, item->getQuantity());
+ }
+ else
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop,
+ this, item);
+ }
+ }
+ }
+ else if (eventId == "split")
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item,
+ (item->getQuantity() - 1));
+ }
+ else if (eventId == "retrieve")
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove,
+ this, item);
+ }
+}
+
+Item *InventoryWindow::getSelectedItem() const
+{
+ return mItems->getSelectedItem();
+}
+
+void InventoryWindow::unselectItem()
+{
+ mItems->selectNone();
+}
+
+void InventoryWindow::widgetHidden(const gcn::Event &event)
+{
+ Window::widgetHidden(event);
+ mItems->hidePopup();
+}
+
+void InventoryWindow::mouseClicked(gcn::MouseEvent &event)
+{
+ Window::mouseClicked(event);
+
+ const int clicks = event.getClickCount();
+
+ if (clicks == 2 && gui)
+ gui->resetClickCount();
+
+ const bool mod = (isStorageActive() && inputManager.isActionActive(
+ static_cast<int>(Input::KEY_MOD)));
+
+ const bool mod2 = (tradeWindow && tradeWindow->isWindowVisible()
+ && inputManager.isActionActive(static_cast<int>(Input::KEY_MOD)));
+
+ if (!mod && !mod2 && event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ Item *const 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 (!mInventory)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT
+ || event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ Item *const item = mItems->getSelectedItem();
+
+ if (!item)
+ return;
+
+ if (mod)
+ {
+ if (mInventory->isMainInventory())
+ {
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd,
+ inventoryWindow, item);
+ }
+ else
+ {
+ Net::getInventoryHandler()->moveItem2(Inventory::INVENTORY,
+ item->getInvIndex(), item->getQuantity(),
+ Inventory::STORAGE);
+ }
+ }
+ else
+ {
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove,
+ inventoryWindow, item);
+ }
+ else
+ {
+ Net::getInventoryHandler()->moveItem2(Inventory::STORAGE,
+ item->getInvIndex(), item->getQuantity(),
+ Inventory::INVENTORY);
+ }
+ }
+ }
+ else if (mod2 && mInventory->isMainInventory())
+ {
+ if (PlayerInfo::isItemProtected(item->getId()))
+ return;
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::TradeAdd,
+ tradeWindow, item);
+ }
+ else
+ {
+ if (tradeWindow)
+ tradeWindow->tradeItem(item, item->getQuantity(), true);
+ }
+ }
+ else if (clicks == 2)
+ {
+ if (mInventory->isMainInventory())
+ {
+ if (isStorageActive())
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd,
+ inventoryWindow, item);
+ }
+ else if (tradeWindow && tradeWindow->isWindowVisible())
+ {
+ if (PlayerInfo::isItemProtected(item->getId()))
+ return;
+ ItemAmountWindow::showWindow(ItemAmountWindow::TradeAdd,
+ tradeWindow, item);
+ }
+ else
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ Net::getInventoryHandler()->unequipItem(item);
+ else
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ else
+ {
+ if (PlayerInfo::isItemProtected(item->getId()))
+ return;
+ Net::getInventoryHandler()->useItem(item);
+ }
+ }
+ }
+ else
+ {
+ if (isStorageActive())
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove,
+ inventoryWindow, item);
+ }
+ }
+ }
+ }
+}
+
+void InventoryWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ Window::mouseMoved(event);
+ const gcn::Widget *const src = event.getSource();
+ if (src == mSlotsBar || src == mWeightBar)
+ {
+ const int x = event.getX();
+ const int y = event.getY();
+ const gcn::Rectangle &rect = mDimension;
+ mTextPopup->show(rect.x + x, rect.y + y, strprintf(_("Money: %s"),
+ Units::formatCurrency(PlayerInfo::getAttribute(
+ PlayerInfo::MONEY)).c_str()));
+ }
+ else
+ {
+ mTextPopup->hide();
+ }
+}
+
+void InventoryWindow::mouseExited(gcn::MouseEvent &event A_UNUSED)
+{
+ mTextPopup->hide();
+}
+
+void InventoryWindow::keyPressed(gcn::KeyEvent &event)
+{
+ if (static_cast<KeyEvent*>(&event)->getActionId()
+ == static_cast<int>(Input::KEY_GUI_MOD))
+ {
+ mSplit = true;
+ }
+}
+
+void InventoryWindow::keyReleased(gcn::KeyEvent &event)
+{
+ if (static_cast<KeyEvent*>(&event)->getActionId()
+ == static_cast<int>(Input::KEY_GUI_MOD))
+ {
+ mSplit = false;
+ }
+}
+
+void InventoryWindow::valueChanged(const gcn::SelectionEvent &event A_UNUSED)
+{
+ if (!mInventory || !mInventory->isMainInventory())
+ return;
+
+ Item *const item = mItems->getSelectedItem();
+
+ if (mSplit && item && Net::getInventoryHandler()->
+ canSplit(mItems->getSelectedItem()))
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit,
+ this, item, item->getQuantity() - 1);
+ }
+ updateButtons(item);
+}
+
+void InventoryWindow::updateButtons(const Item *item)
+{
+ if (!mInventory || !mInventory->isMainInventory())
+ return;
+
+ const Item *const selectedItem = mItems->getSelectedItem();
+ if (item && selectedItem != item)
+ return;
+
+ if (!item)
+ item = selectedItem;
+
+ if (!item || item->getQuantity() == 0)
+ {
+ if (mUseButton)
+ mUseButton->setEnabled(true);
+ if (mDropButton)
+ mDropButton->setEnabled(true);
+ return;
+ }
+
+ if (mUseButton)
+ mUseButton->setEnabled(true);
+ if (mDropButton)
+ mDropButton->setEnabled(true);
+
+ if (mUseButton)
+ {
+ if (item->isEquipment())
+ {
+ if (item->isEquipped())
+ {
+ // TRANSLATORS: inventory button
+ mUseButton->setCaption(_("Unequip"));
+ }
+ else
+ {
+ // TRANSLATORS: inventory button
+ mUseButton->setCaption(_("Equip"));
+ }
+ }
+ else
+ {
+ // TRANSLATORS: inventory button
+ mUseButton->setCaption(_("Use"));
+ }
+ }
+
+ updateDropButton();
+
+ if (mSplitButton)
+ {
+ if (Net::getInventoryHandler()->canSplit(item))
+ mSplitButton->setEnabled(true);
+ else
+ mSplitButton->setEnabled(false);
+ }
+}
+
+void InventoryWindow::setSplitAllowed(const bool allowed)
+{
+ mSplitButton->setVisible(allowed);
+}
+
+void InventoryWindow::close()
+{
+ if (this == inventoryWindow)
+ {
+ setVisible(false);
+ }
+ else
+ {
+ if (Net::getInventoryHandler())
+ Net::getInventoryHandler()->closeStorage(Inventory::STORAGE);
+ scheduleDelete();
+ }
+}
+
+void InventoryWindow::processEvent(const Channels channel A_UNUSED,
+ const DepricatedEvent &event)
+{
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ const int id = event.getInt("id");
+ if (id == PlayerInfo::TOTAL_WEIGHT || id == PlayerInfo::MAX_WEIGHT)
+ updateWeight();
+ }
+}
+
+void InventoryWindow::updateWeight()
+{
+ if (!isMainInventory())
+ return;
+
+ const int total = PlayerInfo::getAttribute(PlayerInfo::TOTAL_WEIGHT);
+ const int max = PlayerInfo::getAttribute(PlayerInfo::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 *const 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));
+ mItems->updateMatrix();
+ }
+}
+
+void InventoryWindow::updateDropButton()
+{
+ if (!mDropButton)
+ return;
+
+ if (isStorageActive())
+ {
+ // TRANSLATORS: inventory button
+ mDropButton->setCaption(_("Store"));
+ }
+ else
+ {
+ const Item *const item = mItems->getSelectedItem();
+ if (item && item->getQuantity() > 1)
+ {
+ // TRANSLATORS: inventory button
+ mDropButton->setCaption(_("Drop..."));
+ }
+ else
+ {
+ // TRANSLATORS: inventory button
+ mDropButton->setCaption(_("Drop"));
+ }
+ }
+}
+
+bool InventoryWindow::isInputFocused() const
+{
+ return mNameFilter && mNameFilter->isFocused();
+}
+
+bool InventoryWindow::isAnyInputFocused()
+{
+ FOR_EACH (WindowList::const_iterator, it, invInstances)
+ {
+ if ((*it) && (*it)->isInputFocused())
+ return true;
+ }
+ return false;
+}
+
+void InventoryWindow::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+
+ if (!isMainInventory())
+ return;
+
+ if (getWidth() < 600)
+ {
+ if (!mCompactMode)
+ {
+ mNameFilter->setVisible(false);
+ mNameFilterCell->setType(LayoutCell::NONE);
+ mFilterCell->setWidth(mFilterCell->getWidth() + 2);
+ mCompactMode = true;
+ }
+ }
+ else if (mCompactMode)
+ {
+ mNameFilter->setVisible(true);
+ mNameFilterCell->setType(LayoutCell::WIDGET);
+ mFilterCell->setWidth(mFilterCell->getWidth() - 2);
+ mCompactMode = false;
+ }
+}
+
+void InventoryWindow::setVisible(bool visible)
+{
+ if (!visible)
+ mSortDropDown->hideDrop();
+ Window::setVisible(visible);
+}
diff --git a/src/gui/windows/inventorywindow.h b/src/gui/windows/inventorywindow.h
new file mode 100644
index 000000000..7bd4ce466
--- /dev/null
+++ b/src/gui/windows/inventorywindow.h
@@ -0,0 +1,196 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_INVENTORYWINDOW_H
+#define GUI_INVENTORYWINDOW_H
+
+#include "inventory.h"
+#include "depricatedlistener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+class Button;
+class DropDown;
+class Item;
+class ItemContainer;
+class Label;
+class LayoutCell;
+class ProgressBar;
+class SortListModelInv;
+class TabStrip;
+class TextField;
+class TextPopup;
+
+/**
+ * Inventory dialog.
+ *
+ * \ingroup Interface
+ */
+class InventoryWindow final : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener,
+ public gcn::SelectionListener,
+ public InventoryListener,
+ public DepricatedListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ explicit InventoryWindow(Inventory *const inventory);
+
+ A_DELETE_COPY(InventoryWindow)
+
+ /**
+ * Destructor.
+ */
+ ~InventoryWindow();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Returns the selected item.
+ */
+ Item* getSelectedItem() const A_WARN_UNUSED;
+
+ /**
+ * Unselect item
+ */
+ void unselectItem();
+
+ /**
+ * Handles closing of the window
+ */
+ void widgetHidden(const gcn::Event &event) override;
+
+ /**
+ * Handles the mouse clicks.
+ */
+ void mouseClicked(gcn::MouseEvent &event) override;
+
+ /**
+ * Handles the key presses.
+ */
+ void keyPressed(gcn::KeyEvent &event) override;
+
+ /**
+ * Handles the key releases.
+ */
+ void keyReleased(gcn::KeyEvent &event) override;
+
+ /**
+ * Updates labels to currently selected item.
+ */
+ void valueChanged(const gcn::SelectionEvent &event) override;
+
+ /**
+ * Sets whether the split button should be shown.
+ */
+ void setSplitAllowed(const bool allowed);
+
+ /**
+ * Closes the Storage Window, as well as telling the server that the
+ * window has been closed.
+ */
+ void close();
+
+ void slotsChanged(Inventory *const inventory);
+
+ bool isMainInventory() const A_WARN_UNUSED
+ { return mInventory->isMainInventory(); }
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isStorageActive() A_WARN_UNUSED
+ { return invInstances.size() > 1; }
+
+ void updateDropButton();
+
+ void processEvent(const Channels channel,
+ const DepricatedEvent &event) override;
+
+ void updateButtons(const Item *item = nullptr);
+
+ bool isInputFocused() const A_WARN_UNUSED;
+
+ void widgetResized(const gcn::Event &event) override;
+
+ void mouseMoved(gcn::MouseEvent &event) override;
+
+ void mouseExited(gcn::MouseEvent &event) override;
+
+ void setVisible(bool visible) override;
+
+ static bool isAnyInputFocused();
+
+ private:
+ /**
+ * Updates the weight bar.
+ */
+ void updateWeight();
+
+ typedef std::list<InventoryWindow*> WindowList;
+ static WindowList invInstances;
+
+ Inventory *mInventory;
+ ItemContainer *mItems;
+
+ std::string mWeight;
+ std::string mSlots;
+
+ Button *mUseButton;
+ Button *mDropButton;
+ Button *mSplitButton;
+ Button *mOutfitButton;
+ Button *mShopButton;
+ Button *mEquipmentButton;
+ Button *mStoreButton;
+ Button *mRetrieveButton;
+ Button *mInvCloseButton;
+
+ ProgressBar *mWeightBar;
+ ProgressBar *mSlotsBar;
+ TabStrip *mFilter;
+ SortListModelInv *mSortModel;
+ DropDown *mSortDropDown;
+ TextField *mNameFilter;
+ LayoutCell *mSortDropDownCell;
+ LayoutCell *mNameFilterCell;
+ LayoutCell *mFilterCell;
+ LayoutCell *mSlotsBarCell;
+ TextPopup *mTextPopup;
+
+ bool mSplit;
+ bool mCompactMode;
+};
+
+extern InventoryWindow *inventoryWindow;
+
+#endif // GUI_INVENTORYWINDOW_H
diff --git a/src/gui/windows/itemamountwindow.cpp b/src/gui/windows/itemamountwindow.cpp
new file mode 100644
index 000000000..7c67fb976
--- /dev/null
+++ b/src/gui/windows/itemamountwindow.cpp
@@ -0,0 +1,451 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/itemamountwindow.h"
+
+#include "inventory.h"
+#include "item.h"
+
+#include "input/keyboardconfig.h"
+
+#include "net/inventoryhandler.h"
+#include "gui/itempopup.h"
+#include "net/net.h"
+#include "gui/viewport.h"
+
+#include "gui/windows/shopwindow.h"
+#include "gui/windows/tradewindow.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 "utils/gettext.h"
+
+#include <math.h>
+
+#include "debug.h"
+
+class ItemsModal final : public gcn::ListModel
+{
+public:
+ ItemsModal() :
+ mStrings()
+ {
+ const std::map<int, ItemInfo*> &items = ItemDB::getItemInfos();
+ std::list<std::string> tempStrings;
+
+ for (std::map<int, ItemInfo*>::const_iterator
+ i = items.begin(), i_end = items.end();
+ i != i_end; ++i)
+ {
+ if (i->first < 0)
+ continue;
+
+ const ItemInfo &info = *i->second;
+ const std::string name = info.getName();
+ if (name != "unnamed" && !info.getName().empty()
+ && info.getName() != "unnamed")
+ {
+ tempStrings.push_back(name);
+ }
+ }
+ tempStrings.sort();
+ FOR_EACH (std::list<std::string>::const_iterator, i, tempStrings)
+ mStrings.push_back(*i);
+ }
+
+ A_DELETE_COPY(ItemsModal)
+
+ ~ItemsModal()
+ { }
+
+ int getNumberOfElements() override
+ {
+ return static_cast<int>(mStrings.size());
+ }
+
+ std::string getElementAt(int i) override
+ {
+ if (i < 0 || i >= getNumberOfElements())
+ return "???";
+ return mStrings.at(i);
+ }
+private:
+ StringVect mStrings;
+};
+
+void ItemAmountWindow::finish(Item *const item, const int amount,
+ const int price, const 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()->moveItem2(Inventory::INVENTORY,
+ item->getInvIndex(), amount, Inventory::STORAGE);
+ break;
+ case StoreRemove:
+ Net::getInventoryHandler()->moveItem2(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(const Usage usage, Window *const parent,
+ Item *const item, const int maxRange) :
+ Window("", false, parent, "amount.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mItemAmountTextField(new IntTextField(this, 1)),
+ mItemPriceTextField(nullptr),
+ mGPLabel(nullptr),
+ mItem(item),
+ mItemIcon(new Icon(this, item ? item->getImage() : nullptr)),
+ mMax(maxRange),
+ mUsage(usage),
+ mItemPopup(new ItemPopup),
+ mItemAmountSlide(new Slider(1.0, mMax)),
+ mItemPriceSlide(nullptr),
+ mItemDropDown(nullptr),
+ mItemsModal(nullptr),
+ mPrice(0),
+ mEnabledKeyboard(keyboard.isEnabled())
+{
+ if (!mItem)
+ {
+ setVisible(false);
+ return;
+ }
+ if (usage == ShopBuyAdd)
+ mMax = 10000;
+ else if (!mMax)
+ mMax = mItem->getQuantity();
+
+ keyboard.setEnabled(false);
+
+ mItemAmountTextField->setRange(1, mMax);
+ mItemAmountTextField->setWidth(35);
+ mItemAmountTextField->addKeyListener(this);
+
+ mItemAmountSlide->setHeight(10);
+ mItemAmountSlide->setActionEventId("slide");
+ mItemAmountSlide->addActionListener(this);
+
+ if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd)
+ {
+ mItemPriceTextField = new IntTextField(this, 1);
+ mItemPriceTextField->setRange(1, 10000000);
+ mItemPriceTextField->setWidth(35);
+ mItemPriceTextField->addKeyListener(this);
+
+ mItemPriceSlide = new Slider(1.0, 10000000);
+ mItemPriceSlide->setHeight(10);
+ mItemPriceSlide->setActionEventId("slidePrice");
+ mItemPriceSlide->addActionListener(this);
+
+ mGPLabel = new Label(this, " GP");
+ }
+
+ if (mUsage == ShopBuyAdd)
+ {
+ mItemsModal = new ItemsModal;
+ mItemDropDown = new DropDown(this, mItemsModal);
+ mItemDropDown->setActionEventId("itemType");
+ mItemDropDown->addActionListener(this);
+ }
+
+ // Buttons
+ // TRANSLATORS: item amount window button
+ Button *const minusAmountButton = new Button(this, _("-"), "dec", this);
+ // TRANSLATORS: item amount window button
+ Button *const plusAmountButton = new Button(this, _("+"), "inc", this);
+ // TRANSLATORS: item amount window button
+ Button *const okButton = new Button(this, _("OK"), "ok", this);
+ // TRANSLATORS: item amount window button
+ Button *const cancelButton = new Button(this, _("Cancel"), "cancel", this);
+ // TRANSLATORS: item amount window button
+ Button *const addAllButton = new Button(this, _("All"), "all", this);
+
+ minusAmountButton->adjustSize();
+ minusAmountButton->setWidth(plusAmountButton->getWidth());
+
+ // Set positions
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+ int n = 0;
+ if (mUsage == ShopBuyAdd)
+ {
+ placer(0, n, mItemDropDown, 8);
+ n++;
+ }
+ placer(1, n, minusAmountButton);
+ placer(2, n, mItemAmountTextField, 3);
+ placer(5, n, plusAmountButton);
+ placer(6, n, addAllButton);
+
+ placer(0, n, mItemIcon, 1, 3);
+ placer(1, n + 1, mItemAmountSlide, 7);
+
+ if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd)
+ {
+ Button *const minusPriceButton = new Button(
+ // TRANSLATORS: item amount window button
+ this, _("-"), "decPrice", this);
+ Button *const plusPriceButton = new Button(
+ // TRANSLATORS: item amount window button
+ this, _("+"), "incPrice", this);
+ minusPriceButton->adjustSize();
+ minusPriceButton->setWidth(plusPriceButton->getWidth());
+
+ placer(1, n + 2, minusPriceButton);
+ placer(2, n + 2, mItemPriceTextField, 3);
+ placer(5, n + 2, plusPriceButton);
+ placer(6, n + 2, mGPLabel);
+
+ placer(1, n + 3, mItemPriceSlide, 7);
+ placer(4, n + 5, cancelButton);
+ placer(5, n + 5, okButton);
+ }
+ else
+ {
+ placer(4, n + 2, cancelButton);
+ placer(5, n + 2, okButton);
+ }
+
+ reflowLayout(225, 0);
+
+ resetAmount();
+
+ switch (usage)
+ {
+ case TradeAdd:
+ // TRANSLATORS: amount window message
+ setCaption(_("Select amount of items to trade."));
+ break;
+ case ItemDrop:
+ // TRANSLATORS: amount window message
+ setCaption(_("Select amount of items to drop."));
+ break;
+ case StoreAdd:
+ // TRANSLATORS: amount window message
+ setCaption(_("Select amount of items to store."));
+ break;
+ case StoreRemove:
+ // TRANSLATORS: amount window message
+ setCaption(_("Select amount of items to retrieve."));
+ break;
+ case ItemSplit:
+ // TRANSLATORS: amount window message
+ setCaption(_("Select amount of items to split."));
+ break;
+ case ShopBuyAdd:
+ // TRANSLATORS: amount window message
+ setCaption(_("Add to buy shop."));
+ break;
+ case ShopSellAdd:
+ // TRANSLATORS: amount window message
+ setCaption(_("Add to sell shop."));
+ break;
+ default:
+ // TRANSLATORS: amount window message
+ setCaption(_("Unknown."));
+ break;
+ }
+
+ setLocationRelativeTo(getParentWindow());
+ setVisible(true);
+
+ mItemIcon->addMouseListener(this);
+}
+
+ItemAmountWindow::~ItemAmountWindow()
+{
+ delete mItemPopup;
+ mItemPopup = nullptr;
+}
+
+// Show ItemTooltip
+void ItemAmountWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ Window::mouseMoved(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 A_UNUSED)
+{
+ if (mItemPopup)
+ mItemPopup->setVisible(false);
+}
+
+void ItemAmountWindow::resetAmount()
+{
+ mItemAmountTextField->setValue(1);
+}
+
+void ItemAmountWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "cancel")
+ {
+ close();
+ return;
+ }
+ else if (eventId == "ok")
+ {
+ if (mItemPriceTextField)
+ {
+ finish(mItem, mItemAmountTextField->getValue(),
+ mItemPriceTextField->getValue(), mUsage);
+ }
+ else
+ {
+ finish(mItem, mItemAmountTextField->getValue(),
+ 0, mUsage);
+ }
+ close();
+ return;
+ }
+ else if (eventId == "itemType")
+ {
+ if (!mItemDropDown || !mItemsModal)
+ return;
+
+ const int id = ItemDB::get(mItemsModal->getElementAt(
+ mItemDropDown->getSelected())).getId();
+
+ mItem = new Item(id, 10000);
+
+ if (mUsage == ShopBuyAdd)
+ mMax = 10000;
+ else if (!mMax)
+ mMax = mItem->getQuantity();
+
+ mItemIcon->setImage(mItem->getImage());
+ }
+
+ int amount = mItemAmountTextField->getValue();
+
+ if (eventId == "inc" && amount < mMax)
+ amount++;
+ else if (eventId == "dec" && amount > 1)
+ amount--;
+ else if (eventId == "all")
+ amount = mMax;
+ else if (eventId == "slide")
+ amount = static_cast<int>(mItemAmountSlide->getValue());
+ mItemAmountTextField->setValue(amount);
+ mItemAmountSlide->setValue(amount);
+
+ if (mItemPriceTextField && mItemPriceSlide)
+ {
+ if (mPrice > 7)
+ mPrice = 7;
+ else if (mPrice < 0)
+ mPrice = 0;
+
+ int price = 0;
+
+ if (eventId == "incPrice")
+ {
+ mPrice++;
+ price = static_cast<int>(pow(10.0, mPrice));
+ mItemPriceTextField->setValue(price);
+ mItemPriceSlide->setValue(price);
+ }
+ else if (eventId == "decPrice")
+ {
+ mPrice--;
+ price = static_cast<int>(pow(10.0, mPrice));
+ mItemPriceTextField->setValue(price);
+ mItemPriceSlide->setValue(price);
+ }
+ else if (eventId == "slidePrice")
+ {
+ price = static_cast<int>(mItemPriceSlide->getValue());
+ if (price)
+ mPrice = static_cast<int>(log(static_cast<float>(price)));
+ else
+ mPrice = 0;
+ mItemPriceTextField->setValue(price);
+ mItemPriceSlide->setValue(price);
+ }
+ }
+}
+
+void ItemAmountWindow::close()
+{
+ keyboard.setEnabled(mEnabledKeyboard);
+ scheduleDelete();
+}
+
+void ItemAmountWindow::keyReleased(gcn::KeyEvent &keyEvent A_UNUSED)
+{
+ mItemAmountSlide->setValue(mItemAmountTextField->getValue());
+}
+
+void ItemAmountWindow::showWindow(const Usage usage, Window *const parent,
+ Item *const 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/windows/itemamountwindow.h b/src/gui/windows/itemamountwindow.h
new file mode 100644
index 000000000..1c95980fb
--- /dev/null
+++ b/src/gui/windows/itemamountwindow.h
@@ -0,0 +1,126 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_ITEMAMOUNTWINDOW_H
+#define GUI_ITEMAMOUNTWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/keylistener.hpp>
+#include <guichan/actionlistener.hpp>
+
+class DropDown;
+class Icon;
+class IntTextField;
+class Item;
+class ItemsModal;
+class ItemPopup;
+class Label;
+class Slider;
+
+/**
+ * Window used for selecting the amount of items to drop, trade or split.
+ *
+ * \ingroup Interface
+ */
+class ItemAmountWindow final : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ enum Usage
+ {
+ TradeAdd = 0,
+ ItemDrop,
+ StoreAdd,
+ StoreRemove,
+ ItemSplit,
+ ShopBuyAdd,
+ ShopSellAdd
+ };
+
+ A_DELETE_COPY(ItemAmountWindow)
+
+ /**
+ * Called when receiving actions from widget.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Sets default amount value.
+ */
+ void resetAmount();
+
+ // MouseListener
+ void mouseMoved(gcn::MouseEvent &event) override;
+
+ void mouseExited(gcn::MouseEvent &event) override;
+
+ /**
+ * Schedules the Item Amount window for deletion.
+ */
+ void close();
+
+ void keyReleased(gcn::KeyEvent &keyEvent) override;
+
+ /**
+ * Creates the dialog, or bypass it if there aren't enough items.
+ */
+ static void showWindow(const Usage usage, Window *const parent,
+ Item *const item, int maxRange = 0);
+
+ ~ItemAmountWindow();
+
+ private:
+ static void finish(Item *const item, const int amount,
+ const int price, const Usage usage);
+
+ ItemAmountWindow(const Usage usage, Window *const parent,
+ Item *const item, const 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.
+ */
+ Slider *mItemAmountSlide;
+
+ Slider *mItemPriceSlide;
+
+ DropDown *mItemDropDown;
+
+ ItemsModal *mItemsModal;
+
+ int mPrice;
+
+ bool mEnabledKeyboard;
+};
+
+#endif // GUI_ITEMAMOUNTWINDOW_H
diff --git a/src/gui/windows/killstats.cpp b/src/gui/windows/killstats.cpp
new file mode 100644
index 000000000..ee265e2b9
--- /dev/null
+++ b/src/gui/windows/killstats.cpp
@@ -0,0 +1,520 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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/windows/killstats.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+
+#include "actorspritemanager.h"
+#include "game.h"
+
+#include "being/localplayer.h"
+#include "being/playerinfo.h"
+
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+
+#include "debug.h"
+
+KillStats::KillStats() :
+ // TRANSLATORS: kill stats window name
+ Window(_("Kill stats"), false, nullptr, "killstats.xml"),
+ gcn::ActionListener(),
+ mKillCounter(0),
+ mExpCounter(0),
+ mKillTCounter(0),
+ mExpTCounter(0),
+ mKillTimer(0),
+ // TRANSLATORS: kill stats window button
+ mResetButton(new Button(this, _("Reset stats"), "reset", this)),
+ // TRANSLATORS: kill stats window button
+ mTimerButton(new Button(this, _("Reset timer"), "timer", this)),
+ mLine1(nullptr),
+ mLine2(nullptr),
+ mLine3(nullptr),
+ // TRANSLATORS: kill stats window label
+ mLine4(new Label(this, strprintf(_("Kills: %s, total exp: %s"),
+ "?", "?"))),
+ // TRANSLATORS: kill stats window label
+ mLine5(new Label(this, strprintf(_("Avg Exp: %s"), "?"))),
+ // TRANSLATORS: kill stats window label
+ mLine6(new Label(this, strprintf(_("No. of avg mob to next level: %s"),
+ "?"))),
+ // TRANSLATORS: kill stats window label
+ mLine7(new Label(this, strprintf(_("Kills/Min: %s, Exp/Min: %s"),
+ "?", "?"))),
+ mExpSpeed1Label(new Label(this, strprintf(ngettext(
+ // TRANSLATORS: kill stats window label
+ "Exp speed per %d min: %s", "Exp speed per %d min: %s", 1), 1, "?"))),
+ mExpTime1Label(new Label(this, strprintf(ngettext(
+ "Time for next level per %d min: %s",
+ "Time for next level per %d min: %s", 1), 1, "?"))),
+ mExpSpeed5Label(new Label(this, strprintf(ngettext(
+ "Exp speed per %d min: %s", "Exp speed per %d min: %s", 5), 5, "?"))),
+ mExpTime5Label(new Label(this, strprintf(ngettext(
+ "Time for next level per %d min: %s",
+ "Time for next level per %d min: %s", 5), 5, "?"))),
+ mExpSpeed15Label(new Label(this, strprintf(ngettext(
+ "Exp speed per %d min: %s", "Exp speed per %d min: %s", 15),
+ 15, "?"))),
+ mExpTime15Label(new Label(this, strprintf(ngettext(
+ "Time for next level per %d min: %s",
+ "Time for next level per %d min: %s", 15), 15, "?"))),
+ // TRANSLATORS: kill stats window label
+ mLastKillExpLabel(new Label(this, strprintf("%s ?", _("Last kill exp:")))),
+ mTimeBeforeJackoLabel(new Label(this, strprintf(
+ // TRANSLATORS: kill stats window label
+ "%s ?", _("Time before jacko spawn:")))),
+ m1minExpTime(0),
+ m1minExpNum(0),
+ m1minSpeed(0),
+ m5minExpTime(0),
+ m5minExpNum(0),
+ m5minSpeed(0),
+ m15minExpTime(0),
+ m15minExpNum(0),
+ m15minSpeed(0),
+ mJackoSpawnTime(0),
+ mJackoId(0),
+ mIsJackoAlive(false),
+ mIsJackoMustSpawn(true),
+ mIsJackoSpawnTimeUnknown(true)
+{
+ setWindowName("Kill stats");
+ setCloseButton(true);
+ setResizable(true);
+ setSaveVisible(true);
+ setStickyButtonLock(true);
+ setDefaultSize(250, 250, 350, 300);
+
+ listen(CHANNEL_ATTRIBUTES);
+ const int xp(PlayerInfo::getAttribute(PlayerInfo::EXP));
+ int xpNextLevel(PlayerInfo::getAttribute(PlayerInfo::EXP_NEEDED));
+
+ if (!xpNextLevel)
+ xpNextLevel = 1;
+
+ // TRANSLATORS: kill stats window label
+ mLine1 = new Label(this, strprintf(_("Level: %d at %f%%"),
+ player_node->getLevel(), static_cast<double>(xp)
+ / static_cast<double>(xpNextLevel) * 100.0));
+
+ // TRANSLATORS: kill stats window label
+ mLine2 = new Label(this, strprintf(_("Exp: %d/%d Left: %d"),
+ xp, xpNextLevel, xpNextLevel - xp));
+
+ // TRANSLATORS: kill stats window label
+ mLine3 = new Label(this, strprintf(_("1%% = %d exp, avg mob for 1%%: %s"),
+ xpNextLevel / 100, "?"));
+
+ 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, mLine7, 6).setPadding(0);
+
+ place(0, 7, mLastKillExpLabel, 6).setPadding(0);
+ place(0, 8, mTimeBeforeJackoLabel, 6).setPadding(0);
+ place(0, 9, mExpSpeed1Label, 6).setPadding(0);
+ place(0, 10, mExpTime1Label, 6).setPadding(0);
+ place(0, 11, mExpSpeed5Label, 6).setPadding(0);
+ place(0, 12, mExpTime5Label, 6).setPadding(0);
+ place(0, 13, mExpSpeed15Label, 6).setPadding(0);
+ place(0, 14, mExpTime15Label, 6).setPadding(0);
+
+ place(5, 13, mTimerButton).setPadding(0);
+ place(5, 14, mResetButton).setPadding(0);
+
+ loadWindowState();
+ enableVisibleSound(true);
+}
+
+KillStats::~KillStats()
+{
+}
+
+void KillStats::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "reset")
+ {
+ mKillCounter = 0;
+ mExpCounter = 0;
+ mLine3->setCaption(strprintf("1%% = %d exp, avg mob for 1%%: %s",
+ PlayerInfo::getAttribute(PlayerInfo::EXP_NEEDED) / 100, "?"));
+ // TRANSLATORS: kill stats window label
+ mLine4->setCaption(strprintf(_("Kills: %s, total exp: %s"), "?", "?"));
+ // TRANSLATORS: kill stats window label
+ mLine5->setCaption(strprintf(_("Avg Exp: %s"), "?"));
+ mLine6->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _("No. of avg mob to next level: %s"), "?"));
+
+ resetTimes();
+ }
+ else if (eventId == "timer")
+ {
+ mKillTimer = 0;
+ mKillTCounter = 0;
+ mExpTCounter = 0;
+ mLine7->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _("Kills/Min: %s, Exp/Min: %s"), "?", "?"));
+
+ resetTimes();
+ }
+}
+
+void KillStats::resetTimes()
+{
+ m1minExpTime = 0;
+ m1minExpNum = 0;
+ m1minSpeed = 0;
+ m5minExpTime = 0;
+ m5minExpNum = 0;
+ m5minSpeed = 0;
+ m15minExpTime = 0;
+ m15minExpNum = 0;
+ m15minSpeed = 0;
+}
+
+void KillStats::gainXp(int xp)
+{
+ const int expNeed = PlayerInfo::getAttribute(PlayerInfo::EXP_NEEDED);
+ if (xp == expNeed)
+ xp = 0;
+ else if (!xp)
+ return;
+
+ mKillCounter++;
+ mKillTCounter++;
+
+ mExpCounter = mExpCounter + xp;
+ mExpTCounter = mExpTCounter + xp;
+ if (!mKillCounter)
+ mKillCounter = 1;
+
+ const float AvgExp = static_cast<float>(mExpCounter / mKillCounter);
+ int xpNextLevel(expNeed);
+
+ if (mKillTimer == 0)
+ mKillTimer = cur_time;
+
+ if (!xpNextLevel)
+ xpNextLevel = 1;
+
+ double timeDiff = difftime(cur_time, mKillTimer) / 60;
+
+ if (timeDiff <= 0.001)
+ timeDiff = 1;
+
+ const int exp = PlayerInfo::getAttribute(PlayerInfo::EXP);
+ // TRANSLATORS: kill stats window label
+ mLine1->setCaption(strprintf(_("Level: %d at %f%%"),
+ player_node->getLevel(), static_cast<double>(exp)
+ / static_cast<double>(xpNextLevel) * 100.0));
+
+ // TRANSLATORS: kill stats window label
+ mLine2->setCaption(strprintf(_("Exp: %d/%d Left: %d"), exp,
+ xpNextLevel, xpNextLevel - exp));
+
+ if (AvgExp >= 0.001F && AvgExp <= 0.001F)
+ {
+ // TRANSLATORS: kill stats window label
+ mLine3->setCaption(strprintf(_("1%% = %d exp, avg mob for 1%%: %s"),
+ xpNextLevel / 100, "?"));
+
+ // TRANSLATORS: kill stats window label
+ mLine5->setCaption(strprintf(_("Avg Exp: %s"),
+ toString(AvgExp).c_str()));
+
+ mLine6->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _("No. of avg mob to next level: %s"), "?"));
+ }
+ else
+ {
+ // TRANSLATORS: kill stats window label
+ mLine3->setCaption(strprintf(_("1%% = %d exp, avg mob for 1%%: %s"),
+ xpNextLevel / 100, toString((static_cast<float>(
+ xpNextLevel) / 100) / AvgExp).c_str()));
+
+ // TRANSLATORS: kill stats window label
+ mLine5->setCaption(strprintf(_("Avg Exp: %s"),
+ toString(AvgExp).c_str()));
+
+ // TRANSLATORS: kill stats window label
+ mLine6->setCaption(strprintf(_("No. of avg mob to next level: %s"),
+ toString(static_cast<float>(xpNextLevel - exp) / AvgExp).c_str()));
+ }
+ // TRANSLATORS: kill stats window label
+ mLine4->setCaption(strprintf(_("Kills: %s, total exp: %s"),
+ toString(mKillCounter).c_str(), toString(mExpCounter).c_str()));
+
+ // TRANSLATORS: kill stats window label
+ mLine7->setCaption(strprintf(_("Kills/Min: %s, Exp/Min: %s"),
+ toString(mKillTCounter / timeDiff).c_str(),
+ toString(mExpTCounter / timeDiff).c_str()));
+
+ // TRANSLATORS: kill stats window label
+ mLastKillExpLabel->setCaption(strprintf("%s %d", _("Last kill exp:"), xp));
+
+ recalcStats();
+ update();
+}
+
+void KillStats::recalcStats()
+{
+ BLOCK_START("KillStats::recalcStats")
+ const int curTime = cur_time;
+
+ // Need Update Exp Counter
+ if (curTime - m1minExpTime > 60)
+ {
+ const int newExp = PlayerInfo::getAttribute(PlayerInfo::EXP);
+ if (m1minExpTime != 0)
+ m1minSpeed = newExp - m1minExpNum;
+ else
+ m1minSpeed = 0;
+ m1minExpTime = curTime;
+ m1minExpNum = newExp;
+ }
+
+ if (curTime - m5minExpTime > 60*5)
+ {
+ const int newExp = PlayerInfo::getAttribute(PlayerInfo::EXP);
+ if (m5minExpTime != 0)
+ m5minSpeed = newExp - m5minExpNum;
+ else
+ m5minSpeed = 0;
+ m5minExpTime = curTime;
+ m5minExpNum = newExp;
+ }
+
+ if (curTime - m15minExpTime > 60*15)
+ {
+ const int newExp = PlayerInfo::getAttribute(PlayerInfo::EXP);
+ if (m15minExpTime != 0)
+ m15minSpeed = newExp - m15minExpNum;
+ else
+ m15minSpeed = 0;
+ m15minExpTime = curTime;
+ m15minExpNum = newExp;
+ }
+ validateJacko();
+ BLOCK_END("KillStats::recalcStats")
+}
+
+void KillStats::update()
+{
+ BLOCK_START("KillStats::update")
+
+ mExpSpeed1Label->setCaption(strprintf(ngettext("Exp speed per %d min: %s",
+ "Exp speed per %d min: %s", 1), 1, toString(m1minSpeed).c_str()));
+
+ if (m1minSpeed != 0)
+ {
+ // TRANSLATORS: kill stats window label
+ mExpTime1Label->setCaption(strprintf(_(" Time for next level: %s"),
+ toString(static_cast<float>((PlayerInfo::getAttribute(
+ PlayerInfo::EXP_NEEDED) - PlayerInfo::getAttribute(
+ PlayerInfo::EXP)) / m1minSpeed)).c_str()));
+ }
+ else
+ {
+ mExpTime1Label->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _(" Time for next level: %s"), "?"));
+ }
+ mExpTime1Label->adjustSize();
+
+ mExpSpeed5Label->setCaption(strprintf(ngettext("Exp speed per %d min: %s",
+ "Exp speed per %d min: %s", 5), 5, toString(m5minSpeed / 5).c_str()));
+ mExpSpeed5Label->adjustSize();
+
+ if (m5minSpeed != 0)
+ {
+ // TRANSLATORS: kill stats window label
+ mExpTime5Label->setCaption(strprintf(_(" Time for next level: %s"),
+ toString(static_cast<float>((PlayerInfo::getAttribute(
+ PlayerInfo::EXP_NEEDED) - PlayerInfo::getAttribute(
+ PlayerInfo::EXP)) / m5minSpeed * 5)).c_str()));
+ }
+ else
+ {
+ mExpTime5Label->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _(" Time for next level: %s"), "?"));
+ }
+ mExpTime5Label->adjustSize();
+
+
+ mExpSpeed15Label->setCaption(strprintf(ngettext("Exp speed per %d min: %s",
+ "Exp speed per %d min: %s", 15), 15, toString(
+ m15minSpeed / 15).c_str()));
+ mExpSpeed15Label->adjustSize();
+
+ if (m15minSpeed != 0)
+ {
+ // TRANSLATORS: kill stats window label
+ mExpTime15Label->setCaption(strprintf(_(" Time for next level: %s"),
+ toString(static_cast<float>((PlayerInfo::getAttribute(
+ PlayerInfo::EXP_NEEDED) - PlayerInfo::getAttribute(
+ PlayerInfo::EXP)) / m15minSpeed * 15)).c_str()));
+ }
+ else
+ {
+ mExpTime15Label->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _(" Time for next level: %s"), "?"));
+ }
+
+ validateJacko();
+ updateJackoLabel();
+ BLOCK_END("KillStats::update")
+}
+
+void KillStats::updateJackoLabel()
+{
+ if (mIsJackoAlive)
+ {
+ mTimeBeforeJackoLabel->setCaption(strprintf("%s jacko alive",
+ // TRANSLATORS: kill stats window label
+ _("Time before jacko spawn:")));
+ }
+ else if (mIsJackoSpawnTimeUnknown && mJackoSpawnTime != 0)
+ {
+ // TRANSLATORS: kill stats window label
+ mTimeBeforeJackoLabel->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _("%s %d?"), _("Time before jacko spawn:"),
+ mJackoSpawnTime - cur_time));
+ }
+ else if (mIsJackoMustSpawn)
+ {
+ mTimeBeforeJackoLabel->setCaption(strprintf("%s %s",
+ // TRANSLATORS: kill stats window label
+ _("Time before jacko spawn:"), _("jacko spawning")));
+ }
+ else
+ {
+ mTimeBeforeJackoLabel->setCaption(strprintf("%s %d",
+ // TRANSLATORS: kill stats window label
+ _("Time before jacko spawn:"), mJackoSpawnTime - cur_time));
+ }
+}
+
+void KillStats::jackoDead(const int id)
+{
+ if (id == mJackoId && mIsJackoAlive)
+ {
+ mIsJackoAlive = false;
+ mJackoSpawnTime = cur_time + 60*4;
+ mIsJackoSpawnTimeUnknown = false;
+ updateJackoLabel();
+ }
+}
+
+void KillStats::jackoAlive(const int id)
+{
+ if (!mIsJackoAlive)
+ {
+ mJackoId = id;
+ mIsJackoAlive = true;
+ mIsJackoMustSpawn = false;
+ mJackoSpawnTime = 0;
+ mIsJackoSpawnTimeUnknown = false;
+ updateJackoLabel();
+ }
+}
+
+void KillStats::validateJacko()
+{
+ if (!actorSpriteManager || !player_node)
+ return;
+
+ const Map *const currentMap = Game::instance()->getCurrentMap();
+ if (currentMap)
+ {
+ if (currentMap->getProperty("_realfilename") == "018-1"
+ || currentMap->getProperty("_realfilename") == "maps/018-1.tmx")
+ {
+ if (player_node->getTileX() >= 167
+ && player_node->getTileX() <= 175
+ && player_node->getTileY() >= 21
+ && player_node->getTileY() <= 46)
+ {
+ const Being *const 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::processEvent(const Channels channel A_UNUSED,
+ const DepricatedEvent &event)
+{
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ const int id = event.getInt("id");
+ if (id == PlayerInfo::EXP || id == PlayerInfo::EXP_NEEDED)
+ {
+ gainXp(event.getInt("newValue") - event.getInt("oldValue"));
+ }
+ else if (id == PlayerInfo::LEVEL)
+ {
+ mKillCounter = 0;
+ mKillTCounter = 0;
+ mExpCounter = 0;
+ mExpTCounter = 0;
+ mLine3->setCaption(strprintf("1%% = %d exp, avg mob for 1%%: %s",
+ PlayerInfo::getAttribute(PlayerInfo::EXP_NEEDED) / 100, "?"));
+ mLine4->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _("Kills: %s, total exp: %s"), "?", "?"));
+ // TRANSLATORS: kill stats window label
+ mLine5->setCaption(strprintf(_("Avg Exp: %s"), "?"));
+ mLine6->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _("No. of avg mob to next level: %s"), "?"));
+ mLine7->setCaption(strprintf(
+ // TRANSLATORS: kill stats window label
+ _("Kills/Min: %s, Exp/Min: %s"), "?", "?"));
+
+ resetTimes();
+ }
+ }
+}
diff --git a/src/gui/windows/killstats.h b/src/gui/windows/killstats.h
new file mode 100644
index 000000000..a5b59affb
--- /dev/null
+++ b/src/gui/windows/killstats.h
@@ -0,0 +1,132 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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_KILLSTATS_H
+#define GUI_KILLSTATS_H
+
+#include <guichan/actionlistener.hpp>
+
+#include "depricatedlistener.h"
+
+#include "gui/widgets/window.h"
+
+class Label;
+class Button;
+
+class KillStats final : public Window,
+ private gcn::ActionListener,
+ public DepricatedListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ KillStats();
+
+ A_DELETE_COPY(KillStats)
+
+ /**
+ * Destructor.
+ */
+ ~KillStats();
+
+ /**
+ * Stuff.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ void gainXp(int Xp);
+
+ /**
+ * Recalc stats if needed
+ */
+ void recalcStats();
+
+ /**
+ * Updates this dialog
+ */
+ void update();
+
+ /**
+ * Updates jacko info
+ */
+ void updateJackoLabel();
+
+ void jackoDead(const int id);
+
+ void jackoAlive(const int id);
+
+ void processEvent(const Channels channel A_UNUSED,
+ const DepricatedEvent &event) override;
+
+ void resetTimes();
+
+ 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;
+ Label *mLine7;
+
+ Label *mExpSpeed1Label;
+ Label *mExpTime1Label;
+ Label *mExpSpeed5Label;
+ Label *mExpTime5Label;
+ Label *mExpSpeed15Label;
+ Label *mExpTime15Label;
+
+ Label *mLastKillExpLabel;
+ Label *mTimeBeforeJackoLabel;
+
+ int m1minExpTime;
+ int m1minExpNum;
+ int m1minSpeed;
+
+ int m5minExpTime;
+ int m5minExpNum;
+ int m5minSpeed;
+
+ int m15minExpTime;
+ int m15minExpNum;
+ int m15minSpeed;
+
+ int mJackoSpawnTime;
+ int mJackoId;
+ bool mIsJackoAlive;
+ bool mIsJackoMustSpawn;
+ bool mIsJackoSpawnTimeUnknown;
+};
+
+extern KillStats *killStats;
+
+#endif // GUI_KILLSTATS_H
diff --git a/src/gui/windows/logindialog.cpp b/src/gui/windows/logindialog.cpp
new file mode 100644
index 000000000..883c4606d
--- /dev/null
+++ b/src/gui/windows/logindialog.cpp
@@ -0,0 +1,413 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/logindialog.h"
+
+#include "client.h"
+#include "configuration.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/windows/confirmdialog.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 "net/charserverhandler.h"
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/paths.h"
+#include "utils/process.h"
+
+#include "debug.h"
+
+std::string LoginDialog::savedPassword("");
+std::string LoginDialog::savedPasswordKey("");
+
+struct OpenUrlListener : public gcn::ActionListener
+{
+ OpenUrlListener() :
+ gcn::ActionListener(),
+ url()
+ {
+ }
+
+ A_DELETE_COPY(OpenUrlListener)
+
+ void action(const gcn::ActionEvent &event) override
+ {
+ if (event.getId() == "yes")
+ openBrowser(url);
+ }
+
+ std::string url;
+} urlListener;
+
+const char *UPDATE_TYPE_TEXT[3] =
+{
+ // TRANSLATORS: update type
+ N_("Normal"),
+ // TRANSLATORS: update type
+ N_("Auto Close"),
+ // TRANSLATORS: update type
+ N_("Skip"),
+};
+
+class UpdateTypeModel final : public gcn::ListModel
+{
+ public:
+ UpdateTypeModel()
+ { }
+
+ A_DELETE_COPY(UpdateTypeModel)
+
+ ~UpdateTypeModel()
+ { }
+
+ int getNumberOfElements() override
+ {
+ return 3;
+ }
+
+ std::string getElementAt(int i) override
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "???";
+ return gettext(UPDATE_TYPE_TEXT[i]);
+ }
+};
+
+class UpdateListModel final : public gcn::ListModel
+{
+ public:
+ explicit UpdateListModel(LoginData *const data) :
+ gcn::ListModel(),
+ mLoginData(data)
+ {
+ }
+
+ A_DELETE_COPY(UpdateListModel)
+
+ ~UpdateListModel()
+ { }
+
+ int getNumberOfElements() override
+ {
+ if (!mLoginData)
+ return 0;
+ return static_cast<int>(mLoginData->updateHosts.size());
+ }
+
+ std::string getElementAt(int i) override
+ {
+ if (!mLoginData || i >= getNumberOfElements() || i < 0)
+ return "???";
+ return mLoginData->updateHosts[i];
+ }
+
+ protected:
+ LoginData *mLoginData;
+};
+
+LoginDialog::LoginDialog(LoginData *const data, std::string serverName,
+ std::string *const updateHost):
+ // TRANSLATORS: login dialog name
+ Window(_("Login"), false, nullptr, "login.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mLoginData(data),
+ mUserField(new TextField(this, mLoginData->username)),
+ mPassField(new PasswordField(this, mLoginData->password)),
+ // TRANSLATORS: login dialog label
+ mKeepCheck(new CheckBox(this, _("Remember username"),
+ mLoginData->remember)),
+ // TRANSLATORS: login dialog label
+ mUpdateTypeLabel(new Label(this, _("Update:"))),
+ mUpdateHostLabel(nullptr),
+ mUpdateTypeModel(new UpdateTypeModel),
+ mUpdateTypeDropDown(new DropDown(this, mUpdateTypeModel)),
+ // TRANSLATORS: login dialog button
+ mServerButton(new Button(this, _("Change Server"), "server", this)),
+ // TRANSLATORS: login dialog button
+ mLoginButton(new Button(this, _("Login"), "login", this)),
+ // TRANSLATORS: login dialog button
+ mRegisterButton(new Button(this, _("Register"), "register", this)),
+ // TRANSLATORS: login dialog checkbox
+ mCustomUpdateHost(new CheckBox(this, _("Custom update host"),
+ mLoginData->updateType & LoginData::Upd_Custom, this, "customhost")),
+ mUpdateHostText(new TextField(this, serverConfig.getValue(
+ "customUpdateHost", ""))),
+ mUpdateListModel(nullptr),
+ mUpdateHostDropDown(nullptr),
+ mUpdateHost(updateHost),
+ mServerName(serverName)
+{
+ setCloseButton(true);
+
+ Net::getCharServerHandler()->clear();
+
+ // TRANSLATORS: login dialog label
+ Label *const serverLabel1 = new Label(this, _("Server:"));
+ Label *const serverLabel2 = new Label(this, serverName);
+ serverLabel2->adjustSize();
+ // TRANSLATORS: login dialog label
+ Label *const userLabel = new Label(this, _("Name:"));
+ // TRANSLATORS: login dialog label
+ Label *const passLabel = new Label(this, _("Password:"));
+ if (mLoginData && mLoginData->updateHosts.size() > 1)
+ {
+ // TRANSLATORS: login dialog label
+ mUpdateHostLabel = new Label(this, strprintf(_("Update host: %s"),
+ mLoginData->updateHost.c_str()));
+ mUpdateListModel = new UpdateListModel(mLoginData);
+ mUpdateHostDropDown = new DropDown(this, mUpdateListModel,
+ false, false, this, "updateselect");
+ const std::string str = serverConfig.getValue("updateHost2", "");
+ if (!str.empty())
+ mUpdateHostDropDown->setSelectedString(str);
+ }
+ else
+ {
+ mUpdateHostLabel = nullptr;
+ mUpdateListModel = nullptr;
+ mUpdateHostDropDown = nullptr;
+ }
+ mUpdateHostText->adjustSize();
+
+ if (mPassField->getText().empty() && LoginDialog::savedPassword != "")
+ mPassField->setText(LoginDialog::savedPassword);
+
+ mUpdateTypeDropDown->setActionEventId("updatetype");
+ mUpdateTypeDropDown->setSelected((mLoginData->updateType
+ | LoginData::Upd_Custom) ^ LoginData::Upd_Custom);
+
+ if (!mCustomUpdateHost->isSelected())
+ mUpdateHostText->setVisible(false);
+
+ 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);
+ place(0, 1, userLabel);
+ place(1, 1, mUserField, 8);
+ place(0, 2, passLabel);
+ place(1, 2, mPassField, 8);
+ place(0, 6, mUpdateTypeLabel, 1);
+ place(1, 6, mUpdateTypeDropDown, 8);
+ int n = 7;
+ if (mUpdateHostLabel)
+ {
+ place(0, 7, mUpdateHostLabel, 9);
+ place(0, 8, mUpdateHostDropDown, 9);
+ n += 2;
+ }
+ place(0, n, mCustomUpdateHost, 9);
+ place(0, n + 1, mUpdateHostText, 9);
+ place(0, n + 2, mKeepCheck, 9);
+ place(0, n + 3, mRegisterButton).setHAlign(LayoutCell::LEFT);
+ place(2, n + 3, mServerButton);
+ place(3, n + 3, mLoginButton);
+
+ addKeyListener(this);
+ if (mUpdateHostLabel)
+ setContentSize(310, 250);
+ else
+ setContentSize(310, 200);
+
+ reflowLayout();
+ center();
+ setVisible(true);
+
+ if (mUserField->getText().empty())
+ mUserField->requestFocus();
+ else
+ mPassField->requestFocus();
+
+ mLoginButton->setEnabled(canSubmit());
+ mRegisterButton->setEnabled(Net::getLoginHandler()->isRegistrationEnabled()
+ || !mLoginData->registerUrl.empty());
+}
+
+LoginDialog::~LoginDialog()
+{
+ delete mUpdateTypeModel;
+ mUpdateTypeModel = nullptr;
+ delete mUpdateListModel;
+ mUpdateListModel = nullptr;
+}
+
+void LoginDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "login" && canSubmit())
+ {
+ prepareUpdate();
+ mLoginData->registerLogin = false;
+ client->setState(STATE_LOGIN_ATTEMPT);
+ }
+ else if (eventId == "server")
+ {
+ close();
+ }
+ else if (eventId == "register")
+ {
+ if (Net::getLoginHandler()->isRegistrationEnabled())
+ {
+ prepareUpdate();
+ client->setState(STATE_REGISTER_PREP);
+ }
+ else if (!mLoginData->registerUrl.empty())
+ {
+ const std::string &url = mLoginData->registerUrl;
+ urlListener.url = url;
+ // TRANSLATORS: question dialog
+ ConfirmDialog *const confirmDlg = new ConfirmDialog(
+ _("Open register url"), url, SOUND_REQUEST, false, true);
+ confirmDlg->addActionListener(&urlListener);
+ }
+ }
+ else if (eventId == "customhost")
+ {
+ mUpdateHostText->setVisible(mCustomUpdateHost->isSelected());
+ }
+ else if (eventId == "updateselect")
+ {
+ mCustomUpdateHost->setSelected(false);
+ mUpdateHostText->setVisible(false);
+ }
+}
+
+void LoginDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ if (keyEvent.isConsumed())
+ {
+ mLoginButton->setEnabled(canSubmit());
+ return;
+ }
+
+ const int actionId = static_cast<KeyEvent*>(
+ &keyEvent)->getActionId();
+ if (actionId == static_cast<int>(Input::KEY_GUI_CANCEL))
+ {
+ action(gcn::ActionEvent(nullptr, mServerButton->getActionEventId()));
+ }
+ else if (actionId == static_cast<int>(Input::KEY_GUI_SELECT)
+ || actionId == static_cast<int>(Input::KEY_GUI_SELECT2))
+ {
+ action(gcn::ActionEvent(nullptr, mLoginButton->getActionEventId()));
+ }
+ else
+ {
+ mLoginButton->setEnabled(canSubmit());
+ }
+}
+
+bool LoginDialog::canSubmit() const
+{
+ return !mUserField->getText().empty() &&
+ !mPassField->getText().empty() &&
+ client->getState() == STATE_LOGIN;
+}
+
+void LoginDialog::prepareUpdate()
+{
+ mLoginData->username = mUserField->getText();
+ mLoginData->password = mPassField->getText();
+ mLoginData->remember = mKeepCheck->isSelected();
+ int updateType = mUpdateTypeDropDown->getSelected();
+
+ if (mCustomUpdateHost->isSelected()
+ && !mUpdateHostText->getText().empty())
+ {
+ updateType |= LoginData::Upd_Custom;
+ serverConfig.setValue("customUpdateHost",
+ mUpdateHostText->getText());
+
+ if (checkPath(mUpdateHostText->getText()))
+ {
+ mLoginData->updateHost = mUpdateHostText->getText();
+ *mUpdateHost = mUpdateHostText->getText();
+ }
+ else
+ {
+ mLoginData->updateHost.clear();
+ (*mUpdateHost).clear();
+ }
+ }
+ else
+ {
+ std::string str;
+ if (mUpdateHostDropDown)
+ {
+ str = mUpdateHostDropDown->getSelectedString();
+ }
+ else if (mLoginData->updateHost.empty()
+ && !mLoginData->updateHosts.empty())
+ {
+ str = mLoginData->updateHosts[0];
+ }
+ serverConfig.setValue("updateHost2", str);
+ if (!str.empty() && checkPath(str))
+ {
+ mLoginData->updateHost = str;
+ *mUpdateHost = str;
+ }
+ else
+ {
+ mLoginData->updateHost.clear();
+ (*mUpdateHost).clear();
+ }
+ }
+
+ mLoginData->updateType = updateType;
+ serverConfig.setValue("updateType", updateType);
+
+ mRegisterButton->setEnabled(false);
+ mServerButton->setEnabled(false);
+ mLoginButton->setEnabled(false);
+
+ LoginDialog::savedPassword = mPassField->getText();
+ if (mLoginData->remember)
+ LoginDialog::savedPasswordKey = mServerName;
+ else
+ LoginDialog::savedPasswordKey = "-";
+}
+
+void LoginDialog::close()
+{
+ client->setState(STATE_SWITCH_SERVER);
+ Window::close();
+}
diff --git a/src/gui/windows/logindialog.h b/src/gui/windows/logindialog.h
new file mode 100644
index 000000000..fee0d4016
--- /dev/null
+++ b/src/gui/windows/logindialog.h
@@ -0,0 +1,108 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_LOGINDIALOG_H
+#define GUI_LOGINDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+#include <string>
+
+class Button;
+class CheckBox;
+class DropDown;
+class Label;
+class LoginData;
+class TextField;
+class UpdateListModel;
+class UpdateTypeModel;
+
+/**
+ * The login dialog.
+ *
+ * \ingroup Interface
+ */
+class LoginDialog final : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ LoginDialog(LoginData *const data, std::string serverName,
+ std::string *const updateHost);
+
+ A_DELETE_COPY(LoginDialog)
+
+ ~LoginDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Called when a key is pressed in one of the text fields.
+ */
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ void close() override;
+
+ 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;
+
+ void prepareUpdate();
+
+ LoginData *mLoginData;
+
+ TextField *mUserField;
+ TextField *mPassField;
+ CheckBox *mKeepCheck;
+ Label *mUpdateTypeLabel;
+ Label *mUpdateHostLabel;
+ UpdateTypeModel *mUpdateTypeModel;
+ DropDown *mUpdateTypeDropDown;
+ Button *mServerButton;
+ Button *mLoginButton;
+ Button *mRegisterButton;
+ CheckBox *mCustomUpdateHost;
+ TextField *mUpdateHostText;
+ UpdateListModel *mUpdateListModel;
+ DropDown *mUpdateHostDropDown;
+
+ std::string *mUpdateHost;
+ std::string mServerName;
+};
+
+#endif // GUI_LOGINDIALOG_H
diff --git a/src/gui/windows/minimap.cpp b/src/gui/windows/minimap.cpp
new file mode 100644
index 000000000..9adadd1e0
--- /dev/null
+++ b/src/gui/windows/minimap.cpp
@@ -0,0 +1,485 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/minimap.h"
+
+#include "actorspritemanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "party.h"
+
+#include "being/localplayer.h"
+
+#include "gui/viewport.h"
+#include "gui/textpopup.h"
+
+#include "gui/windows/setup.h"
+
+#include "resources/image.h"
+#include "resources/imagehelper.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+#include "utils/sdlcheckutils.h"
+
+#include "debug.h"
+
+bool Minimap::mShow = true;
+
+Minimap::Minimap() :
+ // TRANSLATORS: mini map window name
+ Window(_("Map"), false, nullptr, "map.xml"),
+ mWidthProportion(0.5),
+ mHeightProportion(0.5),
+ mMapImage(nullptr),
+ mMapOriginX(0),
+ mMapOriginY(0),
+ mTextPopup(new TextPopup),
+ mCustomMapImage(false),
+ mAutoResize(config.getBoolValue("autoresizeminimaps"))
+{
+ setWindowName("Minimap");
+ mShow = config.getValueBool(getWindowName() + "Show", true);
+
+ config.addListener("autoresizeminimaps", this);
+
+ setDefaultSize(5, 25, 100, 100);
+ // set this to false as the minimap window size is changed
+ // depending on the map size
+ setResizable(true);
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ setDefaultVisible(true);
+ setSaveVisible(true);
+
+ setStickyButton(true);
+ setSticky(false);
+
+ loadWindowState();
+ setVisible(mShow, isSticky());
+ enableVisibleSound(true);
+}
+
+Minimap::~Minimap()
+{
+ config.setValue(getWindowName() + "Show", mShow);
+ config.removeListeners(this);
+
+ if (mMapImage)
+ {
+ if (mCustomMapImage)
+ delete mMapImage;
+ else
+ mMapImage->decRef();
+ mMapImage = nullptr;
+ }
+ delete mTextPopup;
+ mTextPopup = nullptr;
+}
+
+void Minimap::setMap(const Map *const map)
+{
+ std::string caption;
+
+ if (map)
+ caption = map->getName();
+
+ if (caption.empty())
+ {
+ // TRANSLATORS: mini map window name
+ caption = _("Map");
+ }
+
+ setCaption(caption);
+
+ // Adapt the image
+ if (mMapImage)
+ {
+ if (mCustomMapImage)
+ delete mMapImage;
+ else
+ mMapImage->decRef();
+ mMapImage = nullptr;
+ }
+
+ if (map)
+ {
+ if (config.getBoolValue("showExtMinimaps"))
+ {
+ SDL_Surface *const surface = MSDL_CreateRGBSurface(SDL_SWSURFACE,
+ map->getWidth(), map->getHeight(), 32,
+ 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000);
+ if (!surface)
+ {
+ if (!isSticky())
+ setVisible(false);
+ return;
+ }
+
+ // I'm not sure if the locks are necessary since it's a SWSURFACE
+ SDL_LockSurface(surface);
+ int* data = static_cast<int*>(surface->pixels);
+ if (!data)
+ {
+ if (!isSticky())
+ setVisible(false);
+ return;
+ }
+ const int size = surface->h * surface->w;
+ const int mask = (Map::BLOCKMASK_WALL | Map::BLOCKMASK_AIR
+ | Map::BLOCKMASK_WATER);
+
+ for (int ptr = 0; ptr < size; ptr ++)
+ *(data ++) = -!(map->mMetaTiles[ptr].blockmask & mask);
+
+ SDL_UnlockSurface(surface);
+
+ mMapImage = imageHelper->load(surface);
+ mMapImage->setAlpha(client->getGuiAlpha());
+ mCustomMapImage = true;
+ MSDL_FreeSurface(surface);
+ }
+ else
+ {
+ std::string tempname = paths.getStringValue("minimaps").append(
+ map->getFilename()).append(".png");
+ ResourceManager *const resman = ResourceManager::getInstance();
+
+ std::string minimapName = map->getProperty("minimap");
+
+ if (minimapName.empty() && resman->exists(tempname))
+ minimapName = tempname;
+
+ if (minimapName.empty())
+ {
+ tempname = std::string("graphics/minimaps/").append(
+ map->getFilename()).append(".png");
+ if (resman->exists(tempname))
+ minimapName = tempname;
+ }
+
+ mMapImage = resman->getImage(minimapName);
+ mCustomMapImage = false;
+ }
+ }
+
+ if (mMapImage && map)
+ {
+ const int width = mMapImage->mBounds.w + 2 * getPadding();
+ const int height = mMapImage->mBounds.h
+ + getTitleBarHeight() + getPadding();
+ const int mapWidth = mMapImage->mBounds.w < 100 ? width : 100;
+ const int mapHeight = mMapImage->mBounds.h < 100 ? height : 100;
+ const int minWidth = mapWidth > 310 ? 310 : mapWidth;
+ const int minHeight = mapHeight > 220 ? 220 : mapHeight;
+
+ setMinWidth(minWidth);
+ setMinHeight(minHeight);
+
+ mWidthProportion = static_cast<float>(
+ mMapImage->mBounds.w) / static_cast<float>(map->getWidth());
+ mHeightProportion = static_cast<float>(
+ mMapImage->mBounds.h) / static_cast<float>(map->getHeight());
+
+ setMaxWidth(width);
+ setMaxHeight(height);
+ if (mAutoResize)
+ {
+ setWidth(width);
+ setHeight(height);
+ }
+
+ const gcn::Rectangle &rect = mDimension;
+ setDefaultSize(rect.x, rect.y, rect.width, rect.height);
+ resetToDefaultSize();
+
+ if (mShow)
+ setVisible(true);
+ }
+ else
+ {
+ if (!isSticky())
+ setVisible(false);
+ }
+}
+
+void Minimap::toggle()
+{
+ setVisible(!isWindowVisible(), isSticky());
+ mShow = isWindowVisible();
+}
+
+void Minimap::draw(gcn::Graphics *graphics)
+{
+ BLOCK_START("Minimap::draw")
+ Window::draw(graphics);
+
+ if (!userPalette || !player_node || !viewport)
+ {
+ BLOCK_END("Minimap::draw")
+ return;
+ }
+
+ Graphics *const graph = static_cast<Graphics*>(graphics);
+
+ const gcn::Rectangle a = getChildrenArea();
+
+ graphics->pushClipArea(a);
+
+ if (!actorSpriteManager)
+ {
+ BLOCK_END("Minimap::draw")
+ return;
+ }
+
+ mMapOriginX = 0;
+ mMapOriginY = 0;
+
+ if (mMapImage)
+ {
+ const SDL_Rect &rect = mMapImage->mBounds;
+ const int w = rect.w;
+ const int h = rect.h;
+ if (w > a.width || h > a.height)
+ {
+ const Vector &p = player_node->getPosition();
+ mMapOriginX = (a.width / 2) - (p.x + static_cast<float>(
+ viewport->getCameraRelativeX()) * mWidthProportion) / 32;
+
+ mMapOriginY = (a.height / 2) - (p.y + static_cast<float>(
+ viewport->getCameraRelativeY()) * mHeightProportion) / 32;
+
+ const int minOriginX = a.width - w;
+ const int minOriginY = a.height - h;
+
+ if (mMapOriginX < minOriginX)
+ mMapOriginX = minOriginX;
+ if (mMapOriginY < minOriginY)
+ mMapOriginY = minOriginY;
+ if (mMapOriginX > 0)
+ mMapOriginX = 0;
+ if (mMapOriginY > 0)
+ mMapOriginY = 0;
+ }
+
+ graph->drawImage(mMapImage, mMapOriginX, mMapOriginY);
+ }
+
+ const ActorSprites &actors = actorSpriteManager->getAll();
+ FOR_EACH (ActorSpritesConstIterator, it, actors)
+ {
+ if (!(*it) || (*it)->getType() == ActorSprite::FLOOR_ITEM)
+ continue;
+
+ const Being *const being = static_cast<const Being *const>(*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->getGuild() == player_node->getGuild()
+ || being->getGuildName() == player_node->getGuildName())
+ {
+ type = UserPalette::GUILD;
+ }
+ else
+ {
+ switch (being->getType())
+ {
+ case ActorSprite::MONSTER:
+ type = UserPalette::MONSTER;
+ break;
+
+ case ActorSprite::NPC:
+ type = UserPalette::NPC;
+ break;
+
+ case ActorSprite::AVATAR:
+ case ActorSprite::UNKNOWN:
+ case ActorSprite::PLAYER:
+ case ActorSprite::FLOOR_ITEM:
+ case ActorSprite::PORTAL:
+ case ActorSprite::PET:
+ 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<float>(pos.x * mWidthProportion) / 32
+ + mMapOriginX - offsetWidth,
+ static_cast<float>(pos.y * mHeightProportion) / 32
+ + mMapOriginY - offsetHeight, dotSize, dotSize));
+ }
+
+ if (player_node->isInParty())
+ {
+ const Party *const party = player_node->getParty();
+ if (party)
+ {
+ const PartyMember *const m = party->getMember(
+ player_node->getName());
+ const Party::MemberList *const members = party->getMembers();
+ if (m && members)
+ {
+ const std::string curMap = m->getMap();
+ Party::MemberList::const_iterator it = members->begin();
+ const Party::MemberList::const_iterator
+ it_end = members->end();
+ while (it != it_end)
+ {
+ const PartyMember *const member = *it;
+ if (member && member->getMap() == curMap
+ && member->getOnline() && member != m)
+ {
+ if (userPalette)
+ {
+ graphics->setColor(userPalette->getColor(
+ UserPalette::PARTY));
+ }
+
+ const int offsetHeight = static_cast<int>(
+ mHeightProportion);
+ const int offsetWidth = static_cast<int>(
+ mWidthProportion);
+
+ graphics->fillRectangle(gcn::Rectangle(
+ static_cast<int>(member->getX()
+ * mWidthProportion) + mMapOriginX - offsetWidth,
+ static_cast<int>(member->getY()
+ * mHeightProportion) + mMapOriginY - offsetHeight,
+ 2, 2));
+ }
+ ++ it;
+ }
+ }
+ }
+ }
+
+ const Vector &pos = player_node->getPosition();
+
+ const int gw = graph->getWidth();
+ const int gh = graph->getHeight();
+ int x = static_cast<float>((pos.x - (gw / 2)
+ + viewport->getCameraRelativeX())
+ * mWidthProportion) / 32 + mMapOriginX;
+ int y = static_cast<float>((pos.y - (gh / 2)
+ + viewport->getCameraRelativeY())
+ * mHeightProportion) / 32 + mMapOriginY;
+
+ const int w = static_cast<int>(static_cast<float>(
+ gw) * mWidthProportion / 32);
+ const int h = static_cast<int>(static_cast<float>(
+ gh) * 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();
+ BLOCK_END("Minimap::draw")
+}
+
+void Minimap::mouseReleased(gcn::MouseEvent &event)
+{
+ Window::mouseReleased(event);
+
+ if (!player_node || !viewport)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ int x = event.getX();
+ int y = event.getY();
+ screenToMap(x, y);
+
+ player_node->navigateTo(x, y);
+ }
+ else if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ int x = event.getX();
+ int y = event.getY();
+ screenToMap(x, y);
+ viewport->showMapPopup(x, y);
+ }
+}
+
+void Minimap::mouseMoved(gcn::MouseEvent &event)
+{
+ Window::mouseMoved(event);
+ const int x = event.getX();
+ const int y = event.getY();
+ const gcn::Rectangle &rect = mDimension;
+ mTextPopup->show(x + rect.x, y + rect.y, mCaption);
+}
+
+void Minimap::mouseExited(gcn::MouseEvent &event)
+{
+ Window::mouseExited(event);
+ mTextPopup->hide();
+}
+
+void Minimap::screenToMap(int &x, int &y)
+{
+ const gcn::Rectangle a = getChildrenArea();
+ x = (x - a.x - mMapOriginX + mWidthProportion) / mWidthProportion;
+ y = (y - a.y - mMapOriginY + mHeightProportion) / mHeightProportion;
+}
+
+void Minimap::optionChanged(const std::string &name)
+{
+ if (name == "autoresizeminimaps")
+ mAutoResize = config.getBoolValue("autoresizeminimaps");
+}
diff --git a/src/gui/windows/minimap.h b/src/gui/windows/minimap.h
new file mode 100644
index 000000000..decacfec4
--- /dev/null
+++ b/src/gui/windows/minimap.h
@@ -0,0 +1,89 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_MINIMAP_H
+#define GUI_MINIMAP_H
+
+#include "gui/widgets/window.h"
+
+class Image;
+class Map;
+class TextPopup;
+
+/**
+ * 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 final : public Window, public ConfigListener
+{
+ public:
+ Minimap();
+
+ A_DELETE_COPY(Minimap)
+
+ ~Minimap();
+
+ /**
+ * Sets the map image that should be displayed.
+ */
+ void setMap(const Map *const map);
+
+ /**
+ * Toggles the displaying of the minimap.
+ */
+ void toggle();
+
+ /**
+ * Draws the minimap.
+ */
+ void draw(gcn::Graphics *graphics) override;
+
+ void mouseMoved(gcn::MouseEvent &event) override;
+
+ void mouseReleased(gcn::MouseEvent &event) override;
+
+ void mouseExited(gcn::MouseEvent &event) override;
+
+ void screenToMap(int &x, int &y);
+
+ void optionChanged(const std::string &name);
+
+ private:
+ float mWidthProportion;
+ float mHeightProportion;
+ Image *mMapImage;
+ int mMapOriginX;
+ int mMapOriginY;
+ TextPopup *mTextPopup;
+ bool mCustomMapImage;
+ bool mAutoResize;
+ static bool mShow;
+};
+
+extern Minimap *minimap;
+
+#endif // GUI_MINIMAP_H
diff --git a/src/gui/windows/ministatuswindow.cpp b/src/gui/windows/ministatuswindow.cpp
new file mode 100644
index 000000000..21697b83f
--- /dev/null
+++ b/src/gui/windows/ministatuswindow.cpp
@@ -0,0 +1,527 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/ministatuswindow.h"
+
+#include "animatedsprite.h"
+#include "configuration.h"
+
+#include "being/localplayer.h"
+#include "being/playerinfo.h"
+
+#include "gui/statuspopup.h"
+#include "gui/textpopup.h"
+#include "gui/viewport.h"
+
+#include "gui/windows/statuswindow.h"
+
+#include "gui/widgets/progressbar.h"
+
+#include "net/net.h"
+#include "net/playerhandler.h"
+#include "net/gamehandler.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+extern volatile int tick_time;
+
+typedef std::vector <ProgressBar*>::const_iterator ProgressBarVectorCIter;
+
+MiniStatusWindow::MiniStatusWindow() :
+ Popup("MiniStatus", "ministatus.xml"),
+ InventoryListener(),
+ mBars(),
+ mBarNames(),
+ mIcons(),
+ mSpacing(mSkin ? mSkin->getOption("spacing", 3) : 3),
+ mIconPadding(mSkin ? mSkin->getOption("iconPadding", 3) : 3),
+ mIconSpacing(mSkin ? mSkin->getOption("iconSpacing", 2) : 2),
+ mMaxX(0),
+ // TRANSLATORS: status bar name
+ mHpBar(createBar(0, 100, 0, Theme::PROG_HP, "hp bar", _("health bar"))),
+ mMpBar(Net::getGameHandler()->canUseMagicBar()
+ ? createBar(0, 100, 0, Net::getPlayerHandler()->canUseMagic()
+ // TRANSLATORS: status bar name
+ ? Theme::PROG_MP : Theme::PROG_NO_MP, "mp bar", _("mana bar"))
+ : nullptr),
+ mXpBar(createBar(0, 100, 0, Theme::PROG_EXP,
+ // TRANSLATORS: status bar name
+ "xp bar", _("experience bar"))),
+ mJobBar(nullptr),
+ mWeightBar(createBar(0, 140, 0, Theme::PROG_WEIGHT,
+ // TRANSLATORS: status bar name
+ "weight bar", _("weight bar"))),
+ mInvSlotsBar(createBar(0, 45, 0, Theme::PROG_INVY_SLOTS,
+ // TRANSLATORS: status bar name
+ "inventory slots bar", _("inventory slots bar"))),
+ mMoneyBar(createBar(0, 130, 0, Theme::PROG_INVY_SLOTS,
+ // TRANSLATORS: status bar name
+ "money bar", _("money bar"))),
+ mArrowsBar(createBar(0, 50, 0, Theme::PROG_INVY_SLOTS,
+ // TRANSLATORS: status bar name
+ "arrows bar", _("arrows bar"))),
+ mStatusBar(createBar(100, (config.getIntValue("fontSize") > 16
+ ? 250 : 165), 0, Theme::PROG_EXP,
+ // TRANSLATORS: status bar name
+ "status bar", _("status bar"))),
+ mTextPopup(new TextPopup),
+ mStatusPopup(new StatusPopup)
+{
+ listen(CHANNEL_ATTRIBUTES);
+
+ StatusWindow::updateHPBar(mHpBar);
+
+ if (Net::getGameHandler()->canUseMagicBar())
+ StatusWindow::updateMPBar(mMpBar);
+
+ const int job = Net::getPlayerHandler()->getJobLocation()
+ && serverConfig.getValueBool("showJob", true);
+
+ StatusWindow::updateXPBar(mXpBar);
+
+ if (job)
+ {
+ mJobBar = createBar(0, 100, 0, Theme::PROG_JOB, "job bar",
+ // TRANSLATORS: status bar name
+ _("job bar"));
+ StatusWindow::updateJobBar(mJobBar);
+ }
+
+ loadBars();
+ updateBars();
+
+ setVisible(config.getValueBool(getPopupName() + "Visible", true));
+ addMouseListener(this);
+ Inventory *const inv = PlayerInfo::getInventory();
+ if (inv)
+ inv->addInventoyListener(this);
+
+ StatusWindow::updateMoneyBar(mMoneyBar);
+ StatusWindow::updateArrowsBar(mArrowsBar);
+ updateStatus();
+}
+
+MiniStatusWindow::~MiniStatusWindow()
+{
+ delete mTextPopup;
+ mTextPopup = nullptr;
+ delete mStatusPopup;
+ mStatusPopup = nullptr;
+ delete_all(mIcons);
+ mIcons.clear();
+
+ Inventory *const inv = PlayerInfo::getInventory();
+ if (inv)
+ inv->removeInventoyListener(this);
+
+ FOR_EACH (ProgressBarVectorCIter, it, mBars)
+ {
+ ProgressBar *bar = *it;
+ if (!bar)
+ continue;
+ if (!bar->isVisible())
+ delete bar;
+ }
+ mBars.clear();
+}
+
+ProgressBar *MiniStatusWindow::createBar(const float progress,
+ const int width, const int height,
+ const int color,
+ const std::string &name,
+ const std::string &description)
+{
+ ProgressBar *const bar = new ProgressBar(this,
+ progress, width, height, color);
+ bar->setActionEventId(name);
+ bar->setId(description);
+ mBars.push_back(bar);
+ mBarNames[name] = bar;
+ return bar;
+}
+
+void MiniStatusWindow::updateBars()
+{
+ int x = 0;
+ const ProgressBar *lastBar = nullptr;
+ FOR_EACH (ProgressBarVectorCIter, it, mBars)
+ safeRemove(*it);
+
+ FOR_EACH (ProgressBarVectorCIter, it, mBars)
+ {
+ ProgressBar *const bar = *it;
+ if (!bar)
+ continue;
+ if (bar->isVisible())
+ {
+ bar->setPosition(x, 0);
+ add(bar);
+ x += bar->getWidth() + mSpacing;
+ lastBar = bar;
+ }
+ }
+
+ if (lastBar)
+ {
+ setContentSize(lastBar->getX() + lastBar->getWidth(),
+ lastBar->getY() + lastBar->getHeight());
+ }
+ mMaxX = x;
+}
+
+void MiniStatusWindow::setIcon(const int index, AnimatedSprite *const sprite)
+{
+ if (index >= static_cast<int>(mIcons.size()))
+ mIcons.resize(index + 1, nullptr);
+
+ delete mIcons[index];
+ mIcons[index] = sprite;
+}
+
+void MiniStatusWindow::eraseIcon(const int index)
+{
+ if (index < static_cast<int>(mIcons.size()))
+ {
+ delete mIcons[index];
+ mIcons.erase(mIcons.begin() + index);
+ }
+}
+
+void MiniStatusWindow::drawIcons(Graphics *const graphics)
+{
+ // Draw icons
+ int icon_x = mMaxX + mIconPadding;
+ for (size_t i = 0, sz = mIcons.size(); i < sz; i ++)
+ {
+ const AnimatedSprite *const icon = mIcons[i];
+ if (icon)
+ {
+ icon->draw(graphics, icon_x, mIconPadding);
+ icon_x += mIconSpacing + icon->getWidth();
+ }
+ }
+}
+
+void MiniStatusWindow::processEvent(const Channels channel A_UNUSED,
+ const DepricatedEvent &event)
+{
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ const int id = event.getInt("id");
+ if (id == PlayerInfo::HP || id == PlayerInfo::MAX_HP)
+ {
+ StatusWindow::updateHPBar(mHpBar);
+ }
+ else if (id == PlayerInfo::MP || id == PlayerInfo::MAX_MP)
+ {
+ StatusWindow::updateMPBar(mMpBar);
+ }
+ else if (id == PlayerInfo::EXP || id == PlayerInfo::EXP_NEEDED)
+ {
+ StatusWindow::updateXPBar(mXpBar);
+ }
+ else if (id == PlayerInfo::TOTAL_WEIGHT
+ || id == PlayerInfo::MAX_WEIGHT)
+ {
+ StatusWindow::updateWeightBar(mWeightBar);
+ }
+ else if (id == PlayerInfo::MONEY)
+ {
+ StatusWindow::updateMoneyBar(mMoneyBar);
+ }
+ }
+ else if (event.getName() == EVENT_UPDATESTAT)
+ {
+ StatusWindow::updateMPBar(mMpBar);
+ StatusWindow::updateJobBar(mJobBar);
+ }
+}
+
+void MiniStatusWindow::updateStatus()
+{
+ StatusWindow::updateStatusBar(mStatusBar);
+ if (mStatusPopup && mStatusPopup->isPopupVisible())
+ mStatusPopup->update();
+}
+
+void MiniStatusWindow::logic()
+{
+ BLOCK_START("MiniStatusWindow::logic")
+ Popup::logic();
+
+ for (size_t i = 0, sz = mIcons.size(); i < sz; i++)
+ {
+ AnimatedSprite *const icon = mIcons[i];
+ if (icon)
+ icon->update(tick_time * 10);
+ }
+ BLOCK_END("MiniStatusWindow::logic")
+}
+
+void MiniStatusWindow::draw(gcn::Graphics *graphics)
+{
+ BLOCK_START("MiniStatusWindow::draw")
+ drawChildren(graphics);
+ BLOCK_END("MiniStatusWindow::draw")
+}
+
+void MiniStatusWindow::mouseMoved(gcn::MouseEvent &event)
+{
+ Popup::mouseMoved(event);
+
+ const int x = event.getX();
+ const int y = event.getY();
+
+ const gcn::Rectangle &rect = mDimension;
+ if (event.getSource() == mStatusBar)
+ {
+ mStatusPopup->view(x + rect.x, y + rect.y);
+ mTextPopup->hide();
+ }
+ else if (event.getSource() == mXpBar)
+ {
+ std::string level;
+ if (player_node && player_node->isGM())
+ {
+ // TRANSLATORS: status bar label
+ level = strprintf(_("Level: %d (GM %d)"),
+ PlayerInfo::getAttribute(PlayerInfo::LEVEL),
+ player_node->getGMLevel());
+ }
+ else
+ {
+ // TRANSLATORS: status bar label
+ level = strprintf(_("Level: %d"),
+ PlayerInfo::getAttribute(PlayerInfo::LEVEL));
+ }
+
+ const int exp = PlayerInfo::getAttribute(PlayerInfo::EXP);
+ const int expNeed = PlayerInfo::getAttribute(PlayerInfo::EXP_NEEDED);
+ if (exp > expNeed)
+ {
+ mTextPopup->show(x + rect.x, y + rect.y, level, strprintf("%d/%d",
+ exp, expNeed));
+ }
+ else
+ {
+ mTextPopup->show(x + rect.x, y + rect.y, level, strprintf("%d/%d",
+ exp, expNeed),
+ // TRANSLATORS: status bar label
+ strprintf("%s: %d", _("Need"), expNeed - exp));
+ }
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mHpBar)
+ {
+ mTextPopup->show(x + rect.x, y + rect.y, event.getSource()->getId(),
+ strprintf("%d/%d", PlayerInfo::getAttribute(PlayerInfo::HP),
+ PlayerInfo::getAttribute(PlayerInfo::MAX_HP)));
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mMpBar)
+ {
+ mTextPopup->show(x + rect.x, y + rect.y, event.getSource()->getId(),
+ strprintf("%d/%d", PlayerInfo::getAttribute(PlayerInfo::MP),
+ PlayerInfo::getAttribute(PlayerInfo::MAX_MP)));
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mJobBar)
+ {
+ const std::pair<int, int> exp = PlayerInfo::getStatExperience(
+ Net::getPlayerHandler()->getJobLocation());
+
+ // TRANSLATORS: job bar label
+ const std::string level = strprintf(_("Job level: %d"),
+ PlayerInfo::getStatBase(
+ Net::getPlayerHandler()->getJobLocation()));
+
+ if (exp.first > exp.second)
+ {
+ mTextPopup->show(x + rect.x, y + rect.y, level,
+ strprintf("%d/%d", exp.first, exp.second));
+ }
+ else
+ {
+ mTextPopup->show(x + rect.x, y + rect.y, level,
+ strprintf("%d/%d", exp.first, exp.second),
+ // TRANSLATORS: status bar label
+ strprintf("%s: %d", _("Need"), exp.second - exp.first));
+ }
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mWeightBar)
+ {
+ mTextPopup->show(x + rect.x, y + rect.y, event.getSource()->getId(),
+ strprintf("%d/%d", PlayerInfo::getAttribute(
+ PlayerInfo::TOTAL_WEIGHT),
+ PlayerInfo::getAttribute(PlayerInfo::MAX_WEIGHT)));
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mInvSlotsBar)
+ {
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ const int usedSlots = inv->getNumberOfSlotsUsed();
+ const int maxSlots = inv->getSize();
+ mTextPopup->show(x + rect.x, y + rect.y,
+ event.getSource()->getId(),
+ strprintf("%d/%d", usedSlots, maxSlots));
+ }
+ mStatusPopup->hide();
+ }
+ else if (event.getSource() == mMoneyBar)
+ {
+ mTextPopup->show(x + rect.x, y + rect.y,
+ event.getSource()->getId(),
+ toString(PlayerInfo::getAttribute(PlayerInfo::MONEY)));
+ }
+ else
+ {
+ mTextPopup->hide();
+ mStatusPopup->hide();
+ }
+}
+
+void MiniStatusWindow::mousePressed(gcn::MouseEvent &event)
+{
+ if (!viewport)
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::RIGHT)
+ {
+ const ProgressBar *const bar = dynamic_cast<ProgressBar*>(
+ event.getSource());
+ if (!bar)
+ return;
+ if (viewport)
+ {
+ viewport->showPopup(getX() + event.getX(),
+ getY() + event.getY(), bar);
+ }
+ }
+}
+
+void MiniStatusWindow::mouseExited(gcn::MouseEvent &event)
+{
+ Popup::mouseExited(event);
+
+ mTextPopup->hide();
+ mStatusPopup->hide();
+}
+
+void MiniStatusWindow::showBar(const std::string &name, const bool visible)
+{
+ ProgressBar *const bar = mBarNames[name];
+ if (!bar)
+ return;
+ bar->setVisible(visible);
+ updateBars();
+ saveBars();
+}
+
+void MiniStatusWindow::loadBars()
+{
+ if (!config.getIntValue("ministatussaved"))
+ {
+ if (mWeightBar)
+ mWeightBar->setVisible(false);
+ if (mInvSlotsBar)
+ mInvSlotsBar->setVisible(false);
+ if (mMoneyBar)
+ mMoneyBar->setVisible(false);
+ if (mArrowsBar)
+ mArrowsBar->setVisible(false);
+ if (mStatusBar)
+ mStatusBar->setVisible(false);
+ if (mJobBar)
+ mJobBar->setVisible(false);
+ return;
+ }
+
+ for (int f = 0; f < 10; f ++)
+ {
+ const std::string str = config.getValue(
+ "ministatus" + toString(f), "");
+ if (str == "")
+ continue;
+ ProgressBar *const bar = mBarNames[str];
+ if (!bar)
+ continue;
+ bar->setVisible(false);
+ }
+}
+
+void MiniStatusWindow::saveBars() const
+{
+ int i = 0;
+ FOR_EACH (ProgressBarVectorCIter, it, mBars)
+ {
+ const ProgressBar *const bar = *it;
+ if (!bar->isVisible())
+ {
+ config.setValue("ministatus" + toString(i),
+ bar->getActionEventId());
+ i ++;
+ }
+ }
+ for (int f = i; f < 10; f ++)
+ config.deleteKey("ministatus" + toString(f));
+
+ config.setValue("ministatussaved", true);
+}
+
+void MiniStatusWindow::slotsChanged(Inventory *const inventory)
+{
+ if (!inventory)
+ return;
+
+ if (inventory->getType() == Inventory::INVENTORY)
+ StatusWindow::updateInvSlotsBar(mInvSlotsBar);
+}
+
+void MiniStatusWindow::updateArrows()
+{
+ StatusWindow::updateArrowsBar(mArrowsBar);
+}
+
+gcn::Rectangle MiniStatusWindow::getChildrenArea()
+{
+ const int padding = mPadding;
+ const int padding2 = padding * 2;
+ const gcn::Rectangle &rect = mDimension;
+ return gcn::Rectangle(padding, padding,
+ rect.width - padding2,
+ rect.height - padding2);
+}
+
+#ifdef USE_PROFILER
+void MiniStatusWindow::logicChildren()
+{
+ BLOCK_START("MiniStatusWindow::logicChildren")
+ BasicContainer::logicChildren();
+ BLOCK_END("MiniStatusWindow::logicChildren")
+}
+#endif
diff --git a/src/gui/windows/ministatuswindow.h b/src/gui/windows/ministatuswindow.h
new file mode 100644
index 000000000..39d1e689d
--- /dev/null
+++ b/src/gui/windows/ministatuswindow.h
@@ -0,0 +1,135 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_MINISTATUSWINDOW_H
+#define GUI_MINISTATUSWINDOW_H
+
+#include "inventory.h"
+#include "depricatedlistener.h"
+
+#include "gui/widgets/popup.h"
+
+#include <vector>
+
+class AnimatedSprite;
+class Graphics;
+class ProgressBar;
+class StatusPopup;
+class TextPopup;
+
+/**
+ * The player mini-status dialog.
+ *
+ * \ingroup Interface
+ */
+class MiniStatusWindow final : public Popup,
+ public InventoryListener,
+ public DepricatedListener
+{
+ public:
+ MiniStatusWindow();
+
+ A_DELETE_COPY(MiniStatusWindow)
+
+ ~MiniStatusWindow();
+
+ /**
+ * Sets one of the icons.
+ */
+ void setIcon(const int index, AnimatedSprite *const sprite);
+
+ void eraseIcon(const int index);
+
+ void drawIcons(Graphics *const graphics);
+
+ void processEvent(const Channels channel,
+ const DepricatedEvent &event) override;
+
+ void updateStatus();
+
+ void logic() override;
+
+ void draw(gcn::Graphics *graphics) override;
+
+ void mouseMoved(gcn::MouseEvent &mouseEvent) override;
+
+ void mousePressed(gcn::MouseEvent &event) override;
+
+ void mouseExited(gcn::MouseEvent &event) override;
+
+ void showBar(const std::string &name, const bool visible);
+
+ void updateBars();
+
+ void updateArrows();
+
+ void slotsChanged(Inventory *const inventory) override;
+
+ std::vector <ProgressBar*> &getBars() A_WARN_UNUSED
+ { return mBars; }
+
+ gcn::Rectangle getChildrenArea() override A_WARN_UNUSED;
+
+#ifdef USE_PROFILER
+ void logicChildren();
+#endif
+
+ private:
+ bool isInBar(ProgressBar *bar, int x, int y) const;
+
+ ProgressBar *createBar(const float progress, const int width,
+ const int height, const int color,
+ const std::string &name,
+ const std::string &description) A_WARN_UNUSED;
+
+ void loadBars();
+
+ void saveBars() const;
+
+ std::vector <ProgressBar*> mBars;
+ std::map <std::string, ProgressBar*> mBarNames;
+ std::vector<AnimatedSprite *> mIcons;
+
+ int mSpacing;
+ int mIconPadding;
+ int mIconSpacing;
+ int mMaxX;
+
+ /*
+ * Mini Status Bars
+ */
+ ProgressBar *mHpBar;
+ ProgressBar *mMpBar;
+ ProgressBar *mXpBar;
+ ProgressBar *mJobBar;
+ ProgressBar *mWeightBar;
+ ProgressBar *mInvSlotsBar;
+ ProgressBar *mMoneyBar;
+ ProgressBar *mArrowsBar;
+ ProgressBar *mStatusBar;
+ TextPopup *mTextPopup;
+ StatusPopup *mStatusPopup;
+};
+
+extern MiniStatusWindow *miniStatusWindow;
+
+#endif // GUI_MINISTATUSWINDOW_H
diff --git a/src/gui/windows/npcdialog.cpp b/src/gui/windows/npcdialog.cpp
new file mode 100644
index 000000000..6e546ef61
--- /dev/null
+++ b/src/gui/windows/npcdialog.cpp
@@ -0,0 +1,961 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/npcdialog.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+#include "client.h"
+#include "inventory.h"
+#include "item.h"
+#include "soundconsts.h"
+#include "soundmanager.h"
+
+#include "being/being.h"
+
+#include "gui/gui.h"
+#include "gui/sdlfont.h"
+#include "gui/viewport.h"
+
+#include "gui/windows/inventorywindow.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/inttextfield.h"
+#include "gui/widgets/itemcontainer.h"
+#include "gui/widgets/itemlinkhandler.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/extendedlistbox.h"
+#include "gui/widgets/playerbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "resources/avatardb.h"
+#include "resources/npcdb.h"
+#include "resources/resourcemanager.h"
+
+#include "net/net.h"
+#include "net/npchandler.h"
+
+#include "utils/copynpaste.h"
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include "debug.h"
+
+// TRANSLATORS: npc dialog button
+#define CAPTION_WAITING _("Stop waiting")
+// TRANSLATORS: npc dialog button
+#define CAPTION_NEXT _("Next")
+// TRANSLATORS: npc dialog button
+#define CAPTION_CLOSE _("Close")
+// TRANSLATORS: npc dialog button
+#define CAPTION_SUBMIT _("Submit")
+
+NpcDialog::DialogList NpcDialog::instances;
+NpcDialogs NpcDialog::mNpcDialogs;
+
+typedef std::vector<Image *>::iterator ImageVectorIter;
+
+NpcDialog::NpcDialog(const int npcId) :
+ // TRANSLATORS: npc dialog name
+ Window(_("NPC"), false, nullptr, "npc.xml"),
+ gcn::ActionListener(),
+ mNpcId(npcId),
+ mDefaultInt(0),
+ mDefaultString(),
+ mTextBox(new BrowserBox(this, BrowserBox::AUTO_WRAP)),
+ mScrollArea(new ScrollArea(mTextBox,
+ getOptionBool("showtextbackground"), "npc_textbackground.xml")),
+ mText(),
+ mNewText(),
+ mItemList(new ExtendedListBox(this, this, "extendedlistbox.xml")),
+ mListScrollArea(new ScrollArea(mItemList,
+ getOptionBool("showlistbackground"), "npc_listbackground.xml")),
+ mItems(),
+ mImages(),
+ mItemLinkHandler(new ItemLinkHandler),
+ mTextField(new TextField(this, "")),
+ mIntField(new IntTextField(this)),
+ // TRANSLATORS: npc dialog button
+ mPlusButton(new Button(this, _("+"), "inc", this)),
+ // TRANSLATORS: npc dialog button
+ mMinusButton(new Button(this, _("-"), "dec", this)),
+ // TRANSLATORS: npc dialog button
+ mClearButton(new Button(this, _("Clear"), "clear", this)),
+ mButton(new Button(this, "", "ok", this)),
+ // TRANSLATORS: npc dialog button
+ mButton2(new Button(this, _("Close"), "close", this)),
+ // TRANSLATORS: npc dialog button
+ mButton3(new Button(this, _("Add"), "add", this)),
+ // TRANSLATORS: npc dialog button
+ mResetButton(new Button(this, _("Reset"), "reset", this)),
+ mInventory(new Inventory(Inventory::NPC, 1)),
+ mItemContainer(new ItemContainer(this, mInventory)),
+ mItemScrollArea(new ScrollArea(mItemContainer,
+ getOptionBool("showitemsbackground"), "npc_listbackground.xml")),
+ mInputState(NPC_INPUT_NONE),
+ mActionState(NPC_ACTION_WAIT),
+ mLastNextTime(0),
+ mCameraMode(-1),
+ mCameraX(0),
+ mCameraY(0),
+ mPlayerBox(new PlayerBox(nullptr)),
+ mAvatarBeing(nullptr),
+ mShowAvatar(false),
+ mLogInteraction(config.getBoolValue("logNpcInGui"))
+{
+ // Basic Window Setup
+ setWindowName("NpcText");
+ setResizable(true);
+ setFocusable(true);
+ setStickyButtonLock(true);
+
+ setMinWidth(200);
+ setMinHeight(150);
+
+ setDefaultSize(300, 578, ImageRect::LOWER_LEFT);
+
+ mPlayerBox->setWidth(70);
+ mPlayerBox->setHeight(100);
+
+ // Setup output text box
+ mTextBox->setOpaque(false);
+ mTextBox->setMaxRow(config.getIntValue("ChatLogLength"));
+ mTextBox->setLinkHandler(mItemLinkHandler);
+ mTextBox->setFont(gui->getNpcFont());
+ mTextBox->setEnableKeys(true);
+ mTextBox->setEnableTabs(true);
+
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mScrollArea->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ // Setup listbox
+ mItemList->setWrappingEnabled(true);
+ mItemList->setActionEventId("ok");
+ mItemList->addActionListener(this);
+ mItemList->setDistributeMousePressed(false);
+ mItemList->setFont(gui->getNpcFont());
+ if (gui->getNpcFont()->getHeight() < 20)
+ mItemList->setRowHeight(20);
+ else
+ mItemList->setRowHeight(gui->getNpcFont()->getHeight());
+
+ setContentSize(260, 175);
+ mListScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mItemScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mItemList->setVisible(true);
+ mTextField->setVisible(true);
+ mIntField->setVisible(true);
+
+ const gcn::Font *const fnt = mButton->getFont();
+ int width = std::max(fnt->getWidth(CAPTION_WAITING),
+ fnt->getWidth(CAPTION_NEXT));
+ width = std::max(width, fnt->getWidth(CAPTION_CLOSE));
+ width = std::max(width, fnt->getWidth(CAPTION_SUBMIT));
+ mButton->setWidth(8 + width);
+
+ // Place widgets
+ buildLayout();
+
+ center();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(true);
+ requestFocus();
+ enableVisibleSound(true);
+ soundManager.playGuiSound(SOUND_SHOW_WINDOW);
+
+ if (actorSpriteManager)
+ {
+ const Being *const being = actorSpriteManager->findBeing(mNpcId);
+ if (being)
+ {
+ showAvatar(NPCDB::getAvatarFor(being->getSubType()));
+ setCaption(being->getName());
+ }
+ }
+
+ config.addListener("logNpcInGui", this);
+}
+
+NpcDialog::~NpcDialog()
+{
+ config.removeListeners(this);
+ clearLayout();
+
+ if (mPlayerBox)
+ {
+ delete mPlayerBox->getBeing();
+ delete mPlayerBox;
+ }
+
+ delete mTextBox;
+ mTextBox = nullptr;
+ delete mClearButton;
+ mClearButton = nullptr;
+ delete mButton;
+ mButton = nullptr;
+ delete mButton2;
+ mButton2 = nullptr;
+ delete mButton3;
+ mButton3 = nullptr;
+
+ // These might not actually be in the layout, so lets be safe
+ delete mScrollArea;
+ mScrollArea = nullptr;
+ delete mItemList;
+ mItemList = nullptr;
+ delete mTextField;
+ mTextField = nullptr;
+ delete mIntField;
+ mIntField = nullptr;
+ delete mResetButton;
+ mResetButton = nullptr;
+ delete mPlusButton;
+ mPlusButton = nullptr;
+ delete mMinusButton;
+ mMinusButton = nullptr;
+ delete mItemLinkHandler;
+ mItemLinkHandler = nullptr;
+
+ delete mItemContainer;
+ mItemContainer = nullptr;
+ delete mInventory;
+ mInventory = nullptr;
+ delete mItemScrollArea;
+ mItemScrollArea = nullptr;
+
+ delete mListScrollArea;
+ mListScrollArea = nullptr;
+
+ FOR_EACH (ImageVectorIter, it, mImages)
+ {
+ if (*it)
+ (*it)->decRef();
+ }
+
+ mImages.clear();
+
+ instances.remove(this);
+}
+
+void NpcDialog::addText(const std::string &text, const bool save)
+{
+ if (save || mLogInteraction)
+ {
+ if (mText.size() > 5000)
+ mText.clear();
+
+ mNewText.append(text);
+ mTextBox->addRow(text);
+ }
+ 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)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "ok")
+ {
+ if (mActionState == NPC_ACTION_NEXT)
+ {
+ if (!client->limitPackets(PACKET_NPC_NEXT))
+ return;
+
+ nextDialog();
+ addText(std::string(), false);
+ }
+ else if (mActionState == NPC_ACTION_CLOSE
+ || mActionState == NPC_ACTION_WAIT)
+ {
+ closeDialog();
+ }
+ else if (mActionState == NPC_ACTION_INPUT)
+ {
+ std::string printText; // Text that will get printed
+ // in the textbox
+ switch (mInputState)
+ {
+ case NPC_INPUT_LIST:
+ {
+ if (gui)
+ gui->resetClickCount();
+ const int selectedIndex = mItemList->getSelected();
+
+ if (selectedIndex >= static_cast<int>(mItems.size())
+ || selectedIndex < 0
+ || !client->limitPackets(PACKET_NPC_INPUT))
+ {
+ return;
+ }
+ unsigned char choice = static_cast<unsigned char>(
+ selectedIndex + 1);
+ printText = mItems[selectedIndex];
+
+ Net::getNpcHandler()->listInput(mNpcId, choice);
+ break;
+ }
+ case NPC_INPUT_STRING:
+ {
+ if (!client->limitPackets(PACKET_NPC_INPUT))
+ return;
+ printText = mTextField->getText();
+ Net::getNpcHandler()->stringInput(mNpcId, printText);
+ break;
+ }
+ case NPC_INPUT_INTEGER:
+ {
+ if (!client->limitPackets(PACKET_NPC_INPUT))
+ return;
+ printText = strprintf("%d", mIntField->getValue());
+ Net::getNpcHandler()->integerInput(
+ mNpcId, mIntField->getValue());
+ break;
+ }
+ case NPC_INPUT_ITEM:
+ {
+ if (!client->limitPackets(PACKET_NPC_INPUT))
+ return;
+
+ const Item *const item = mInventory->getItem(0);
+ std::string str;
+ if (item)
+ {
+ str = strprintf("%d,%d", item->getId(),
+ item->getColor());
+ }
+ else
+ {
+ str = "0,0";
+ }
+
+ // need send selected item
+ Net::getNpcHandler()->stringInput(mNpcId, str);
+ mInventory->clear();
+ break;
+ }
+
+ case NPC_INPUT_NONE:
+ default:
+ break;
+ }
+ if (mInputState != NPC_INPUT_ITEM)
+ {
+ // addText will auto remove the input layout
+ addText(strprintf("> \"%s\"", printText.c_str()), false);
+ }
+ mNewText.clear();
+ }
+
+ if (!mLogInteraction)
+ mTextBox->clearRows();
+ }
+ else if (eventId == "reset")
+ {
+ switch (mInputState)
+ {
+ case NPC_INPUT_STRING:
+ mTextField->setText(mDefaultString);
+ break;
+ case NPC_INPUT_INTEGER:
+ mIntField->setValue(mDefaultInt);
+ break;
+ case NPC_INPUT_ITEM:
+ mInventory->clear();
+ break;
+ case NPC_INPUT_NONE:
+ case NPC_INPUT_LIST:
+ default:
+ break;
+ }
+ }
+ else if (eventId == "inc")
+ {
+ mIntField->setValue(mIntField->getValue() + 1);
+ }
+ else if (eventId == "dec")
+ {
+ mIntField->setValue(mIntField->getValue() - 1);
+ }
+ else if (eventId == "clear")
+ {
+ switch (mInputState)
+ {
+ case NPC_INPUT_ITEM:
+ mInventory->clear();
+ break;
+ case NPC_INPUT_STRING:
+ case NPC_INPUT_INTEGER:
+ case NPC_INPUT_LIST:
+ case NPC_INPUT_NONE:
+ default:
+ clearRows();
+ break;
+ }
+ }
+ else if (eventId == "close")
+ {
+ if (mActionState == NPC_ACTION_INPUT)
+ {
+ switch (mInputState)
+ {
+ case NPC_INPUT_ITEM:
+ Net::getNpcHandler()->stringInput(mNpcId, "0,0");
+ break;
+ case NPC_INPUT_STRING:
+ case NPC_INPUT_INTEGER:
+ case NPC_INPUT_NONE:
+ case NPC_INPUT_LIST:
+ default:
+ Net::getNpcHandler()->listInput(mNpcId, 255);
+ break;
+ }
+ closeDialog();
+ }
+ }
+ else if (eventId == "add")
+ {
+ if (inventoryWindow)
+ {
+ const Item *const item = inventoryWindow->getSelectedItem();
+ if (item)
+ mInventory->addItem(item->getId(), 1, 1, item->getColor());
+ }
+ }
+}
+
+void NpcDialog::nextDialog()
+{
+ Net::getNpcHandler()->nextDialog(mNpcId);
+}
+
+void NpcDialog::closeDialog()
+{
+ restoreCamera();
+ Net::getNpcHandler()->closeDialog(mNpcId);
+}
+
+int NpcDialog::getNumberOfElements()
+{
+ return static_cast<int>(mItems.size());
+}
+
+std::string NpcDialog::getElementAt(int i)
+{
+ return mItems[i];
+}
+
+const Image *NpcDialog::getImageAt(int i)
+{
+ return mImages[i];
+}
+
+void NpcDialog::choiceRequest()
+{
+ mItems.clear();
+ FOR_EACH (ImageVectorIter, it, mImages)
+ {
+ if (*it)
+ (*it)->decRef();
+ }
+ mImages.clear();
+ mActionState = NPC_ACTION_INPUT;
+ mInputState = NPC_INPUT_LIST;
+ buildLayout();
+}
+
+void NpcDialog::addChoice(const std::string &choice)
+{
+ mItems.push_back(choice);
+ mImages.push_back(nullptr);
+}
+
+void NpcDialog::parseListItems(const std::string &itemString)
+{
+ std::istringstream iss(itemString);
+ ResourceManager *const resman = ResourceManager::getInstance();
+
+ std::string tmp;
+ const std::string path = paths.getStringValue("guiIcons");
+ while (getline(iss, tmp, ':'))
+ {
+ const size_t pos = tmp.find("|");
+ if (pos == std::string::npos)
+ {
+ mItems.push_back(tmp);
+ mImages.push_back(nullptr);
+ }
+ else
+ {
+ mItems.push_back(tmp.substr(pos + 1));
+ Image *const img = resman->getImage(std::string(
+ path).append(tmp.substr(0, pos)).append(".png"));
+ mImages.push_back(img);
+ }
+ }
+
+ if (!mItems.empty())
+ {
+ mItemList->setSelected(0);
+ mItemList->requestFocus();
+ }
+ else
+ {
+ mItemList->setSelected(-1);
+ }
+}
+
+void NpcDialog::refocus()
+{
+ if (!mItems.empty())
+ mItemList->refocus();
+}
+
+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()
+ || mItemList->isFocused();
+}
+
+bool NpcDialog::isAnyInputFocused()
+{
+ FOR_EACH (DialogList::const_iterator, it, instances)
+ {
+ if ((*it) && (*it)->isInputFocused())
+ return true;
+ }
+
+ return false;
+}
+
+void NpcDialog::integerRequest(const int defaultValue, const int min,
+ const int max)
+{
+ mActionState = NPC_ACTION_INPUT;
+ mInputState = NPC_INPUT_INTEGER;
+ mDefaultInt = defaultValue;
+ mIntField->setRange(min, max);
+ mIntField->setValue(defaultValue);
+ buildLayout();
+}
+
+void NpcDialog::itemRequest()
+{
+ mActionState = NPC_ACTION_INPUT;
+ mInputState = NPC_INPUT_ITEM;
+
+ buildLayout();
+}
+
+void NpcDialog::move(const 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:
+ case NPC_INPUT_ITEM:
+ default:
+ break;
+ }
+}
+
+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();
+
+ FOR_EACH (DialogList::const_iterator, it, instances)
+ {
+ if ((*it) && (*it)->isFocused())
+ return (*it);
+ }
+
+ return nullptr;
+}
+
+void NpcDialog::closeAll()
+{
+ FOR_EACH (DialogList::const_iterator, it, instances)
+ {
+ if (*it)
+ (*it)->close();
+ }
+}
+
+void NpcDialog::placeNormalControls()
+{
+ if (mShowAvatar)
+ {
+ place(0, 0, mPlayerBox);
+ place(1, 0, mScrollArea, 5, 3);
+ place(4, 3, mClearButton);
+ place(5, 3, mButton);
+ }
+ else
+ {
+ place(0, 0, mScrollArea, 5, 3);
+ place(3, 3, mClearButton);
+ place(4, 3, mButton);
+ }
+}
+
+void NpcDialog::placeMenuControls()
+{
+ if (mShowAvatar)
+ {
+ place(0, 0, mPlayerBox);
+ place(1, 0, mScrollArea, 6, 3);
+ place(0, 3, mListScrollArea, 7, 3);
+ place(1, 6, mButton2, 2);
+ place(3, 6, mClearButton, 2);
+ place(5, 6, mButton, 2);
+ }
+ else
+ {
+ place(0, 0, mScrollArea, 6, 3);
+ place(0, 3, mListScrollArea, 6, 3);
+ place(0, 6, mButton2, 2);
+ place(2, 6, mClearButton, 2);
+ place(4, 6, mButton, 2);
+ }
+}
+
+void NpcDialog::placeTextInputControls()
+{
+ if (mShowAvatar)
+ {
+ place(0, 0, mPlayerBox);
+ place(1, 0, mScrollArea, 6, 3);
+ place(0, 3, mTextField, 6);
+ place(0, 4, mResetButton, 2);
+ place(4, 4, mClearButton, 2);
+ place(5, 4, mButton, 2);
+ }
+ else
+ {
+ 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);
+ }
+}
+
+void NpcDialog::placeIntInputControls()
+{
+ if (mShowAvatar)
+ {
+ place(0, 0, mPlayerBox);
+ place(1, 0, mScrollArea, 6, 3);
+ place(1, 3, mMinusButton, 1);
+ place(2, 3, mIntField, 4);
+ place(6, 3, mPlusButton, 1);
+ place(0, 4, mResetButton, 2);
+ place(3, 4, mClearButton, 2);
+ place(5, 4, mButton, 2);
+ }
+ else
+ {
+ 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);
+ }
+}
+
+void NpcDialog::placeItemInputControls()
+{
+ if (mShowAvatar)
+ {
+ place(0, 0, mPlayerBox);
+ place(1, 0, mScrollArea, 6, 3);
+ place(0, 3, mItemScrollArea, 7, 3);
+ place(1, 6, mButton3, 2);
+ place(3, 6, mClearButton, 2);
+ place(5, 6, mButton, 2);
+ }
+ else
+ {
+ place(0, 0, mScrollArea, 6, 3);
+ place(0, 3, mItemScrollArea, 6, 3);
+ place(0, 6, mButton3, 2);
+ place(2, 6, mClearButton, 2);
+ place(4, 6, mButton, 2);
+ }
+}
+
+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);
+ placeNormalControls();
+ }
+ else if (mInputState != NPC_INPUT_NONE)
+ {
+ mButton->setCaption(CAPTION_SUBMIT);
+ switch (mInputState)
+ {
+ case NPC_INPUT_LIST:
+ placeMenuControls();
+ mItemList->setSelected(-1);
+ break;
+
+ case NPC_INPUT_STRING:
+ placeTextInputControls();
+ break;
+
+ case NPC_INPUT_INTEGER:
+ placeIntInputControls();
+ break;
+
+ case NPC_INPUT_ITEM:
+ placeItemInputControls();
+ break;
+
+ case NPC_INPUT_NONE:
+ default:
+ break;
+ }
+ }
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(1, Layout::AUTO_SET);
+ redraw();
+ mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
+}
+
+void NpcDialog::saveCamera()
+{
+ if (!viewport || mCameraMode >= 0)
+ return;
+
+ mCameraMode = viewport->getCameraMode();
+ mCameraX = viewport->getCameraRelativeX();
+ mCameraY = viewport->getCameraRelativeY();
+}
+
+void NpcDialog::restoreCamera()
+{
+ if (!viewport || mCameraMode == -1)
+ return;
+
+ if (!mCameraMode)
+ {
+ if (viewport->getCameraMode() != mCameraMode)
+ viewport->toggleCameraMode();
+ }
+ else
+ {
+ if (viewport->getCameraMode() != mCameraMode)
+ viewport->toggleCameraMode();
+ viewport->setCameraRelativeX(mCameraX);
+ viewport->setCameraRelativeY(mCameraY);
+ }
+ mCameraMode = -1;
+}
+
+void NpcDialog::showAvatar(const uint16_t avatarId)
+{
+ const bool needShow = (avatarId != 0);
+ if (needShow)
+ {
+ delete mAvatarBeing;
+ mAvatarBeing = new Being(0, ActorSprite::AVATAR, avatarId, nullptr);
+ mPlayerBox->setPlayer(mAvatarBeing);
+ if (!mAvatarBeing->empty())
+ {
+ mAvatarBeing->logic();
+ const BeingInfo *const info = AvatarDB::get(avatarId);
+ const int pad2 = 2 * mPadding;
+ int width = 0;
+ if (info)
+ {
+ width = info->getWidth();
+ mPlayerBox->setWidth(width + pad2);
+ mPlayerBox->setHeight(info->getHeight() + pad2);
+ }
+ const Sprite *const sprite = mAvatarBeing->getSprite(0);
+ if (sprite && !width)
+ {
+ mPlayerBox->setWidth(sprite->getWidth() + pad2);
+ mPlayerBox->setHeight(sprite->getHeight() + pad2);
+ }
+ }
+ }
+ else
+ {
+ delete mAvatarBeing;
+ mAvatarBeing = nullptr;
+ mPlayerBox->setPlayer(nullptr);
+ }
+ if (needShow != mShowAvatar)
+ {
+ mShowAvatar = needShow;
+ buildLayout();
+ }
+ else
+ {
+ mShowAvatar = needShow;
+ }
+}
+
+void NpcDialog::setAvatarDirection(const uint8_t direction)
+{
+ Being *const being = mPlayerBox->getBeing();
+ if (being)
+ being->setDirection(direction);
+}
+
+void NpcDialog::setAvatarAction(const int actionId)
+{
+ Being *const being = mPlayerBox->getBeing();
+ if (being)
+ being->setAction(static_cast<Being::Action>(actionId));
+}
+
+void NpcDialog::logic()
+{
+ BLOCK_START("NpcDialog::logic")
+ Window::logic();
+ if (mShowAvatar && mAvatarBeing)
+ {
+ mAvatarBeing->logic();
+ if (mPlayerBox->getWidth() < static_cast<signed>(3 * getPadding()))
+ {
+ const Sprite *const sprite = mAvatarBeing->getSprite(0);
+ if (sprite)
+ {
+ mPlayerBox->setWidth(sprite->getWidth() + 2 * getPadding());
+ mPlayerBox->setHeight(sprite->getHeight() + 2 * getPadding());
+ buildLayout();
+ }
+ }
+ }
+ BLOCK_END("NpcDialog::logic")
+}
+
+void NpcDialog::clearRows()
+{
+ mTextBox->clearRows();
+}
+
+void NpcDialog::clearDialogs()
+{
+ NpcDialogs::iterator it = mNpcDialogs.begin();
+ const NpcDialogs::iterator it_end = mNpcDialogs.end();
+ while (it != it_end)
+ {
+ delete (*it).second;
+ ++ it;
+ }
+ mNpcDialogs.clear();
+}
+
+void NpcDialog::mousePressed(gcn::MouseEvent &event)
+{
+ Window::mousePressed(event);
+ if (event.getButton() == gcn::MouseEvent::RIGHT
+ && event.getSource() == mTextBox)
+ {
+ if (viewport)
+ viewport->showNpcDialogPopup(mNpcId);
+ }
+}
+
+void NpcDialog::copyToClipboard(const int npcId, const int x, const int y)
+{
+ NpcDialogs::iterator it = mNpcDialogs.find(npcId);
+ if (it != mNpcDialogs.end())
+ {
+ const BrowserBox *const text = (*it).second->mTextBox;
+ if (!text)
+ return;
+
+ std::string str = text->getTextAtPos(x, y);
+ sendBuffer(str);
+ }
+}
diff --git a/src/gui/windows/npcdialog.h b/src/gui/windows/npcdialog.h
new file mode 100644
index 000000000..d67407ac9
--- /dev/null
+++ b/src/gui/windows/npcdialog.h
@@ -0,0 +1,301 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_NPCDIALOG_H
+#define GUI_NPCDIALOG_H
+
+#include "configlistener.h"
+
+#include "gui/widgets/extendedlistmodel.h"
+#include "gui/widgets/window.h"
+
+#include "utils/stringvector.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <list>
+
+class Being;
+class Button;
+class BrowserBox;
+class ExtendedListBox;
+class ItemLinkHandler;
+class Inventory;
+class IntTextField;
+class ItemContainer;
+class NpcDialog;
+class PlayerBox;
+class ScrollArea;
+class TextBox;
+class TextField;
+
+typedef std::map<int, NpcDialog*> NpcDialogs;
+
+/**
+ * The npc dialog.
+ *
+ * \ingroup Interface
+ */
+class NpcDialog final : public Window,
+ public gcn::ActionListener,
+ public ExtendedListModel,
+ public ConfigListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ explicit NpcDialog(const int npcId);
+
+ A_DELETE_COPY(NpcDialog)
+
+ ~NpcDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * 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, const 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() override A_WARN_UNUSED;
+
+ /**
+ * Returns the name of item number i of the choices list.
+ */
+ std::string getElementAt(int i) override A_WARN_UNUSED;
+
+ /**
+ * Returns the image of item number i of the choices list.
+ */
+ const Image *getImageAt(int i) override A_WARN_UNUSED;
+
+ /**
+ * 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 A_WARN_UNUSED;
+
+ bool isTextInputFocused() const A_WARN_UNUSED;
+
+ static bool isAnyInputFocused() A_WARN_UNUSED;
+
+ /**
+ * Requests a interger from the user.
+ */
+ void integerRequest(const int defaultValue = 0, const int min = 0,
+ const int max = 2147483647);
+
+ void itemRequest();
+
+ void move(const int amount);
+
+ void setVisible(bool visible) override;
+
+ void optionChanged(const std::string &name) override;
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive() A_WARN_UNUSED
+ { return !instances.empty(); }
+
+ /**
+ * Returns the first active instance. Useful for pushing user
+ * interaction.
+ */
+ static NpcDialog *getActive() A_WARN_UNUSED;
+
+ /**
+ * Closes all instances.
+ */
+ static void closeAll();
+
+ /**
+ * Closes all instances and destroy also net handler dialogs.
+ */
+ static void destroyAll();
+
+ void saveCamera();
+
+ void restoreCamera();
+
+ void refocus();
+
+ void showAvatar(const uint16_t avatarId);
+
+ void setAvatarDirection(const uint8_t direction);
+
+ void setAvatarAction(const int actionId);
+
+ void logic() override;
+
+ void clearRows();
+
+ void mousePressed(gcn::MouseEvent &event);
+
+ static void copyToClipboard(const int npcId, const int x, const int y);
+
+ static NpcDialogs mNpcDialogs;
+
+ static void clearDialogs();
+
+ private:
+ typedef std::list<NpcDialog*> DialogList;
+ static DialogList instances;
+
+ void buildLayout();
+
+ void placeNormalControls();
+
+ void placeMenuControls();
+
+ void placeTextInputControls();
+
+ void placeIntInputControls();
+
+ void placeItemInputControls();
+
+ int mNpcId;
+
+ int mDefaultInt;
+ std::string mDefaultString;
+
+ // Used for the main input area
+ BrowserBox *mTextBox;
+ ScrollArea *mScrollArea;
+ std::string mText;
+ std::string mNewText;
+
+ // Used for choice input
+ ExtendedListBox *mItemList;
+ ScrollArea *mListScrollArea;
+ StringVect mItems;
+ std::vector<Image *> mImages;
+ ItemLinkHandler *mItemLinkHandler;
+
+ // Used for string and integer input
+ TextField *mTextField;
+ IntTextField *mIntField;
+ Button *mPlusButton;
+ Button *mMinusButton;
+ Button *mClearButton;
+
+ // Used for the button
+ Button *mButton;
+ Button *mButton2;
+ Button *mButton3;
+
+ // Will reset the text and integer input to the provided default
+ Button *mResetButton;
+
+ Inventory *mInventory;
+ ItemContainer *mItemContainer;
+ ScrollArea *mItemScrollArea;
+
+ enum NpcInputState
+ {
+ NPC_INPUT_NONE = 0,
+ NPC_INPUT_LIST,
+ NPC_INPUT_STRING,
+ NPC_INPUT_INTEGER,
+ NPC_INPUT_ITEM
+ };
+
+ enum NpcActionState
+ {
+ NPC_ACTION_WAIT = 0,
+ NPC_ACTION_NEXT,
+ NPC_ACTION_INPUT,
+ NPC_ACTION_CLOSE
+ };
+
+ NpcInputState mInputState;
+ NpcActionState mActionState;
+ int mLastNextTime;
+ int mCameraMode;
+ int mCameraX;
+ int mCameraY;
+ PlayerBox *mPlayerBox;
+ Being *mAvatarBeing;
+ bool mShowAvatar;
+ bool mLogInteraction;
+};
+
+#endif // GUI_NPCDIALOG_H
diff --git a/src/gui/windows/npcpostdialog.cpp b/src/gui/windows/npcpostdialog.cpp
new file mode 100644
index 000000000..af42495a9
--- /dev/null
+++ b/src/gui/windows/npcpostdialog.cpp
@@ -0,0 +1,136 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/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"
+
+#include "debug.h"
+
+NpcPostDialog::DialogList NpcPostDialog::instances;
+
+NpcPostDialog::NpcPostDialog(const int npcId):
+ // TRANSLATORS: npc post dialog caption
+ Window(_("NPC"), false, nullptr, "npcpost.xml"),
+ gcn::ActionListener(),
+ mNpcId(npcId),
+ mText(new TextBox(this)),
+ mSender(new TextField(this))
+{
+ setContentSize(400, 180);
+
+ // create text field for receiver
+ // TRANSLATORS: label in npc post dialog
+ Label *const senderText = new Label(this, _("To:"));
+ senderText->setPosition(5, 5);
+ mSender->setPosition(senderText->getWidth() + 5, 5);
+ mSender->setWidth(65);
+
+ // create button for sending
+ // TRANSLATORS: button in npc post dialog
+ Button *const sendButton = new Button(this, _("Send"), "send", this);
+ sendButton->setPosition(400 - sendButton->getWidth(),
+ 170 - sendButton->getHeight());
+ // TRANSLATORS: button in npc post dialog
+ Button *const cancelButton = new Button(this, _("Cancel"), "cancel", this);
+ cancelButton->setPosition(sendButton->getX()
+ - (cancelButton->getWidth() + 2), sendButton->getY());
+
+ // create textfield for letter
+ mText->setHeight(400 - (mSender->getHeight() + sendButton->getHeight()));
+ mText->setEditable(true);
+
+ // create scroll box for letter text
+ ScrollArea *const 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);
+ enableVisibleSound(true);
+}
+
+NpcPostDialog::~NpcPostDialog()
+{
+ instances.remove(this);
+}
+
+void NpcPostDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "send")
+ {
+ if (mSender->getText().empty() || mText->getText().empty())
+ {
+ if (localChatTab)
+ {
+ // TRANSLATORS: npc post message error
+ localChatTab->chatLog(_("Failed to send as sender or letter "
+ "invalid."));
+ }
+ }
+ else
+ {
+ Net::getNpcHandler()->sendLetter(mNpcId, mSender->getText(),
+ mText->getText());
+ }
+ setVisible(false);
+ }
+ else if (eventId == "cancel")
+ {
+ setVisible(false);
+ }
+}
+
+void NpcPostDialog::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (!visible)
+ scheduleDelete();
+}
+
+void NpcPostDialog::closeAll()
+{
+ FOR_EACH (DialogList::const_iterator, it, instances)
+ (*it)->close();
+}
diff --git a/src/gui/windows/npcpostdialog.h b/src/gui/windows/npcpostdialog.h
new file mode 100644
index 000000000..d9c31d6fd
--- /dev/null
+++ b/src/gui/windows/npcpostdialog.h
@@ -0,0 +1,74 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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 final : public Window,
+ public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor
+ */
+ explicit NpcPostDialog(const int npcId);
+
+ A_DELETE_COPY(NpcPostDialog)
+
+ ~NpcPostDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ void setVisible(bool visible) override;
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive() A_WARN_UNUSED
+ { 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 // GUI_NPCPOSTDIALOG_H
diff --git a/src/gui/windows/okdialog.cpp b/src/gui/windows/okdialog.cpp
new file mode 100644
index 000000000..49bf8aa0a
--- /dev/null
+++ b/src/gui/windows/okdialog.cpp
@@ -0,0 +1,89 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/okdialog.h"
+
+#include "soundconsts.h"
+#include "soundmanager.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/textbox.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include "debug.h"
+
+OkDialog::OkDialog(const std::string &title, const std::string &msg,
+ const int soundEvent, const bool modal,
+ const bool showCenter, Window *const parent,
+ const int minWidth) :
+ Window(title, modal, parent, "ok.xml"),
+ gcn::ActionListener(),
+ mTextBox(new TextBox(this))
+{
+ mTextBox->setEditable(false);
+ mTextBox->setOpaque(false);
+ mTextBox->setTextWrapped(msg, minWidth);
+
+ // TRANSLATORS: ok dialog button
+ Button *const okButton = new Button(this, _("OK"), "ok", this);
+
+ int width = getFont()->getWidth(title);
+ if (width < mTextBox->getMinWidth())
+ width = mTextBox->getMinWidth();
+ if (width < okButton->getWidth())
+ width = okButton->getWidth();
+
+ if (mTextBox->getWidth() > width)
+ width = mTextBox->getWidth();
+ if (okButton->getWidth() > width)
+ width = okButton->getWidth();
+ setContentSize(width, mTextBox->getHeight() + okButton->getHeight()
+ + getOption("buttonPadding", 8));
+ mTextBox->setPosition((width - mTextBox->getWidth()) / 2, 0);
+ okButton->setPosition((width - okButton->getWidth()) / 2,
+ mTextBox->getHeight() + getOption("buttonPadding", 8));
+
+ add(mTextBox);
+ add(okButton);
+
+ if (showCenter)
+ center();
+ else
+ centerHorisontally();
+ setVisible(true);
+ okButton->requestFocus();
+
+ if (soundEvent == DIALOG_OK)
+ soundManager.playGuiSound(SOUND_INFO);
+ else if (soundEvent == DIALOG_ERROR)
+ soundManager.playGuiSound(SOUND_ERROR);
+}
+
+void OkDialog::action(const gcn::ActionEvent &event)
+{
+ setActionEventId(event.getId());
+ distributeActionEvent();
+ scheduleDelete();
+}
diff --git a/src/gui/windows/okdialog.h b/src/gui/windows/okdialog.h
new file mode 100644
index 000000000..851590595
--- /dev/null
+++ b/src/gui/windows/okdialog.h
@@ -0,0 +1,71 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_OKDIALOG_H
+#define GUI_OKDIALOG_H
+
+#include "localconsts.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class TextBox;
+
+enum
+{
+ DIALOG_OK = 0,
+ DIALOG_ERROR,
+ DIALOG_SILENCE
+};
+
+/**
+ * An 'Ok' button dialog.
+ *
+ * \ingroup GUI
+ */
+class OkDialog final : public Window,
+ public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ OkDialog(const std::string &title, const std::string &msg,
+ const int soundEvent = DIALOG_OK, const bool modal = true,
+ const bool showCenter = true, Window *const parent = nullptr,
+ const int minWidth = 260);
+
+ A_DELETE_COPY(OkDialog)
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ private:
+ TextBox *mTextBox;
+};
+
+#endif // GUI_OKDIALOG_H
diff --git a/src/gui/windows/outfitwindow.cpp b/src/gui/windows/outfitwindow.cpp
new file mode 100644
index 000000000..38a837872
--- /dev/null
+++ b/src/gui/windows/outfitwindow.cpp
@@ -0,0 +1,662 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/outfitwindow.h"
+
+#include "configuration.h"
+#include "dragdrop.h"
+#include "emoteshortcut.h"
+#include "game.h"
+#include "inventory.h"
+#include "item.h"
+
+#include "being/playerinfo.h"
+
+#include "input/inputmanager.h"
+
+#include "gui/viewport.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+
+#include "resources/image.h"
+
+#include "utils/gettext.h"
+
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+
+#include <vector>
+
+#include "debug.h"
+
+float OutfitWindow::mAlpha = 1.0;
+
+OutfitWindow::OutfitWindow():
+ // TRANSLATORS: outfits window name
+ Window(_("Outfits"), false, nullptr, "outfits.xml"),
+ gcn::ActionListener(),
+ // TRANSLATORS: outfits window button
+ mPreviousButton(new Button(this, _("<"), "previous", this)),
+ // TRANSLATORS: outfits window button
+ mNextButton(new Button(this, _(">"), "next", this)),
+ // TRANSLATORS: outfits window button
+ mEquipButtom(new Button(this, _("Equip"), "equip", this)),
+ // TRANSLATORS: outfits window label
+ mCurrentLabel(new Label(this, strprintf(_("Outfit: %d"), 1))),
+ // TRANSLATORS: outfits window checkbox
+ mUnequipCheck(new CheckBox(this, _("Unequip first"),
+ serverConfig.getValueBool("OutfitUnequip0", true))),
+ // TRANSLATORS: outfits window checkbox
+ mAwayOutfitCheck(new CheckBox(this, _("Away outfit"),
+ serverConfig.getValue("OutfitAwayIndex", OUTFITS_COUNT - 1))),
+ mCurrentOutfit(0),
+ // TRANSLATORS: outfits window label
+ mKeyLabel(new Label(this, strprintf(_("Key: %s"),
+ keyName(mCurrentOutfit).c_str()))),
+ mBoxWidth(33),
+ mBoxHeight(33),
+ mGridWidth(4),
+ mGridHeight(4),
+ mItems(),
+ mAwayOutfit(0),
+ mBorderColor(getThemeColor(Theme::BORDER, 64)),
+ mBackgroundColor(getThemeColor(Theme::BACKGROUND, 32)),
+ mItemColors(),
+ mItemClicked(false),
+ mItemsUnequip()
+{
+ setWindowName("Outfits");
+ setResizable(true);
+ setCloseButton(true);
+ setStickyButtonLock(true);
+
+ setDefaultSize(250, 400, 150, 290);
+ setMinWidth(145);
+ setMinHeight(220);
+
+ mCurrentLabel->setAlignment(gcn::Graphics::CENTER);
+ mKeyLabel->setAlignment(gcn::Graphics::CENTER);
+
+ mUnequipCheck->setActionEventId("unequip");
+ mUnequipCheck->addActionListener(this);
+
+ mAwayOutfitCheck->setActionEventId("away");
+ mAwayOutfitCheck->addActionListener(this);
+
+ place(1, 3, mEquipButtom, 2);
+ place(0, 4, mKeyLabel, 4);
+ place(0, 5, mPreviousButton, 1);
+ place(1, 5, mCurrentLabel, 2);
+ place(3, 5, mNextButton, 1);
+ place(0, 6, mUnequipCheck, 4);
+ place(0, 7, mAwayOutfitCheck, 4);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+ layout.setColWidth(4, Layout::CENTER);
+
+ loadWindowState();
+
+ enableVisibleSound(true);
+ load();
+}
+
+OutfitWindow::~OutfitWindow()
+{
+ save();
+}
+
+void OutfitWindow::load(const bool oldConfig)
+{
+ const Configuration *cfg;
+ if (oldConfig)
+ cfg = &config;
+ else
+ cfg = &serverConfig;
+
+ memset(mItems, -1, sizeof(mItems));
+ memset(mItemColors, 1, sizeof(mItemColors));
+
+ for (unsigned 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 (size_t i = 0, sz = tokens.size();
+ i < sz && i < OUTFIT_ITEM_COUNT; i++)
+ {
+ mItems[o][i] = tokens[i];
+ }
+
+ outfit = cfg->getValue("OutfitColor" + toString(o), "1");
+ std::stringstream ss2(outfit);
+
+ tokens.clear();
+
+ std::vector<unsigned char> tokens2;
+ while (ss2 >> buf)
+ tokens2.push_back(static_cast<unsigned char>(atoi(buf.c_str())));
+
+ for (size_t i = 0, sz = tokens2.size();
+ i < sz && i < OUTFIT_ITEM_COUNT; i++)
+ {
+ mItemColors[o][i] = tokens2[i];
+ }
+
+ mItemsUnequip[o] = cfg->getValueBool("OutfitUnequip" + toString(o),
+ true);
+ }
+ mAwayOutfit = cfg->getValue("OutfitAwayIndex", OUTFITS_COUNT - 1);
+ if (mAwayOutfit >= static_cast<int>(OUTFITS_COUNT))
+ mAwayOutfit = static_cast<int>(OUTFITS_COUNT) - 1;
+
+ if (mAwayOutfitCheck)
+ mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit);
+}
+
+void OutfitWindow::save() const
+{
+ std::string outfitStr;
+ std::string outfitColorsStr;
+ for (unsigned o = 0; o < OUTFITS_COUNT; o++)
+ {
+ bool good = false;
+ for (unsigned i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ {
+ const int val = mItems[o][i];
+ const int res = val ? val : -1;
+ if (res != -1)
+ good = true;
+ outfitStr.append(toString(res));
+ if (i < OUTFIT_ITEM_COUNT - 1)
+ outfitStr.append(" ");
+ outfitColorsStr.append(toString(static_cast<int>(
+ mItemColors[o][i])));
+ if (i < OUTFIT_ITEM_COUNT - 1)
+ outfitColorsStr.append(" ");
+ }
+ if (good)
+ {
+ serverConfig.setValue("Outfit" + toString(o), outfitStr);
+ serverConfig.setValue("OutfitColor" + toString(o),
+ outfitColorsStr);
+ }
+ else
+ {
+ serverConfig.deleteKey("Outfit" + toString(o));
+ serverConfig.deleteKey("OutfitColor" + toString(o));
+ }
+
+ if (mItemsUnequip[o])
+ {
+ serverConfig.deleteKey("OutfitUnequip" + toString(o));
+ }
+ else
+ {
+ serverConfig.setValue("OutfitUnequip" + toString(o),
+ mItemsUnequip[o]);
+ }
+ outfitStr.clear();
+ outfitColorsStr.clear();
+ }
+ serverConfig.setValue("OutfitAwayIndex", mAwayOutfit);
+}
+
+void OutfitWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string eventId = event.getId();
+ if (eventId == "next")
+ {
+ next();
+ }
+ else if (eventId == "previous")
+ {
+ previous();
+ }
+ else if (eventId == "unequip")
+ {
+ if (mCurrentOutfit >= 0 && mCurrentOutfit < static_cast<int>(
+ OUTFITS_COUNT))
+ {
+ mItemsUnequip[mCurrentOutfit] = mUnequipCheck->isSelected();
+ }
+ }
+ else if (eventId == "equip")
+ {
+ wearOutfit(mCurrentOutfit);
+ if (Game::instance())
+ Game::instance()->setValidSpeed();
+ }
+ else if (eventId == "away")
+ {
+ mAwayOutfit = mCurrentOutfit;
+ if (!mAwayOutfitCheck->isSelected())
+ mAwayOutfitCheck->setSelected(true);
+ }
+}
+
+void OutfitWindow::wearOutfit(const int outfit, const bool unwearEmpty,
+ const bool select)
+{
+ bool isEmpty = true;
+
+ if (outfit < 0 || outfit > static_cast<int>(OUTFITS_COUNT))
+ return;
+
+ for (unsigned i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ {
+ const Item *const item = PlayerInfo::getInventory()->findItem(
+ mItems[outfit][i], mItemColors[outfit][i]);
+ if (item && !item->isEquipped() && item->getQuantity())
+ {
+ if (item->isEquipment())
+ {
+ Net::getInventoryHandler()->equipItem(item);
+ isEmpty = false;
+ }
+ }
+ }
+
+ if ((!isEmpty || unwearEmpty) && outfit < static_cast<int>(OUTFITS_COUNT)
+ && mItemsUnequip[outfit])
+ {
+ unequipNotInOutfit(outfit);
+ }
+ if (select)
+ {
+ mCurrentOutfit = outfit;
+ showCurrentOutfit();
+ }
+}
+
+void OutfitWindow::copyOutfit(const int outfit)
+{
+ copyOutfit(outfit, mCurrentOutfit);
+}
+
+void OutfitWindow::copyOutfit(const int src, const int dst)
+{
+ if (src < 0 || src > static_cast<int>(OUTFITS_COUNT)
+ || dst < 0 || dst > static_cast<int>(OUTFITS_COUNT))
+ {
+ return;
+ }
+
+ for (unsigned int i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ mItems[dst][i] = mItems[src][i];
+}
+
+void OutfitWindow::draw(gcn::Graphics *graphics)
+{
+ BLOCK_START("OutfitWindow::draw")
+ Window::draw(graphics);
+ Graphics *const g = static_cast<Graphics*>(graphics);
+
+ if (mCurrentOutfit < 0 || mCurrentOutfit
+ >= static_cast<signed int>(OUTFITS_COUNT))
+ {
+ return;
+ }
+
+ for (unsigned int i = 0; i < OUTFIT_ITEM_COUNT; i++)
+ {
+ const int itemX = mPadding + ((i % mGridWidth) * mBoxWidth);
+ const int itemY = mPadding + mTitleBarHeight
+ + ((i / mGridWidth) * mBoxHeight);
+
+ graphics->setColor(mBorderColor);
+ graphics->drawRectangle(gcn::Rectangle(itemX, itemY, 32, 32));
+ graphics->setColor(mBackgroundColor);
+ graphics->fillRectangle(gcn::Rectangle(itemX, itemY, 32, 32));
+
+ if (mItems[mCurrentOutfit][i] < 0)
+ continue;
+
+ bool foundItem = false;
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ const Item *const item = inv->findItem(mItems[mCurrentOutfit][i],
+ mItemColors[mCurrentOutfit][i]);
+ if (item)
+ {
+ // Draw item icon.
+ const Image *const image = item->getImage();
+ if (image)
+ {
+ g->drawImage(image, itemX, itemY);
+ foundItem = true;
+ }
+ }
+ }
+ if (!foundItem)
+ {
+ Image *const image = Item::getImage(mItems[mCurrentOutfit][i],
+ mItemColors[mCurrentOutfit][i]);
+ if (image)
+ {
+ g->drawImage(image, itemX, itemY);
+ image->decRef();
+ }
+ }
+ }
+ BLOCK_END("OutfitWindow::draw")
+}
+
+
+void OutfitWindow::mouseDragged(gcn::MouseEvent &event)
+{
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (dragDrop.isEmpty() && mItemClicked)
+ {
+ if (mCurrentOutfit < 0 || mCurrentOutfit
+ >= static_cast<signed int>(OUTFITS_COUNT))
+ {
+ Window::mouseDragged(event);
+ return;
+ }
+
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+ if (index == -1)
+ {
+ Window::mouseDragged(event);
+ return;
+ }
+ const int itemId = mItems[mCurrentOutfit][index];
+ const unsigned char itemColor = mItemColors[mCurrentOutfit][index];
+ if (itemId < 0)
+ {
+ Window::mouseDragged(event);
+ return;
+ }
+ mMoved = false;
+ event.consume();
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ Item *const item = inv->findItem(itemId, itemColor);
+ if (item)
+ dragDrop.dragItem(item, DRAGDROP_SOURCE_OUTFIT);
+ else
+ dragDrop.clear();
+ mItems[mCurrentOutfit][index] = -1;
+ }
+ }
+ }
+ 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();
+ event.consume();
+ }
+ else
+ {
+ Window::mousePressed(event);
+ }
+ return;
+ }
+ mMoved = false;
+ event.consume();
+
+ if (mItems[mCurrentOutfit][index] > 0)
+ {
+ mItemClicked = true;
+ }
+ else
+ {
+ if (dragDrop.isSelected())
+ {
+ mItems[mCurrentOutfit][index] = dragDrop.getSelected();
+ mItemColors[mCurrentOutfit][index] = dragDrop.getSelectedColor();
+ dragDrop.deselect();
+ }
+ }
+
+ Window::mousePressed(event);
+}
+
+void OutfitWindow::mouseReleased(gcn::MouseEvent &event)
+{
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (mCurrentOutfit < 0 || mCurrentOutfit
+ >= static_cast<signed int>(OUTFITS_COUNT))
+ {
+ return;
+ }
+ const int index = getIndexFromGrid(event.getX(), event.getY());
+ if (index == -1)
+ {
+ dragDrop.clear();
+ Window::mouseReleased(event);
+ return;
+ }
+ mMoved = false;
+ event.consume();
+ if (!dragDrop.isEmpty())
+ {
+ if (dragDrop.isSourceItemContainer())
+ {
+ mItems[mCurrentOutfit][index] = dragDrop.getItem();
+ mItemColors[mCurrentOutfit][index] = dragDrop.getItemColor();
+ dragDrop.clear();
+ dragDrop.deselect();
+ }
+ }
+ if (mItemClicked)
+ mItemClicked = false;
+ }
+ Window::mouseReleased(event);
+}
+
+int OutfitWindow::getIndexFromGrid(const int pointX, const int pointY) const
+{
+ const gcn::Rectangle tRect = gcn::Rectangle(mPadding, mTitleBarHeight,
+ mGridWidth * mBoxWidth, mGridHeight * mBoxHeight);
+ if (!tRect.isPointInRect(pointX, pointY))
+ return -1;
+ const int index = (((pointY - mTitleBarHeight) / mBoxHeight) * mGridWidth)
+ + (pointX - mPadding) / mBoxWidth;
+ if (index >= static_cast<int>(OUTFIT_ITEM_COUNT) || index < 0)
+ return -1;
+ return index;
+}
+
+void OutfitWindow::unequipNotInOutfit(const int outfit) const
+{
+ // here we think that outfit is correct index
+
+ const Inventory *const inventory = PlayerInfo::getInventory();
+ if (!inventory)
+ return;
+
+ const unsigned int invSize = inventory->getSize();
+ for (unsigned i = 0; i < invSize; i++)
+ {
+ const Item *const item = inventory->getItem(i);
+ if (item && item->isEquipped())
+ {
+ bool found = false;
+ for (unsigned f = 0; f < OUTFIT_ITEM_COUNT; f++)
+ {
+ if (item->getId() == mItems[outfit][f])
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ Net::getInventoryHandler()->unequipItem(item);
+ }
+ }
+}
+
+std::string OutfitWindow::keyName(const int number) const
+{
+ if (number < 0 || number >= SHORTCUT_EMOTES)
+ return "";
+ return inputManager.getKeyStringLong(static_cast<int>(
+ Input::KEY_EMOTE_1) + number);
+}
+
+void OutfitWindow::next()
+{
+ if (mCurrentOutfit < (static_cast<int>(OUTFITS_COUNT) - 1))
+ mCurrentOutfit++;
+ else
+ mCurrentOutfit = 0;
+ showCurrentOutfit();
+}
+
+void OutfitWindow::previous()
+{
+ if (mCurrentOutfit > 0)
+ mCurrentOutfit--;
+ else
+ mCurrentOutfit = OUTFITS_COUNT - 1;
+ showCurrentOutfit();
+}
+
+void OutfitWindow::showCurrentOutfit()
+{
+ // TRANSLATORS: outfits window label
+ mCurrentLabel->setCaption(strprintf(_("Outfit: %d"), mCurrentOutfit + 1));
+ if (mCurrentOutfit < static_cast<int>(OUTFITS_COUNT))
+ mUnequipCheck->setSelected(mItemsUnequip[mCurrentOutfit]);
+ else
+ mUnequipCheck->setSelected(false);
+ // TRANSLATORS: outfits window label
+ mKeyLabel->setCaption(strprintf(_("Key: %s"),
+ keyName(mCurrentOutfit).c_str()));
+ mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit);
+}
+
+void OutfitWindow::wearNextOutfit(const bool all)
+{
+ next();
+ if (!all && mCurrentOutfit >= 0 && mCurrentOutfit
+ < static_cast<int>(OUTFITS_COUNT))
+ {
+ bool fromStart = false;
+ while (!mItemsUnequip[mCurrentOutfit])
+ {
+ next();
+ if (mCurrentOutfit == 0)
+ {
+ if (!fromStart)
+ fromStart = true;
+ else
+ return;
+ }
+ }
+ }
+ wearOutfit(mCurrentOutfit);
+}
+
+void OutfitWindow::wearPreviousOutfit(const bool all)
+{
+ previous();
+ if (!all && mCurrentOutfit >= 0 && mCurrentOutfit
+ < static_cast<int>(OUTFITS_COUNT))
+ {
+ bool fromStart = false;
+ while (!mItemsUnequip[mCurrentOutfit])
+ {
+ previous();
+ if (mCurrentOutfit == 0)
+ {
+ if (!fromStart)
+ fromStart = true;
+ else
+ return;
+ }
+ }
+ }
+ wearOutfit(mCurrentOutfit);
+}
+
+void OutfitWindow::copyFromEquiped()
+{
+ copyFromEquiped(mCurrentOutfit);
+}
+
+void OutfitWindow::copyFromEquiped(const int dst)
+{
+ const Inventory *const inventory = PlayerInfo::getInventory();
+ if (!inventory)
+ return;
+
+ int outfitCell = 0;
+ for (unsigned i = 0, sz = inventory->getSize(); i < sz; i++)
+ {
+ const Item *const item = inventory->getItem(i);
+ if (item && item->isEquipped())
+ {
+ mItems[dst][outfitCell] = item->getId();
+ mItemColors[dst][outfitCell++] = item->getColor();
+ if (outfitCell >= static_cast<int>(OUTFIT_ITEM_COUNT))
+ break;
+ }
+ }
+}
+
+void OutfitWindow::wearAwayOutfit()
+{
+ copyFromEquiped(OUTFITS_COUNT);
+ wearOutfit(mAwayOutfit, false);
+}
+
+void OutfitWindow::unwearAwayOutfit()
+{
+ wearOutfit(OUTFITS_COUNT);
+}
+
+void OutfitWindow::clearCurrentOutfit()
+{
+ if (mCurrentOutfit < 0 || mCurrentOutfit
+ >= static_cast<signed int>(OUTFITS_COUNT))
+ {
+ return;
+ }
+ for (unsigned f = 0; f < OUTFIT_ITEM_COUNT; f++)
+ {
+ mItems[mCurrentOutfit][f] = -1;
+ mItemColors[mCurrentOutfit][f] = 1;
+ }
+}
diff --git a/src/gui/windows/outfitwindow.h b/src/gui/windows/outfitwindow.h
new file mode 100644
index 000000000..5f7d32e02
--- /dev/null
+++ b/src/gui/windows/outfitwindow.h
@@ -0,0 +1,129 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_OUTFITWINDOW_H
+#define GUI_OUTFITWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+const unsigned int OUTFITS_COUNT = 100;
+const unsigned int OUTFIT_ITEM_COUNT = 16;
+
+class Button;
+class CheckBox;
+class Label;
+
+class OutfitWindow final : public Window,
+ private gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ OutfitWindow();
+
+ A_DELETE_COPY(OutfitWindow)
+
+ /**
+ * Destructor.
+ */
+ ~OutfitWindow();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void draw(gcn::Graphics *graphics) override;
+
+ void mousePressed(gcn::MouseEvent &event) override;
+
+ void mouseDragged(gcn::MouseEvent &event) override;
+
+ void mouseReleased(gcn::MouseEvent &event) override;
+
+ void load(const bool oldConfig = false);
+
+ void wearOutfit(const int outfit, const bool unwearEmpty = true,
+ const bool select = false);
+
+ void copyOutfit(const int outfit);
+
+ void copyOutfit(const int src, const int dst);
+
+ void copyFromEquiped();
+
+ void copyFromEquiped(const int dst);
+
+ void unequipNotInOutfit(const int outfit) const;
+
+ void next();
+
+ void previous();
+
+ void wearNextOutfit(const bool all = false);
+
+ void wearPreviousOutfit(const bool all = false);
+
+ void wearAwayOutfit();
+
+ void unwearAwayOutfit();
+
+ void showCurrentOutfit();
+
+ std::string keyName(const int number) const A_WARN_UNUSED;
+
+ void clearCurrentOutfit();
+
+ private:
+ Button *mPreviousButton;
+ Button *mNextButton;
+ Button *mEquipButtom;
+ Label *mCurrentLabel;
+ CheckBox *mUnequipCheck;
+ CheckBox *mAwayOutfitCheck;
+ int mCurrentOutfit;
+ Label *mKeyLabel;
+
+ int getIndexFromGrid(const int pointX,
+ const int pointY) const A_WARN_UNUSED;
+ void save() const;
+
+ int mBoxWidth;
+ int mBoxHeight;
+ int mGridWidth;
+ int mGridHeight;
+
+ int mItems[OUTFITS_COUNT + 1][OUTFIT_ITEM_COUNT];
+ int mAwayOutfit;
+
+ gcn::Color mBorderColor;
+ gcn::Color mBackgroundColor;
+ unsigned char mItemColors[OUTFITS_COUNT + 1][OUTFIT_ITEM_COUNT];
+ bool mItemClicked;
+ bool mItemsUnequip[OUTFITS_COUNT];
+
+ static float mAlpha;
+};
+
+extern OutfitWindow *outfitWindow;
+
+#endif // GUI_OUTFITWINDOW_H
diff --git a/src/gui/windows/questswindow.cpp b/src/gui/windows/questswindow.cpp
new file mode 100644
index 000000000..8baa2454c
--- /dev/null
+++ b/src/gui/windows/questswindow.cpp
@@ -0,0 +1,567 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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/windows/questswindow.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+#include "effectmanager.h"
+
+#include "being/localplayer.h"
+
+#include "gui/gui.h"
+#include "gui/sdlfont.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/extendedlistbox.h"
+#include "gui/widgets/extendednamesmodel.h"
+#include "gui/widgets/itemlinkhandler.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include "utils/translation/podict.h"
+
+#include "debug.h"
+
+enum QuestType
+{
+ QUEST_TEXT = 0,
+ QUEST_NAME = 1,
+ QUEST_REWARD = 2
+};
+
+struct QuestItemText final
+{
+ QuestItemText(const std::string &text0, const int type0) :
+ text(text0), type(type0)
+ {
+ }
+
+ std::string text;
+ int type;
+};
+
+struct QuestItem final
+{
+ QuestItem() :
+ var(0),
+ name(),
+ group(),
+ incomplete(),
+ complete(),
+ texts(),
+ completeFlag(-1),
+ broken(false)
+ {
+ }
+
+ int var;
+ std::string name;
+ std::string group;
+ std::set<int> incomplete;
+ std::set<int> complete;
+ std::vector<QuestItemText> texts;
+ int completeFlag;
+ bool broken;
+};
+
+class QuestsModel final : public ExtendedNamesModel
+{
+ public:
+ QuestsModel() :
+ ExtendedNamesModel()
+ {
+ }
+
+ A_DELETE_COPY(QuestsModel)
+
+ ~QuestsModel()
+ { }
+};
+
+struct QuestEffect final
+{
+ QuestEffect() :
+ map(),
+ var(0),
+ id(0),
+ effectId(0),
+ values()
+ {
+ }
+
+ std::string map;
+ int var;
+ int id;
+ int effectId;
+ std::set<int> values;
+};
+
+QuestsWindow::QuestsWindow() :
+ // TRANSLATORS: quests window name
+ Window(_("Quests"), false, nullptr, "quests.xml"),
+ gcn::ActionListener(),
+ mQuestsModel(new QuestsModel),
+ mQuestsListBox(new ExtendedListBox(this,
+ mQuestsModel, "extendedlistbox.xml")),
+ mQuestScrollArea(new ScrollArea(mQuestsListBox,
+ getOptionBool("showlistbackground"), "quests_list_background.xml")),
+ mItemLinkHandler(new ItemLinkHandler),
+ mText(new BrowserBox(this, BrowserBox::AUTO_WRAP)),
+ mTextScrollArea(new ScrollArea(mText,
+ getOptionBool("showtextbackground"), "quests_text_background.xml")),
+ // TRANSLATORS: quests window button
+ mCloseButton(new Button(this, _("Close"), "close", this)),
+ mVars(),
+ mQuests(),
+ mAllEffects(),
+ mMapEffects(),
+ mNpcEffects(),
+ mQuestLinks(),
+ mCompleteIcon(Theme::getImageFromThemeXml("complete_icon.xml", "")),
+ mIncompleteIcon(Theme::getImageFromThemeXml("incomplete_icon.xml", "")),
+ mNewQuestEffectId(paths.getIntValue("newQuestEffectId")),
+ mCompleteQuestEffectId(paths.getIntValue("completeQuestEffectId")),
+ mMap(nullptr)
+{
+ setWindowName("Quests");
+ setResizable(true);
+ setCloseButton(true);
+ setStickyButtonLock(true);
+ setSaveVisible(true);
+
+ setDefaultSize(400, 350, ImageRect::RIGHT);
+ setMinWidth(310);
+ setMinHeight(220);
+
+ mQuestsListBox->setActionEventId("select");
+ mQuestsListBox->addActionListener(this);
+
+ mQuestScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mText->setOpaque(false);
+ mText->setLinkHandler(mItemLinkHandler);
+ mTextScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mQuestsListBox->setWidth(500);
+ if (gui->getNpcFont()->getHeight() < 20)
+ mQuestsListBox->setRowHeight(20);
+ else
+ mQuestsListBox->setRowHeight(gui->getNpcFont()->getHeight());
+
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+
+ placer(0, 0, mQuestScrollArea, 4, 3).setPadding(3);
+ placer(4, 0, mTextScrollArea, 4, 3).setPadding(3);
+ placer(7, 3, mCloseButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ loadWindowState();
+ enableVisibleSound(true);
+ loadXml();
+}
+
+QuestsWindow::~QuestsWindow()
+{
+ delete mQuestsModel;
+ mQuestsModel = nullptr;
+
+ for (std::map<int, std::vector<QuestItem*> >::iterator it
+ = mQuests.begin(), it_end = mQuests.end(); it != it_end; ++ it)
+ {
+ std::vector<QuestItem*> &quests = (*it).second;
+ for (std::vector<QuestItem*>::iterator it2 = quests.begin(),
+ it2_end = quests.end(); it2 != it2_end; ++ it2)
+ {
+ delete *it2;
+ }
+ }
+ delete_all(mAllEffects);
+ mAllEffects.clear();
+
+ delete mItemLinkHandler;
+ mItemLinkHandler = nullptr;
+ mQuests.clear();
+ mQuestLinks.clear();
+ if (mCompleteIcon)
+ {
+ mCompleteIcon->decRef();
+ mCompleteIcon = nullptr;
+ }
+ if (mIncompleteIcon)
+ {
+ mIncompleteIcon->decRef();
+ mIncompleteIcon = nullptr;
+ }
+}
+
+void QuestsWindow::loadXml()
+{
+ XML::Document doc(paths.getStringValue("questsFile"));
+ const XmlNodePtr root = doc.rootNode();
+ if (!root)
+ return;
+
+ for_each_xml_child_node(varNode, root)
+ {
+ if (xmlNameEqual(varNode, "var"))
+ {
+ const int id = XML::getProperty(varNode, "id", 0);
+ if (id < 0)
+ continue;
+ for_each_xml_child_node(questNode, varNode)
+ {
+ if (xmlNameEqual(questNode, "quest"))
+ loadQuest(id, questNode);
+ else if (xmlNameEqual(questNode, "effect"))
+ loadEffect(id, questNode);
+ }
+ }
+ }
+}
+
+void QuestsWindow::loadQuest(const int var, const XmlNodePtr node)
+{
+ QuestItem *const quest = new QuestItem();
+ // TRANSLATORS: quests window quest name
+ quest->name = XML::langProperty(node, "name", _("unknown"));
+ quest->group = XML::getProperty(node, "group", "");
+ std::string incompleteStr = XML::getProperty(node, "incomplete", "");
+ std::string completeStr = XML::getProperty(node, "complete", "");
+ if (incompleteStr.empty() && completeStr.empty())
+ {
+ logger->log("complete flags incorrect");
+ delete quest;
+ return;
+ }
+ splitToIntSet(quest->incomplete, incompleteStr, ',');
+ splitToIntSet(quest->complete, completeStr, ',');
+ if (quest->incomplete.empty() && quest->complete.empty())
+ {
+ logger->log("complete flags incorrect");
+ delete quest;
+ return;
+ }
+ if (quest->incomplete.empty() || quest->complete.empty())
+ quest->broken = true;
+
+ for_each_xml_child_node(dataNode, node)
+ {
+ if (!xmlTypeEqual(dataNode, XML_ELEMENT_NODE))
+ continue;
+ const char *const data = reinterpret_cast<const char*>(
+ xmlNodeGetContent(dataNode));
+ if (!data)
+ continue;
+ std::string str = translator->getStr(data);
+
+ if (xmlNameEqual(dataNode, "text"))
+ quest->texts.push_back(QuestItemText(str, QUEST_TEXT));
+ else if (xmlNameEqual(dataNode, "name"))
+ quest->texts.push_back(QuestItemText(str, QUEST_NAME));
+ else if (xmlNameEqual(dataNode, "reward"))
+ quest->texts.push_back(QuestItemText(str, QUEST_REWARD));
+ }
+ mQuests[var].push_back(quest);
+}
+
+void QuestsWindow::loadEffect(const int var, const XmlNodePtr node)
+{
+ QuestEffect *const effect = new QuestEffect;
+ effect->map = XML::getProperty(node, "map", "");
+ effect->id = XML::getProperty(node, "npc", -1);
+ effect->effectId = XML::getProperty(node, "effect", -1);
+ const std::string values = XML::getProperty(node, "value", "");
+ splitToIntSet(effect->values, values, ',');
+
+ if (effect->map.empty() || effect->id == -1
+ || effect->effectId == -1 || values.empty())
+ {
+ delete effect;
+ return;
+ }
+ effect->var = var;
+ mAllEffects.push_back(effect);
+}
+
+void QuestsWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "select")
+ {
+ const int id = mQuestsListBox->getSelected();
+ if (id < 0)
+ return;
+ showQuest(mQuestLinks[id]);
+ }
+ else if (eventId == "close")
+ {
+ setVisible(false);
+ }
+}
+
+void QuestsWindow::updateQuest(const int var, const int val)
+{
+ mVars[var] = val;
+}
+
+void QuestsWindow::rebuild(const bool playSound)
+{
+ mQuestsModel->clear();
+ mQuestLinks.clear();
+ StringVect &names = mQuestsModel->getNames();
+ std::vector<Image*> &images = mQuestsModel->getImages();
+ std::vector<QuestItem*> complete;
+ std::vector<QuestItem*> incomplete;
+ std::vector<QuestItem*> hidden;
+ int updatedQuest = -1;
+ int newCompleteStatus = -1;
+
+ for (std::map<int, int>::const_iterator it = mVars.begin(),
+ it_end = mVars.end(); it != it_end; ++ it)
+ {
+ const int var = (*it).first;
+ const int val = (*it).second;
+ const std::vector<QuestItem*> &quests = mQuests[var];
+ FOR_EACH (std::vector<QuestItem*>::const_iterator, it2, quests)
+ {
+ if (!*it2)
+ continue;
+ QuestItem *const quest = *it2;
+ // complete quest
+ if (quest->complete.find(val) != quest->complete.end())
+ complete.push_back(quest);
+ // incomplete quest
+ else if (quest->incomplete.find(val) != quest->incomplete.end())
+ incomplete.push_back(quest);
+ // hidden quest
+ else
+ hidden.push_back(quest);
+ }
+ }
+
+ int k = 0;
+
+ for (std::vector<QuestItem*>::const_iterator it = complete.begin(),
+ it_end = complete.end(); it != it_end; ++ it, k ++)
+ {
+ QuestItem *const quest = *it;
+ if (quest->completeFlag == 0 || (quest->broken
+ && quest->completeFlag == -1))
+ {
+ updatedQuest = k;
+ newCompleteStatus = 1;
+ }
+ quest->completeFlag = 1;
+ mQuestLinks.push_back(quest);
+ names.push_back(quest->name);
+ if (mCompleteIcon)
+ {
+ mCompleteIcon->incRef();
+ images.push_back(mCompleteIcon);
+ }
+ else
+ {
+ images.push_back(nullptr);
+ }
+ }
+
+ for (std::vector<QuestItem*>::const_iterator it = incomplete.begin(),
+ it_end = incomplete.end(); it != it_end; ++ it, k ++)
+ {
+ QuestItem *const quest = *it;
+ if (quest->completeFlag == -1)
+ {
+ updatedQuest = k;
+ newCompleteStatus = 0;
+ }
+ quest->completeFlag = 0;
+ mQuestLinks.push_back(quest);
+ names.push_back(quest->name);
+ if (mIncompleteIcon)
+ {
+ mIncompleteIcon->incRef();
+ images.push_back(mIncompleteIcon);
+ }
+ else
+ {
+ images.push_back(nullptr);
+ }
+ }
+
+ FOR_EACH (std::vector<QuestItem*>::const_iterator, it, hidden)
+ (*it)->completeFlag = -1;
+
+ if (updatedQuest == -1 || updatedQuest >= static_cast<int>(
+ mQuestLinks.size()))
+ {
+ updatedQuest = static_cast<int>(mQuestLinks.size() - 1);
+ }
+ if (updatedQuest >= 0)
+ {
+ mQuestsListBox->setSelected(updatedQuest);
+ showQuest(mQuestLinks[updatedQuest]);
+ if (playSound && effectManager)
+ {
+ switch (newCompleteStatus)
+ {
+ case 0:
+ effectManager->trigger(mNewQuestEffectId, player_node);
+ break;
+ case 1:
+ effectManager->trigger(mCompleteQuestEffectId,
+ player_node);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ updateEffects();
+}
+
+void QuestsWindow::showQuest(const QuestItem *const quest)
+{
+ if (!quest || !translator)
+ return;
+
+ const std::vector<QuestItemText> &texts = quest->texts;
+ mText->clearRows();
+ FOR_EACH (std::vector<QuestItemText>::const_iterator, it, texts)
+ {
+ const QuestItemText &data = *it;
+ switch (data.type)
+ {
+ case QUEST_TEXT:
+ case QUEST_REWARD:
+ default:
+ mText->addRow(translator->getStr(data.text));
+ break;
+ case QUEST_NAME:
+ mText->addRow(std::string("[").append(translator->getStr(
+ data.text)).append("]"));
+ break;
+ }
+ }
+}
+
+void QuestsWindow::setMap(const Map *const map)
+{
+ if (mMap != map)
+ {
+ mMap = map;
+ mMapEffects.clear();
+ if (!mMap)
+ return;
+
+ const std::string name = mMap->getProperty("shortName");
+ FOR_EACH (std::vector<QuestEffect*>::const_iterator, it, mAllEffects)
+ {
+ const QuestEffect *const effect = *it;
+ if (effect && name == effect->map)
+ mMapEffects.push_back(effect);
+ }
+ updateEffects();
+ }
+}
+
+void QuestsWindow::updateEffects()
+{
+ NpcQuestEffectMap oldNpc = mNpcEffects;
+ mNpcEffects.clear();
+
+ FOR_EACH (std::vector<const QuestEffect*>::const_iterator,
+ it, mMapEffects)
+ {
+ const QuestEffect *const effect = *it;
+ if (effect)
+ {
+ const std::map<int, int>::const_iterator
+ varIt = mVars.find(effect->var);
+ if (varIt != mVars.end())
+ {
+ const std::set<int> &vals = effect->values;
+ if (vals.find(mVars[effect->var]) != vals.end())
+ mNpcEffects[effect->id] = effect;
+ }
+ }
+ }
+ if (!actorSpriteManager)
+ return;
+
+ std::set<int> removeEffects;
+ std::map<int, int> addEffects;
+
+ // for old effects
+ FOR_EACH (NpcQuestEffectMapCIter, it, oldNpc)
+ {
+ const int id = (*it).first;
+ const QuestEffect *const effect = (*it).second;
+
+ const NpcQuestEffectMapCIter itNew = mNpcEffects.find(id);
+ if (itNew == mNpcEffects.end())
+ { // in new list no effect for this npc
+ removeEffects.insert(id);
+ }
+ else
+ { // in new list exists effect for this npc
+ const QuestEffect *const newEffect = (*itNew).second;
+ if (effect != newEffect)
+ { // new effects is not equal to old effect
+ addEffects[id] = newEffect->effectId;
+ removeEffects.insert(id);
+ }
+ }
+ }
+
+ // for new effects
+ FOR_EACH (NpcQuestEffectMapCIter, it, mNpcEffects)
+ {
+ const int id = (*it).first;
+ const QuestEffect *const effect = (*it).second;
+
+ const NpcQuestEffectMapCIter itNew = oldNpc.find(id);
+ // check if old effect was not present
+ if (itNew == oldNpc.end())
+ addEffects[id] = effect->effectId;
+ }
+ if (!removeEffects.empty() || !addEffects.empty())
+ actorSpriteManager->updateEffects(addEffects, removeEffects);
+}
+
+void QuestsWindow::addEffect(Being *const being)
+{
+ if (!being)
+ return;
+ const int id = being->getSubType();
+ const std::map<int, const QuestEffect*>::const_iterator
+ it = mNpcEffects.find(id);
+ if (it != mNpcEffects.end())
+ {
+ const QuestEffect *const effect = (*it).second;
+ if (effect)
+ being->addSpecialEffect(effect->effectId);
+ }
+}
diff --git a/src/gui/windows/questswindow.h b/src/gui/windows/questswindow.h
new file mode 100644
index 000000000..72fdb7bb6
--- /dev/null
+++ b/src/gui/windows/questswindow.h
@@ -0,0 +1,106 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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_QUESTSWINDOW_H
+#define GUI_QUESTSWINDOW_H
+
+#include "localconsts.h"
+
+#include "gui/widgets/window.h"
+
+#include "utils/xml.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <map>
+#include <vector>
+
+class Being;
+class Button;
+class BrowserBox;
+class ExtendedListBox;
+class ItemLinkHandler;
+class Map;
+class ScrollArea;
+class QuestsModel;
+
+struct QuestEffect;
+struct QuestItem;
+
+typedef std::map<int, const QuestEffect*> NpcQuestEffectMap;
+typedef NpcQuestEffectMap::const_iterator NpcQuestEffectMapCIter;
+
+class QuestsWindow final : public Window,
+ public gcn::ActionListener
+{
+ public:
+ QuestsWindow();
+
+ A_DELETE_COPY(QuestsWindow)
+
+ ~QuestsWindow();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void updateQuest(const int var, const int val);
+
+ void rebuild(const bool playSound);
+
+ void showQuest(const QuestItem *const quest);
+
+ void setMap(const Map *const map);
+
+ void updateEffects();
+
+ void addEffect(Being *const being);
+
+ private:
+ void loadXml();
+
+ void loadQuest(const int var, const XmlNodePtr node);
+
+ void loadEffect(const int var, const XmlNodePtr node);
+
+ QuestsModel *mQuestsModel;
+ ExtendedListBox *mQuestsListBox;
+ ScrollArea *mQuestScrollArea;
+ ItemLinkHandler *mItemLinkHandler;
+ BrowserBox *mText;
+ ScrollArea *mTextScrollArea;
+ Button *mCloseButton;
+ // quest variables: var, value
+ std::map<int, int> mVars;
+ // quests: var, quests
+ std::map<int, std::vector<QuestItem*> > mQuests;
+ std::vector<QuestEffect*> mAllEffects;
+ std::vector<const QuestEffect*> mMapEffects;
+ // npc effects for current map and values: npc, effect
+ NpcQuestEffectMap mNpcEffects;
+ std::vector<QuestItem*> mQuestLinks;
+ Image *mCompleteIcon;
+ Image *mIncompleteIcon;
+ int mNewQuestEffectId;
+ int mCompleteQuestEffectId;
+ const Map *mMap;
+};
+
+extern QuestsWindow *questsWindow;
+
+#endif // GUI_QUESTSWINDOW_H
diff --git a/src/gui/windows/quitdialog.cpp b/src/gui/windows/quitdialog.cpp
new file mode 100644
index 000000000..0eeece318
--- /dev/null
+++ b/src/gui/windows/quitdialog.cpp
@@ -0,0 +1,256 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/quitdialog.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "game.h"
+#include "soundconsts.h"
+#include "soundmanager.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/viewport.h"
+
+#include "gui/widgets/layout.h"
+#include "gui/widgets/button.h"
+#include "gui/widgets/radiobutton.h"
+
+#include "net/charserverhandler.h"
+#include "net/gamehandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+#include "utils/process.h"
+
+#include "debug.h"
+
+QuitDialog::QuitDialog(QuitDialog **const pointerToMe):
+ // TRANSLATORS: quit dialog name
+ Window(_("Quit"), true, nullptr, "quit.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mOptions(),
+ // TRANSLATORS: quit dialog button
+ mLogoutQuit(new RadioButton(this, _("Quit"), "quitdialog")),
+ // TRANSLATORS: quit dialog button
+ mForceQuit(new RadioButton(this, _("Quit"), "quitdialog")),
+ mSwitchAccountServer(new RadioButton(this,
+ // TRANSLATORS: quit dialog button
+ _("Switch server"), "quitdialog")),
+ mSwitchCharacter(new RadioButton(this,
+ // TRANSLATORS: quit dialog button
+ _("Switch character"), "quitdialog")),
+ mRate(nullptr),
+ // TRANSLATORS: quit dialog button
+ mOkButton(new Button(this, _("OK"), "ok", this)),
+ // TRANSLATORS: quit dialog button
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ mMyPointer(pointerToMe),
+ mNeedForceQuit(false)
+{
+ addKeyListener(this);
+
+ ContainerPlacer placer = getPlacer(0, 0);
+ const State state = client->getState();
+ mNeedForceQuit = (state == STATE_CHOOSE_SERVER
+ || state == STATE_CONNECT_SERVER || state == STATE_LOGIN
+ || state == STATE_PRE_LOGIN || state == STATE_LOGIN_ATTEMPT
+ || state == STATE_UPDATE || state == STATE_LOAD_DATA);
+
+ // All states, when we're not logged in to someone.
+ if (mNeedForceQuit)
+ {
+ placeOption(placer, mForceQuit);
+ }
+ else
+ {
+ // Only added if we are connected to an accountserver or gameserver
+ placeOption(placer, mLogoutQuit);
+ placeOption(placer, mSwitchAccountServer);
+
+ // Only added if we are connected to a gameserver
+ if (state == STATE_GAME)
+ placeOption(placer, mSwitchCharacter);
+ }
+
+/*
+#ifdef ANDROID
+ if (config.getBoolValue("rated") == false
+ && config.getIntValue("gamecount") > 3)
+ {
+ mRate = new RadioButton(this, _("Rate in google play"), "quitdialog");
+ placeOption(placer, mRate);
+ mOptions[mOptions.size() - 1]->setSelected(true);
+ }
+ else
+#endif
+*/
+ {
+ mOptions[0]->setSelected(true);
+ }
+
+ placer = getPlacer(0, 1);
+ placer(1, 0, mOkButton, 1);
+ placer(2, 0, mCancelButton, 1);
+
+ reflowLayout(200, 0);
+ setLocationRelativeTo(getParent());
+ setVisible(true);
+ soundManager.playGuiSound(SOUND_SHOW_WINDOW);
+ requestModalFocus();
+ mOkButton->requestFocus();
+}
+
+QuitDialog::~QuitDialog()
+{
+ if (mMyPointer)
+ *mMyPointer = nullptr;
+ delete mForceQuit;
+ mForceQuit = nullptr;
+ delete mLogoutQuit;
+ mLogoutQuit = nullptr;
+ delete mSwitchAccountServer;
+ mSwitchAccountServer = nullptr;
+ delete mSwitchCharacter;
+ mSwitchCharacter = nullptr;
+}
+
+void QuitDialog::placeOption(ContainerPlacer &placer,
+ RadioButton *const option)
+{
+ placer(0, static_cast<int>(mOptions.size()), option, 3);
+ mOptions.push_back(option);
+}
+
+void QuitDialog::action(const gcn::ActionEvent &event)
+{
+ soundManager.playGuiSound(SOUND_HIDE_WINDOW);
+ if (event.getId() == "ok")
+ {
+ if (viewport)
+ {
+ const Map *const map = viewport->getMap();
+ if (map)
+ map->saveExtraLayer();
+ }
+
+ if (mForceQuit->isSelected())
+ {
+ client->setState(STATE_FORCE_QUIT);
+ }
+ else if (mLogoutQuit->isSelected())
+ {
+ Game::closeDialogs();
+ client->setState(STATE_EXIT);
+ }
+ else if (mRate && mRate->isSelected())
+ {
+ openBrowser("https://play.google.com/store/apps/details?"
+ "id=org.evolonline.beta.manaplus");
+ config.setValue("rated", true);
+ if (mNeedForceQuit)
+ {
+ client->setState(STATE_FORCE_QUIT);
+ }
+ else
+ {
+ Game::closeDialogs();
+ client->setState(STATE_EXIT);
+ }
+ }
+ else if (Net::getGameHandler()->isConnected()
+ && mSwitchAccountServer->isSelected())
+ {
+ Game::closeDialogs();
+ client->setState(STATE_SWITCH_SERVER);
+ }
+ else if (mSwitchCharacter->isSelected())
+ {
+ if (client->getState() == STATE_GAME)
+ {
+ Net::getCharServerHandler()->switchCharacter();
+ Game::closeDialogs();
+ }
+ }
+ }
+ scheduleDelete();
+}
+
+void QuitDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ const int actionId = static_cast<KeyEvent*>(&keyEvent)->getActionId();
+ int dir = 0;
+
+ switch (actionId)
+ {
+ case Input::KEY_GUI_SELECT:
+ case Input::KEY_GUI_SELECT2:
+ action(gcn::ActionEvent(nullptr, mOkButton->getActionEventId()));
+ break;
+ case Input::KEY_GUI_CANCEL:
+ action(gcn::ActionEvent(nullptr,
+ mCancelButton->getActionEventId()));
+ break;
+ case Input::KEY_GUI_UP:
+ dir = -1;
+ break;
+ case Input::KEY_GUI_DOWN:
+ dir = 1;
+ break;
+ default:
+ break;
+ }
+
+ if (dir != 0)
+ {
+ std::vector<RadioButton*>::const_iterator it = mOptions.begin();
+ const std::vector<RadioButton*>::const_iterator
+ it_end = mOptions.end();
+
+ for (; it < it_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/windows/quitdialog.h b/src/gui/windows/quitdialog.h
new file mode 100644
index 000000000..b0bde98fc
--- /dev/null
+++ b/src/gui/windows/quitdialog.h
@@ -0,0 +1,83 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_QUITDIALOG_H
+#define GUI_QUITDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+#include <vector>
+
+class Button;
+class RadioButton;
+
+/**
+ * The quit dialog.
+ *
+ * \ingroup Interface
+ */
+class QuitDialog final : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @pointerToMe will be set to NULL when the QuitDialog is destroyed
+ */
+ explicit QuitDialog(QuitDialog **const pointerToMe);
+
+ A_DELETE_COPY(QuitDialog)
+
+ /**
+ * Destructor
+ */
+ ~QuitDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ private:
+ void placeOption(ContainerPlacer &placer,
+ RadioButton *const option);
+ std::vector<RadioButton*> mOptions;
+
+ RadioButton *mLogoutQuit;
+ RadioButton *mForceQuit;
+ RadioButton *mSwitchAccountServer;
+ RadioButton *mSwitchCharacter;
+ RadioButton *mRate;
+ Button *mOkButton;
+ Button *mCancelButton;
+
+ QuitDialog **mMyPointer;
+ bool mNeedForceQuit;
+};
+
+#endif // GUI_QUITDIALOG_H
diff --git a/src/gui/windows/registerdialog.cpp b/src/gui/windows/registerdialog.cpp
new file mode 100644
index 000000000..841b3768f
--- /dev/null
+++ b/src/gui/windows/registerdialog.cpp
@@ -0,0 +1,316 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/registerdialog.h"
+
+#include "client.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/windows/okdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/passwordfield.h"
+#include "gui/widgets/radiobutton.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+WrongDataNoticeListener::WrongDataNoticeListener():
+ gcn::ActionListener(),
+ mTarget(nullptr)
+{
+}
+
+void WrongDataNoticeListener::setTarget(TextField *const textField)
+{
+ mTarget = textField;
+}
+
+void WrongDataNoticeListener::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "ok" && mTarget)
+ mTarget->requestFocus();
+}
+
+RegisterDialog::RegisterDialog(LoginData *const data) :
+ // TRANSLATORS: register dialog name
+ Window(_("Register"), false, nullptr, "register.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mLoginData(data),
+ mUserField(new TextField(this, mLoginData->username)),
+ mPasswordField(new PasswordField(this, mLoginData->password)),
+ mConfirmField(new PasswordField(this)),
+ mEmailField(nullptr),
+ // TRANSLATORS: register dialog. button.
+ mRegisterButton(new Button(this, _("Register"), "register", this)),
+ // TRANSLATORS: register dialog. button.
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ mMaleButton(nullptr),
+ mFemaleButton(nullptr),
+ mOtherButton(nullptr),
+ mWrongDataNoticeListener(new WrongDataNoticeListener)
+{
+ setCloseButton(true);
+
+ const int optionalActions = Net::getLoginHandler()->
+ supportedOptionalActions();
+
+ // TRANSLATORS: register dialog. label.
+ Label *const userLabel = new Label(this, _("Name:"));
+ // TRANSLATORS: register dialog. label.
+ Label *const passwordLabel = new Label(this, _("Password:"));
+ // TRANSLATORS: register dialog. label.
+ Label *const confirmLabel = new Label(this, _("Confirm:"));
+
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+ placer(0, 0, userLabel);
+ placer(0, 1, passwordLabel);
+ placer(0, 2, confirmLabel);
+
+ placer(1, 0, mUserField, 3).setPadding(2);
+ placer(1, 1, mPasswordField, 3).setPadding(2);
+ placer(1, 2, mConfirmField, 3).setPadding(2);
+
+ int row = 3;
+
+ if (optionalActions & Net::LoginHandler::SetGenderOnRegister)
+ {
+ // TRANSLATORS: register dialog. button.
+ mMaleButton = new RadioButton(this, _("Male"), "sex", true);
+ // TRANSLATORS: register dialog. button.
+ mFemaleButton = new RadioButton(this, _("Female"), "sex", false);
+ if (serverVersion >= 5)
+ {
+ // TRANSLATORS: register dialog. button.
+ mOtherButton = new RadioButton(this, _("Other"), "sex", false);
+ placer(0, row, mMaleButton);
+ placer(1, row, mFemaleButton);
+ placer(2, row, mOtherButton);
+ }
+ else
+ {
+ placer(1, row, mMaleButton);
+ placer(2, row, mFemaleButton);
+ }
+
+ row++;
+ }
+
+ if (optionalActions & Net::LoginHandler::SetEmailOnRegister)
+ {
+ // TRANSLATORS: register dialog. label.
+ Label *const emailLabel = new Label(this, _("Email:"));
+ mEmailField = new TextField(this);
+ placer(0, row, emailLabel);
+ placer(1, row, mEmailField, 3).setPadding(2);
+// row++;
+ }
+
+ placer = getPlacer(0, 2);
+ placer(1, 0, mRegisterButton);
+ placer(2, 0, mCancelButton);
+ reflowLayout(250, 0);
+
+ mUserField->addKeyListener(this);
+ mPasswordField->addKeyListener(this);
+ mConfirmField->addKeyListener(this);
+
+ 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 = nullptr;
+}
+
+void RegisterDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "cancel")
+ {
+ close();
+ }
+ else if (eventId == "register" && canSubmit())
+ {
+ const std::string user = mUserField->getText();
+ logger->log("RegisterDialog::register Username is %s", user.c_str());
+
+ std::string errorMsg;
+ int error = 0;
+
+ const unsigned int minUser = Net::getLoginHandler()
+ ->getMinUserNameLength();
+ const unsigned int maxUser = Net::getLoginHandler()
+ ->getMaxUserNameLength();
+ const unsigned int minPass = Net::getLoginHandler()
+ ->getMinPasswordLength();
+ const unsigned int maxPass = Net::getLoginHandler()
+ ->getMaxPasswordLength();
+
+ if (user.length() < minUser)
+ {
+ // Name too short
+ errorMsg = strprintf
+ // TRANSLATORS: error message
+ (_("The username needs to be at least %u characters long."),
+ minUser);
+ error = 1;
+ }
+ else if (user.length() > maxUser - 1)
+ {
+ // Name too long
+ errorMsg = strprintf
+ // TRANSLATORS: error message
+ (_("The username needs to be less than %u characters long."),
+ maxUser);
+ error = 1;
+ }
+ else if (mPasswordField->getText().length() < minPass)
+ {
+ // Pass too short
+ errorMsg = strprintf
+ // TRANSLATORS: error message
+ (_("The password needs to be at least %u characters long."),
+ minPass);
+ error = 2;
+ }
+ else if (mPasswordField->getText().length() > maxPass)
+ {
+ // Pass too long
+ errorMsg = strprintf
+ // TRANSLATORS: error message
+ (_("The password needs to be less than %u characters long."),
+ maxPass);
+ error = 2;
+ }
+ else if (mPasswordField->getText() != mConfirmField->getText())
+ {
+ // Password does not match with the confirmation one
+ // TRANSLATORS: error message
+ errorMsg = _("Passwords do not match.");
+ error = 2;
+ }
+
+ 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 *const dlg = new OkDialog(
+ // TRANSLATORS: error message
+ _("Error"), errorMsg, DIALOG_ERROR);
+ dlg->addActionListener(mWrongDataNoticeListener);
+ }
+ else
+ {
+ // No errors detected, register the new user.
+ mRegisterButton->setEnabled(false);
+ mLoginData->username = mUserField->getText();
+ mLoginData->password = mPasswordField->getText();
+ if (mFemaleButton && mFemaleButton->isSelected())
+ mLoginData->gender = GENDER_FEMALE;
+ else if (mOtherButton && mOtherButton->isSelected())
+ mLoginData->gender = GENDER_OTHER;
+ else
+ mLoginData->gender = GENDER_MALE;
+
+ if (mEmailField)
+ mLoginData->email = mEmailField->getText();
+ mLoginData->registerLogin = true;
+
+ client->setState(STATE_REGISTER_ATTEMPT);
+ }
+ }
+}
+
+void RegisterDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ if (keyEvent.isConsumed())
+ {
+ mRegisterButton->setEnabled(canSubmit());
+ return;
+ }
+ const int actionId = static_cast<KeyEvent*>(
+ &keyEvent)->getActionId();
+ if (actionId == static_cast<int>(Input::KEY_GUI_CANCEL))
+ {
+ action(gcn::ActionEvent(nullptr, mCancelButton->getActionEventId()));
+ }
+ else if (actionId == static_cast<int>(Input::KEY_GUI_SELECT)
+ || actionId == static_cast<int>(Input::KEY_GUI_SELECT2))
+ {
+ action(gcn::ActionEvent(nullptr, mRegisterButton->getActionEventId()));
+ }
+ else
+ {
+ mRegisterButton->setEnabled(canSubmit());
+ }
+}
+
+bool RegisterDialog::canSubmit() const
+{
+ return !mUserField->getText().empty() &&
+ !mPasswordField->getText().empty() &&
+ !mConfirmField->getText().empty() &&
+ client->getState() == STATE_REGISTER;
+}
+
+void RegisterDialog::close()
+{
+ client->setState(STATE_LOGIN);
+ Window::close();
+}
diff --git a/src/gui/windows/registerdialog.h b/src/gui/windows/registerdialog.h
new file mode 100644
index 000000000..0bc06b92d
--- /dev/null
+++ b/src/gui/windows/registerdialog.h
@@ -0,0 +1,112 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_REGISTERDIALOG_H
+#define GUI_REGISTERDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+class Button;
+class LoginData;
+class RadioButton;
+class TextField;
+
+/**
+ * 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 final : public gcn::ActionListener
+{
+ public:
+ WrongDataNoticeListener();
+
+ A_DELETE_COPY(WrongDataNoticeListener)
+
+ void setTarget(TextField *const textField);
+
+ void action(const gcn::ActionEvent &event) override;
+ private:
+ TextField *mTarget;
+};
+
+/**
+ * The registration dialog.
+ *
+ * \ingroup Interface
+ */
+class RegisterDialog final : 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
+ */
+ explicit RegisterDialog(LoginData *const loginData);
+
+ A_DELETE_COPY(RegisterDialog)
+
+ /**
+ * Destructor
+ */
+ ~RegisterDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Called when a key is pressed in one of the text fields.
+ */
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ void close() override;
+
+ private:
+ /**
+ * Returns whether submit can be enabled. This is true in the register
+ * state, when all necessary fields have some text.
+ */
+ bool canSubmit() const;
+
+ LoginData *mLoginData;
+ TextField *mUserField;
+ TextField *mPasswordField;
+ TextField *mConfirmField;
+ TextField *mEmailField;
+ Button *mRegisterButton;
+ Button *mCancelButton;
+ RadioButton *mMaleButton;
+ RadioButton *mFemaleButton;
+ RadioButton *mOtherButton;
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+};
+
+#endif // GUI_REGISTERDIALOG_H
diff --git a/src/gui/windows/selldialog.cpp b/src/gui/windows/selldialog.cpp
new file mode 100644
index 000000000..6c0edbe74
--- /dev/null
+++ b/src/gui/windows/selldialog.cpp
@@ -0,0 +1,389 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/selldialog.h"
+
+#include "shopitem.h"
+#include "units.h"
+
+#include "being/playerinfo.h"
+
+#include "gui/windows/confirmdialog.h"
+#include "gui/windows/tradewindow.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 "debug.h"
+
+SellDialog::DialogList SellDialog::instances;
+
+SellDialog::SellDialog(const int npcId) :
+ // TRANSLATORS: sell dialog name
+ Window(_("Sell"), false, nullptr, "sell.xml"),
+ gcn::ActionListener(),
+ gcn::SelectionListener(),
+ mNpcId(npcId), mMaxItems(0), mAmountItems(0), mNick("")
+{
+ init();
+}
+
+SellDialog::SellDialog(std::string nick):
+ // TRANSLATORS: sell dialog name
+ Window(_("Sell"), false, nullptr, "sell.xml"),
+ gcn::ActionListener(),
+ gcn::SelectionListener(),
+ mNpcId(-1), mMaxItems(0), mAmountItems(0), mNick(nick)
+{
+ init();
+}
+
+void SellDialog::init()
+{
+ setWindowName("Sell");
+ setResizable(true);
+ setCloseButton(true);
+ setStickyButtonLock(true);
+ setMinWidth(260);
+ setMinHeight(220);
+ setDefaultSize(260, 230, ImageRect::CENTER);
+
+ // Create a ShopItems instance, that is aware of duplicate entries.
+ mShopItems = new ShopItems(true);
+
+ mShopItemList = new ShopListBox(this, mShopItems, mShopItems);
+ mShopItemList->setProtectItems(true);
+ mScrollArea = new ScrollArea(mShopItemList,
+ getOptionBool("showbackground"), "sell_background.xml");
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mSlider = new Slider(1.0);
+
+ mQuantityLabel = new Label(this, strprintf(
+ "%d / %d", mAmountItems, mMaxItems));
+ mQuantityLabel->setAlignment(gcn::Graphics::CENTER);
+ // TRANSLATORS: sell dialog label
+ mMoneyLabel = new Label(this, strprintf(_("Price: %s / Total: %s"),
+ "", ""));
+
+ // TRANSLATORS: sell dialog button
+ mIncreaseButton = new Button(this, _("+"), "inc", this);
+ // TRANSLATORS: sell dialog button
+ mDecreaseButton = new Button(this, _("-"), "dec", this);
+ // TRANSLATORS: sell dialog button
+ mSellButton = new Button(this, _("Sell"), "presell", this);
+ // TRANSLATORS: sell dialog button
+ mQuitButton = new Button(this, _("Quit"), "quit", this);
+ // TRANSLATORS: sell dialog button
+ mAddMaxButton = new Button(this, _("Max"), "max", this);
+
+ mDecreaseButton->adjustSize();
+ mDecreaseButton->setWidth(mIncreaseButton->getWidth());
+
+ mIncreaseButton->setEnabled(false);
+ mDecreaseButton->setEnabled(false);
+ mSellButton->setEnabled(false);
+ mSlider->setEnabled(false);
+
+ mShopItemList->setDistributeMousePressed(false);
+ mShopItemList->setPriceCheck(false);
+ mShopItemList->addSelectionListener(this);
+ mShopItemList->setActionEventId("sell");
+ mShopItemList->addActionListener(this);
+
+ mSlider->setActionEventId("slider");
+ mSlider->addActionListener(this);
+
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+
+ placer(0, 0, mScrollArea, 8, 5).setPadding(3);
+ placer(0, 5, mDecreaseButton);
+ placer(1, 5, mSlider, 3);
+ placer(4, 5, mIncreaseButton);
+ placer(5, 5, mQuantityLabel, 2);
+ placer(7, 5, mAddMaxButton);
+ placer(0, 6, mMoneyLabel, 8);
+ placer(6, 7, mSellButton);
+ placer(7, 7, mQuitButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ center();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(true);
+ enableVisibleSound(true);
+}
+
+SellDialog::~SellDialog()
+{
+ delete mShopItems;
+ mShopItems = nullptr;
+ instances.remove(this);
+}
+
+void SellDialog::reset()
+{
+ mShopItems->clear();
+ mSlider->setValue(0);
+ mShopItemList->setSelected(-1);
+ updateButtonsAndLabels();
+}
+
+void SellDialog::addItem(const Item *const item, const int price)
+{
+ if (!item)
+ return;
+
+ mShopItems->addItem2(item->getInvIndex(), item->getId(),
+ item->getColor(), item->getQuantity(), price);
+
+ mShopItemList->adjustSize();
+}
+
+void SellDialog::addItem(const int id, const unsigned char color,
+ const int amount, const int price)
+{
+ mShopItems->addItem(id, color, amount, price);
+ mShopItemList->adjustSize();
+}
+
+
+void SellDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+
+ if (eventId == "quit")
+ {
+ close();
+ return;
+ }
+
+ const int selectedItem = mShopItemList->getSelected();
+
+ // The following actions require a valid item selection
+ if (selectedItem == -1
+ || selectedItem >= mShopItems->getNumberOfElements())
+ {
+ return;
+ }
+
+ if (eventId == "slider")
+ {
+ mAmountItems = static_cast<int>(mSlider->getValue());
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "inc" && mAmountItems < mMaxItems)
+ {
+ mAmountItems++;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "dec" && mAmountItems > 1)
+ {
+ mAmountItems--;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if (eventId == "max")
+ {
+ mAmountItems = mMaxItems;
+ mSlider->setValue(mAmountItems);
+ updateButtonsAndLabels();
+ }
+ else if ((eventId == "presell" || eventId == "sell" || eventId == "yes")
+ && mAmountItems > 0 && mAmountItems <= mMaxItems)
+ {
+ if (mNpcId != -1)
+ {
+ ShopItem *const item = mShopItems->at(selectedItem);
+ if (PlayerInfo::isItemProtected(item->getId()))
+ return;
+ if (eventId == "presell")
+ {
+ const ItemInfo &info = ItemDB::get(item->getId());
+ if (info.isProtected())
+ {
+ ConfirmDialog *const dialog = new ConfirmDialog(
+ // TRANSLATORS: sell confirmation header
+ _("sell item"),
+ // TRANSLATORS: sell confirmation message
+ strprintf(_("Do you really want to sell %s?"),
+ info.getName().c_str()), SOUND_REQUEST, false, true);
+ dialog->addActionListener(this);
+ return;
+ }
+ }
+ // Attempt sell
+ mPlayerMoney +=
+ mAmountItems * mShopItems->at(selectedItem)->getPrice();
+ mMaxItems -= mAmountItems;
+ while (mAmountItems > 0)
+ {
+#ifdef MANASERV_SUPPORT
+ // This order is important, item->getCurrentInvIndex() would
+ // return the inventory index of the next Duplicate otherwise.
+ int itemIndex = item->getCurrentInvIndex();
+ const int sellCount = item->sellCurrentDuplicate(mAmountItems);
+ // For Manaserv, the Item id is to be given as index.
+ if ((Net::getNetworkType() == ServerInfo::MANASERV))
+ itemIndex = item->getId();
+#else
+ // This order is important, item->getCurrentInvIndex() would
+ // return the inventory index of the next Duplicate otherwise.
+ const int itemIndex = item->getCurrentInvIndex();
+ const int sellCount = item->sellCurrentDuplicate(mAmountItems);
+#endif
+ Net::getNpcHandler()->sellItem(mNpcId, itemIndex, sellCount);
+ mAmountItems -= sellCount;
+ }
+
+ mPlayerMoney +=
+ mAmountItems * mShopItems->at(selectedItem)->getPrice();
+ mAmountItems = 1;
+ mSlider->setValue(0);
+
+ if (mMaxItems)
+ {
+ updateButtonsAndLabels();
+ }
+ else
+ {
+ // 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 *const item = mShopItems->at(selectedItem);
+ Net::getBuySellHandler()->sendSellRequest(mNick,
+ item, mAmountItems);
+
+ if (tradeWindow)
+ tradeWindow->addAutoItem(mNick, item, mAmountItems);
+ }
+ }
+}
+
+void SellDialog::valueChanged(const gcn::SelectionEvent &event A_UNUSED)
+{
+ // Reset amount of items and update labels
+ mAmountItems = 1;
+ mSlider->setValue(0);
+
+ updateButtonsAndLabels();
+ mSlider->setScale(1, mMaxItems);
+}
+
+void SellDialog::setMoney(const int amount)
+{
+ mPlayerMoney = amount;
+ mShopItemList->setPlayersMoney(amount);
+}
+
+void SellDialog::updateButtonsAndLabels()
+{
+ const int selectedItem = mShopItemList->getSelected();
+ int income = 0;
+ ShopItem *item = nullptr;
+
+ if (selectedItem > -1 && mShopItems->at(selectedItem))
+ {
+ item = mShopItems->at(selectedItem);
+ if (item)
+ {
+ mMaxItems = item->getQuantity();
+ if (mAmountItems > mMaxItems)
+ mAmountItems = mMaxItems;
+ income = mAmountItems * mShopItems->at(selectedItem)->getPrice();
+ }
+ else
+ {
+ mMaxItems = 0;
+ mAmountItems = 0;
+ }
+ }
+ 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));
+ // TRANSLATORS: sell dialog label
+ mMoneyLabel->setCaption(strprintf(_("Price: %s / Total: %s"),
+ Units::formatCurrency(income).c_str(),
+ Units::formatCurrency(mPlayerMoney + income).c_str()));
+ if (item)
+ item->update();
+}
+
+void SellDialog::setVisible(bool visible)
+{
+ Window::setVisible(visible);
+
+ if (visible)
+ {
+ if (mShopItemList)
+ mShopItemList->requestFocus();
+ }
+ else
+ {
+ scheduleDelete();
+ }
+}
+
+void SellDialog::closeAll()
+{
+ FOR_EACH (DialogList::const_iterator, it, instances)
+ (*it)->close();
+}
diff --git a/src/gui/windows/selldialog.h b/src/gui/windows/selldialog.h
new file mode 100644
index 000000000..532244845
--- /dev/null
+++ b/src/gui/windows/selldialog.h
@@ -0,0 +1,147 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_SELLDIALOG_H
+#define GUI_SELLDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+class Button;
+class Item;
+class Label;
+class ScrollArea;
+class ShopItems;
+class ShopListBox;
+class Slider;
+
+/**
+ * The sell dialog.
+ *
+ * \ingroup Interface
+ */
+class SellDialog final : public Window,
+ private gcn::ActionListener,
+ private gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ explicit SellDialog(const int npcId);
+
+ /**
+ * Constructor.
+ */
+ explicit SellDialog(std::string nick);
+
+ A_DELETE_COPY(SellDialog)
+
+ /**
+ * Destructor
+ */
+ ~SellDialog();
+
+ void init();
+
+ /**
+ * Resets the dialog, clearing inventory.
+ */
+ void reset();
+
+ /**
+ * Adds an item to the inventory.
+ */
+ void addItem(const Item *const item, const int price);
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Updates labels according to selected item.
+ *
+ * @see SelectionListener::selectionChanged
+ */
+ void valueChanged(const gcn::SelectionEvent &event) override;
+
+ /**
+ * Gives Player's Money amount
+ */
+ void setMoney(const int amount);
+
+ /**
+ * Sets the visibility of this window.
+ */
+ void setVisible(bool visible) override;
+
+ void addItem(const int id, const unsigned char color,
+ const int amount, const int price);
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive() A_WARN_UNUSED
+ { 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;
+
+ Button *mSellButton;
+ Button *mQuitButton;
+ Button *mAddMaxButton;
+ Button *mIncreaseButton;
+ Button *mDecreaseButton;
+ ShopListBox *mShopItemList;
+ ScrollArea *mScrollArea;
+ Label *mMoneyLabel;
+ Label *mQuantityLabel;
+ Slider *mSlider;
+
+ ShopItems *mShopItems;
+ int mPlayerMoney;
+
+ int mMaxItems;
+ int mAmountItems;
+
+ std::string mNick;
+};
+
+#endif // GUI_SELLDIALOG_H
diff --git a/src/gui/windows/serverdialog.cpp b/src/gui/windows/serverdialog.cpp
new file mode 100644
index 000000000..96eb20562
--- /dev/null
+++ b/src/gui/windows/serverdialog.cpp
@@ -0,0 +1,867 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/serverdialog.h"
+
+#include "chatlogger.h"
+#include "client.h"
+#include "configuration.h"
+#include "main.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/gui.h"
+#include "gui/sdlfont.h"
+
+#include "gui/windows/editserverdialog.h"
+#include "gui/windows/logindialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "utils/gettext.h"
+#include "utils/langs.h"
+
+#include <guichan/font.hpp>
+
+#include <string>
+
+#include "debug.h"
+
+static const int MAX_SERVERLIST = 15;
+
+static std::string serverTypeToString(const ServerInfo::Type type)
+{
+ switch (type)
+ {
+ case ServerInfo::TMWATHENA:
+ return "TmwAthena";
+ case ServerInfo::EVOL:
+ return "Evol";
+#ifdef EATHENA_SUPPORT
+ case ServerInfo::EATHENA:
+ return "eAthena";
+#endif
+#ifdef MANASERV_SUPPORT
+ case ServerInfo::MANASERV:
+ return "ManaServ";
+#else
+ case ServerInfo::MANASERV:
+#endif
+#ifndef EATHENA_SUPPORT
+ case ServerInfo::EATHENA:
+#endif
+ default:
+ case ServerInfo::UNKNOWN:
+ return "";
+ }
+}
+
+static uint16_t defaultPortForServerType(const ServerInfo::Type type)
+{
+ switch (type)
+ {
+ default:
+ case ServerInfo::EATHENA:
+#ifdef EATHENA_SUPPORT
+ return 6900;
+#else
+ return 6901;
+#endif
+ case ServerInfo::UNKNOWN:
+ case ServerInfo::TMWATHENA:
+ case ServerInfo::EVOL:
+#ifdef MANASERV_SUPPORT
+ return 6901;
+ case ServerInfo::MANASERV:
+ return 9601;
+#else
+ case ServerInfo::MANASERV:
+ return 6901;
+#endif
+ }
+}
+
+ServersListModel::ServersListModel(ServerInfos *const servers,
+ ServerDialog *const 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.append(server.hostname);
+ return myServer;
+}
+
+void ServersListModel::setVersionString(const int index,
+ const std::string &version)
+{
+ if (index < 0 || index >= static_cast<int>(mVersionStrings.size()))
+ return;
+
+ if (version.empty())
+ {
+ mVersionStrings[index] = VersionString(0, "");
+ }
+ else
+ {
+ mVersionStrings[index] = VersionString(
+ gui->getFont()->getWidth(version), version);
+ }
+}
+
+class ServersListBox final : public ListBox
+{
+public:
+ ServersListBox(const Widget2 *const widget,
+ ServersListModel *const model) :
+ ListBox(widget, model, "serverslistbox.xml"),
+ mNotSupportedColor(getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED)),
+ mNotSupportedColor2(getThemeColor(
+ Theme::SERVER_VERSION_NOT_SUPPORTED_OUTLINE))
+ {
+ mHighlightColor = getThemeColor(Theme::HIGHLIGHT);
+ }
+
+ void draw(gcn::Graphics *graphics) override
+ {
+ if (!mListModel)
+ return;
+
+ ServersListModel *const model = static_cast<ServersListModel *const>(
+ mListModel);
+ Graphics *const g = static_cast<Graphics*>(graphics);
+
+ updateAlpha();
+
+ mHighlightColor.a = static_cast<int>(mAlpha * 255.0F);
+ g->setColor(mHighlightColor);
+
+ const int height = getRowHeight();
+ mNotSupportedColor.a = static_cast<int>(mAlpha * 255.0F);
+
+ // Draw filled rectangle around the selected list element
+ if (mSelected >= 0)
+ {
+ graphics->fillRectangle(gcn::Rectangle(mPadding,
+ height * mSelected + mPadding, getWidth() - 2 * mPadding,
+ height));
+ }
+
+ gcn::Font *const font1 = boldFont;
+ gcn::Font *const font2 = getFont();
+ const int fontHeight = font1->getHeight();
+ const int pad1 = fontHeight + mPadding;
+ const int pad2 = height / 4 + mPadding;
+ const int width = getWidth();
+ // Draw the list elements
+ for (int i = 0, y = 0; i < model->getNumberOfElements();
+ ++i, y += height)
+ {
+ const ServerInfo &info = model->getServer(i);
+
+ if (mSelected == i)
+ {
+ g->setColorAll(mForegroundSelectedColor,
+ mForegroundSelectedColor2);
+ }
+ else
+ {
+ g->setColorAll(mForegroundColor, mForegroundColor2);
+ }
+
+ int top;
+ int x = mPadding;
+
+ if (!info.name.empty())
+ {
+ x += font1->getWidth(info.name) + 15;
+ font1->drawString(graphics, info.name, mPadding, y + mPadding);
+ top = y + pad1;
+ }
+ else
+ {
+ top = y + pad2;
+ }
+
+ if (!info.description.empty())
+ font2->drawString(graphics, info.description, x, y + mPadding);
+ font2->drawString(graphics, model->getElementAt(i), mPadding, top);
+
+ if (info.version.first > 0)
+ {
+ g->setColorAll(mNotSupportedColor, mNotSupportedColor2);
+ font2->drawString(graphics, info.version.second,
+ width - info.version.first - mPadding, top);
+ }
+ }
+ }
+
+ unsigned int getRowHeight() const override
+ {
+ return 2 * getFont()->getHeight() + 5;
+ }
+private:
+ gcn::Color mNotSupportedColor;
+ gcn::Color mNotSupportedColor2;
+};
+
+
+ServerDialog::ServerDialog(ServerInfo *const serverInfo,
+ const std::string &dir) :
+ // TRANSLATORS: servers dialog name
+ Window(_("Choose Your Server"), false, nullptr, "server.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ gcn::SelectionListener(),
+ mMutex(),
+ mDescription(new Label(this, std::string())),
+ // TRANSLATORS: servers dialog button
+ mQuitButton(new Button(this, _("Quit"), "quit", this)),
+ // TRANSLATORS: servers dialog button
+ mConnectButton(new Button(this, _("Connect"), "connect", this)),
+ // TRANSLATORS: servers dialog button
+ mAddEntryButton(new Button(this, _("Add"), "addEntry", this)),
+ // TRANSLATORS: servers dialog button
+ mEditEntryButton(new Button(this, _("Edit"), "editEntry", this)),
+ // TRANSLATORS: servers dialog button
+ mDeleteButton(new Button(this, _("Delete"), "remove", this)),
+ // TRANSLATORS: servers dialog button
+ mLoadButton(new Button(this, _("Load"), "load", this)),
+ mServers(ServerInfos()),
+ mServersListModel(new ServersListModel(&mServers, this)),
+ mServersList(new ServersListBox(this, mServersListModel)),
+ mDir(dir),
+ mDownloadStatus(DOWNLOADING_UNKNOWN),
+ mDownload(nullptr),
+ mDownloadProgress(-1.0F),
+ mServerInfo(serverInfo),
+ mPersistentIPCheckBox(nullptr)
+{
+ if (isSafeMode)
+ {
+ // TRANSLATORS: servers dialog name
+ setCaption(_("Choose Your Server *** SAFE MODE ***"));
+ }
+
+ setWindowName("ServerDialog");
+
+ setCloseButton(true);
+
+ mPersistentIPCheckBox = new CheckBox(this,
+ // TRANSLATORS: servers dialog checkbox
+ _("Use same ip for game sub servers"),
+ config.getBoolValue("usePersistentIP"),
+ this, "persitent ip");
+
+ loadCustomServers();
+
+ mServersList->addMouseListener(this);
+
+ ScrollArea *const usedScroll = new ScrollArea(mServersList,
+ getOptionBool("showbackground"), "server_background.xml");
+ usedScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mServersList->addSelectionListener(this);
+ usedScroll->setVerticalScrollAmount(0);
+
+ place(0, 0, usedScroll, 7, 5).setPadding(3);
+ place(0, 5, mDescription, 7);
+ place(0, 6, mPersistentIPCheckBox, 7);
+ place(0, 7, mAddEntryButton);
+ place(1, 7, mEditEntryButton);
+ place(2, 7, mLoadButton);
+ place(3, 7, mDeleteButton);
+ place(5, 7, mQuitButton);
+ place(6, 7, mConnectButton);
+
+ // Make sure the list has enough height
+ getLayout().setRowHeight(0, 80);
+
+ // Do this manually instead of calling reflowLayout so we can enforce a
+ // minimum width.
+ int width = 500;
+ int height = 350;
+
+ getLayout().reflow(width, height);
+ setContentSize(width, height);
+
+ setMinWidth(310);
+ setMinHeight(220);
+ setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER);
+
+ setResizable(true);
+ addKeyListener(this);
+
+ loadWindowState();
+
+ setVisible(true);
+
+ mConnectButton->requestFocus();
+
+ loadServers(true);
+
+ mServersList->setSelected(0); // Do this after for the Delete button
+
+ if (needUpdateServers())
+ downloadServerList();
+}
+
+ServerDialog::~ServerDialog()
+{
+ if (mDownload)
+ {
+ mDownload->cancel();
+ delete mDownload;
+ mDownload = nullptr;
+ }
+ delete mServersListModel;
+ mServersListModel = nullptr;
+}
+
+void ServerDialog::connectToSelectedServer()
+{
+ if (client->getState() == STATE_CONNECT_SERVER)
+ return;
+
+ const int index = mServersList->getSelected();
+ if (index < 0)
+ return;
+
+ if (mDownload)
+ mDownload->cancel();
+
+ mQuitButton->setEnabled(false);
+ mConnectButton->setEnabled(false);
+ mLoadButton->setEnabled(false);
+
+ ServerInfo server = mServers.at(index);
+ mServerInfo->hostname = server.hostname;
+ mServerInfo->althostname = server.althostname;
+ mServerInfo->port = server.port;
+ mServerInfo->type = server.type;
+ mServerInfo->name = server.name;
+ mServerInfo->description = server.description;
+ mServerInfo->registerUrl = server.registerUrl;
+ mServerInfo->onlineListUrl = server.onlineListUrl;
+ mServerInfo->supportUrl = server.supportUrl;
+ mServerInfo->save = true;
+
+ if (chatLogger)
+ chatLogger->setServerName(mServerInfo->hostname);
+
+ saveCustomServers(*mServerInfo);
+
+ if (!LoginDialog::savedPasswordKey.empty())
+ {
+ if (mServerInfo->hostname != LoginDialog::savedPasswordKey)
+ LoginDialog::savedPassword.clear();
+ }
+
+ config.setValue("usePersistentIP",
+ mPersistentIPCheckBox->isSelected());
+ client->setState(STATE_CONNECT_SERVER);
+}
+
+void ServerDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "connect")
+ {
+ connectToSelectedServer();
+ }
+ else if (eventId == "quit")
+ {
+ close();
+ }
+ else if (eventId == "load")
+ {
+ downloadServerList();
+ }
+ else if (eventId == "addEntry")
+ {
+ new EditServerDialog(this, ServerInfo(), -1);
+ }
+ else if (eventId == "editEntry")
+ {
+ const int index = mServersList->getSelected();
+ if (index >= 0)
+ new EditServerDialog(this, mServers.at(index), index);
+ }
+ else if (eventId == "remove")
+ {
+ const int index = mServersList->getSelected();
+ if (index >= 0)
+ {
+ mServersList->setSelected(0);
+ mServers.erase(mServers.begin() + index);
+ saveCustomServers();
+ }
+ }
+}
+
+void ServerDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ switch (static_cast<KeyEvent*>(&keyEvent)->getActionId())
+ {
+ case Input::KEY_GUI_CANCEL:
+ keyEvent.consume();
+ client->setState(STATE_EXIT);
+ return;
+
+ case Input::KEY_GUI_SELECT:
+ case Input::KEY_GUI_SELECT2:
+ keyEvent.consume();
+ action(gcn::ActionEvent(nullptr,
+ mConnectButton->getActionEventId()));
+ return;
+
+ case Input::KEY_GUI_INSERT:
+ new EditServerDialog(this, ServerInfo(), -1);
+ return;
+
+ case Input::KEY_GUI_DELETE:
+ {
+ const int index = mServersList->getSelected();
+ if (index >= 0)
+ {
+ mServersList->setSelected(0);
+ mServers.erase(mServers.begin() + index);
+ saveCustomServers();
+ }
+ return;
+ }
+
+ case Input::KEY_GUI_BACKSPACE:
+ {
+ const int index = mServersList->getSelected();
+ if (index >= 0)
+ new EditServerDialog(this, mServers.at(index), index);
+ return;
+ }
+
+ default:
+ break;
+ }
+ if (!keyEvent.isConsumed())
+ mServersList->keyPressed(keyEvent);
+}
+
+void ServerDialog::valueChanged(const gcn::SelectionEvent &)
+{
+ const int index = mServersList->getSelected();
+ if (index == -1)
+ {
+ mDeleteButton->setEnabled(false);
+ return;
+ }
+ mDeleteButton->setEnabled(true);
+}
+
+void ServerDialog::mouseClicked(gcn::MouseEvent &mouseEvent)
+{
+ if (mouseEvent.getClickCount() == 2 &&
+ mouseEvent.getSource() == mServersList)
+ {
+ action(gcn::ActionEvent(mConnectButton,
+ mConnectButton->getActionEventId()));
+ }
+}
+
+void ServerDialog::logic()
+{
+ BLOCK_START("ServerDialog::logic")
+ {
+ MutexLocker tempLock(&mMutex);
+ if (mDownloadStatus == DOWNLOADING_COMPLETE)
+ {
+ mDownloadStatus = DOWNLOADING_OVER;
+ mDescription->setCaption(std::string());
+ }
+ else if (mDownloadStatus == DOWNLOADING_IN_PROGRESS)
+ {
+ // TRANSLATORS: servers dialog label
+ mDescription->setCaption(strprintf(_("Downloading server list..."
+ "%2.2f%%"), static_cast<double>(mDownloadProgress * 100)));
+ }
+ else if (mDownloadStatus == DOWNLOADING_IDLE)
+ {
+ // TRANSLATORS: servers dialog label
+ mDescription->setCaption(_("Waiting for server..."));
+ }
+ else if (mDownloadStatus == DOWNLOADING_PREPARING)
+ {
+ // TRANSLATORS: servers dialog label
+ mDescription->setCaption(_("Preparing download"));
+ }
+ else if (mDownloadStatus == DOWNLOADING_ERROR)
+ {
+ // TRANSLATORS: servers dialog label
+ mDescription->setCaption(_("Error retreiving server list!"));
+ }
+ }
+
+ Window::logic();
+ BLOCK_END("ServerDialog::logic")
+}
+
+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 manaplus.org when neither branding
+ // nor config set it
+ if (listFile.empty())
+ listFile = "http://manaplus.org/serverlist.xml";
+
+ if (mDownload)
+ {
+ mDownload->cancel();
+ delete mDownload;
+ mDownload = nullptr;
+ }
+
+ mDownload = new Net::Download(this, listFile, &downloadUpdate);
+ mDownload->setFile(std::string(mDir).append("/").append(
+ branding.getStringValue("onlineServerFile")));
+ mDownload->start();
+
+ config.setValue("serverslistupdate", getDateString());
+}
+
+void ServerDialog::loadServers(const bool addNew)
+{
+ XML::Document doc(std::string(mDir).append("/").append(
+ branding.getStringValue("onlineServerFile")), false);
+ const XmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlNameEqual(rootNode, "serverlist"))
+ {
+ logger->log1("Error loading server list!");
+ return;
+ }
+
+ const int ver = XML::getProperty(rootNode, "version", 0);
+ if (ver != 1)
+ {
+ logger->log("Error: unsupported online server list version: %d",
+ ver);
+ return;
+ }
+
+ const std::string lang = getLangShort();
+ const std::string description2("description_" + lang);
+
+ for_each_xml_child_node(serverNode, rootNode)
+ {
+ if (!xmlNameEqual(serverNode, "server"))
+ continue;
+
+ const std::string type = XML::getProperty(
+ serverNode, "type", "unknown");
+ ServerInfo server;
+ 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());
+
+ const bool meetsMinimumVersion = (compareStrI(version, SMALL_VERSION)
+ <= 0);
+
+ // For display in the list
+ if (meetsMinimumVersion)
+ version.clear();
+ else if (version.empty())
+ {
+ // TRANSLATORS: servers dialog label
+ version = _("requires a newer version");
+ }
+ else
+ {
+ // TRANSLATORS: servers dialog label
+ version = strprintf(_("requires v%s"), version.c_str());
+ }
+
+ const gcn::Font *const font = gui->getFont();
+
+ for_each_xml_child_node(subNode, serverNode)
+ {
+ if (xmlNameEqual(subNode, "connection"))
+ {
+ server.hostname = XML::getProperty(subNode, "hostname", "");
+ server.althostname = XML::getProperty(
+ subNode, "althostname", "");
+ server.port = static_cast<uint16_t>(
+ 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 ((xmlNameEqual(subNode, "description")
+ && server.description.empty()) || (!lang.empty()
+ && xmlNameEqual(subNode, description2.c_str())))
+ {
+ server.description = reinterpret_cast<const char*>(
+ subNode->xmlChildrenNode->content);
+ }
+ else if (xmlNameEqual(subNode, "registerurl"))
+ {
+ server.registerUrl = reinterpret_cast<const char*>(
+ subNode->xmlChildrenNode->content);
+ }
+ else if (xmlNameEqual(subNode, "onlineListUrl"))
+ {
+ server.onlineListUrl = reinterpret_cast<const char*>(
+ subNode->xmlChildrenNode->content);
+ }
+ else if (xmlNameEqual(subNode, "support"))
+ {
+ server.supportUrl = reinterpret_cast<const char*>(
+ subNode->xmlChildrenNode->content);
+ }
+ }
+
+ server.version.first = font->getWidth(version);
+ server.version.second = version;
+
+ MutexLocker tempLock(&mMutex);
+ // Add the server to the local list if it's not already present
+ bool found = false;
+ for (unsigned int i = 0, sz = static_cast<unsigned int>(
+ mServers.size()); i < sz; i++)
+ {
+ if (mServers[i] == server)
+ {
+ // Use the name listed in the server list
+ mServers[i].name = server.name;
+ mServers[i].version = server.version;
+ mServers[i].description = server.description;
+ mServers[i].registerUrl = server.registerUrl;
+ mServers[i].onlineListUrl = server.onlineListUrl;
+ mServers[i].supportUrl = server.supportUrl;
+ mServers[i].althostname = server.althostname;
+ mServersListModel->setVersionString(i, version);
+ found = true;
+ break;
+ }
+ }
+ if (!found && addNew)
+ mServers.push_back(server);
+ }
+ if (mServersList->getSelected() < 0)
+ mServersList->setSelected(0);
+}
+
+void ServerDialog::loadCustomServers()
+{
+ for (int i = 0; i < MAX_SERVERLIST; ++i)
+ {
+ const std::string index = toString(i);
+ const std::string nameKey("MostUsedServerDescName" + index);
+ const std::string descKey("MostUsedServerDescription" + index);
+ const std::string hostKey("MostUsedServerName" + index);
+ const std::string typeKey("MostUsedServerType" + index);
+ const std::string portKey("MostUsedServerPort" + index);
+ const std::string onlineListUrlKey
+ ("MostUsedServerOnlineList" + index);
+
+ ServerInfo server;
+ server.name = config.getValue(nameKey, "");
+ server.description = config.getValue(descKey, "");
+ server.onlineListUrl = config.getValue(onlineListUrlKey, "");
+ server.hostname = config.getValue(hostKey, "");
+ server.type = ServerInfo::parseType(config.getValue(typeKey, ""));
+
+ const int defaultPort = defaultPortForServerType(server.type);
+ server.port = static_cast<uint16_t>(
+ config.getValue(portKey, defaultPort));
+
+ // skip invalid server
+ if (!server.isValid())
+ continue;
+
+ server.save = true;
+ mServers.push_back(server);
+ }
+}
+
+void ServerDialog::saveCustomServers(const ServerInfo &currentServer,
+ const int index)
+{
+ // Make sure the current server is mentioned first
+ if (currentServer.isValid())
+ {
+ if (index >= 0 && static_cast<unsigned>(index) < mServers.size())
+ {
+ mServers[index] = currentServer;
+ }
+ else
+ {
+ FOR_EACH (ServerInfos::iterator, i, mServers)
+ {
+ if (*i == currentServer)
+ {
+ mServers.erase(i);
+ break;
+ }
+ }
+ mServers.insert(mServers.begin(), currentServer);
+ }
+ }
+
+ int savedServerCount = 0;
+
+ for (unsigned i = 0, sz = static_cast<unsigned>(mServers.size());
+ i < sz && 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 num = toString(savedServerCount);
+ const std::string nameKey("MostUsedServerDescName" + num);
+ const std::string descKey("MostUsedServerDescription" + num);
+ const std::string hostKey("MostUsedServerName" + num);
+ const std::string typeKey("MostUsedServerType" + num);
+ const std::string portKey("MostUsedServerPort" + num);
+ const std::string onlineListUrlKey
+ ("MostUsedServerOnlineList" + num);
+
+ config.setValue(nameKey, toString(server.name));
+ config.setValue(descKey, toString(server.description));
+ config.setValue(onlineListUrlKey, toString(server.onlineListUrl));
+ config.setValue(hostKey, 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 (!ptr || status == DOWNLOAD_STATUS_CANCELLED)
+ return -1;
+
+ ServerDialog *const 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)
+ progress = 0.0f;
+ else if (progress > 1.0F)
+ progress = 1.0F;
+
+ MutexLocker lock1(&sd->mMutex);
+ sd->mDownloadStatus = DOWNLOADING_IN_PROGRESS;
+ sd->mDownloadProgress = progress;
+ }
+
+ if (finished)
+ {
+ sd->loadServers();
+ MutexLocker lock1(&sd->mMutex);
+ sd->mDownloadStatus = DOWNLOADING_COMPLETE;
+ }
+
+ return 0;
+}
+
+void ServerDialog::updateServer(const ServerInfo &server, const int index)
+{
+ saveCustomServers(server, index);
+}
+
+bool ServerDialog::needUpdateServers() const
+{
+ if (mServers.empty() || config.getStringValue("serverslistupdate")
+ != getDateString())
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void ServerDialog::close()
+{
+ if (mDownload)
+ mDownload->cancel();
+ client->setState(STATE_FORCE_QUIT);
+ Window::close();
+}
diff --git a/src/gui/windows/serverdialog.h b/src/gui/windows/serverdialog.h
new file mode 100644
index 000000000..ea6954e15
--- /dev/null
+++ b/src/gui/windows/serverdialog.h
@@ -0,0 +1,194 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_SERVERDIALOG_H
+#define GUI_SERVERDIALOG_H
+
+#include "gui/widgets/window.h"
+#include "gui/widgets/checkbox.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/selectionlistener.hpp>
+
+#include <string>
+#include <vector>
+
+class Button;
+class Label;
+class ListBox;
+class ServerDialog;
+
+/**
+ * Server and Port List Model
+ */
+class ServersListModel final : public gcn::ListModel
+{
+ public:
+ typedef std::pair<int, std::string> VersionString;
+
+ ServersListModel(ServerInfos *const servers,
+ ServerDialog *const parent);
+
+ A_DELETE_COPY(ServersListModel)
+
+ /**
+ * Used to get number of line in the list
+ */
+ int getNumberOfElements() override A_WARN_UNUSED;
+
+ /**
+ * Used to get an element from the list
+ */
+ std::string getElementAt(int elementIndex) override A_WARN_UNUSED;
+
+ /**
+ * Used to get the corresponding Server struct
+ */
+ const ServerInfo &getServer(const int elementIndex) const A_WARN_UNUSED
+ { return mServers->at(elementIndex); }
+
+ void setVersionString(const int index, const std::string &version);
+
+ private:
+ typedef std::vector<VersionString> VersionStrings;
+
+ ServerInfos *mServers;
+ VersionStrings mVersionStrings;
+ ServerDialog *mParent;
+};
+
+
+/**
+ * The server choice dialog.
+ *
+ * \ingroup Interface
+ */
+class ServerDialog final : public Window,
+ public gcn::ActionListener,
+ public gcn::KeyListener,
+ public gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ ServerDialog(ServerInfo *const serverInfo, const std::string &dir);
+
+ A_DELETE_COPY(ServerDialog)
+
+ /**
+ * Destructor
+ */
+ ~ServerDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ /**
+ * Called when the selected value changed in the servers list box.
+ */
+ void valueChanged(const gcn::SelectionEvent &event) override;
+
+ void mouseClicked(gcn::MouseEvent &mouseEvent) override;
+
+ void logic() override;
+
+ void updateServer(const ServerInfo &server, const int index);
+
+ void connectToSelectedServer();
+
+ void close() override;
+
+ protected:
+ friend class ServersListModel;
+
+ MutexLocker lock()
+ { return MutexLocker(&mMutex); }
+
+ private:
+ friend class EditServerDialog;
+
+ /**
+ * Called to load a list of available server from an online xml file.
+ */
+ void downloadServerList();
+
+ void loadServers(const bool addNew = true);
+
+ void loadCustomServers();
+
+ void saveCustomServers(const ServerInfo &currentServer = ServerInfo(),
+ const int index = -1);
+
+ bool needUpdateServers() const;
+
+ static int downloadUpdate(void *ptr, DownloadStatus status,
+ size_t total, size_t remaining);
+
+ Mutex mMutex;
+ Label *mDescription;
+ Button *mQuitButton;
+ Button *mConnectButton;
+ Button *mAddEntryButton;
+ Button *mEditEntryButton;
+ Button *mDeleteButton;
+ Button *mLoadButton;
+
+ ServerInfos mServers;
+ ServersListModel *mServersListModel;
+ ListBox *mServersList;
+
+ 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;
+ float mDownloadProgress;
+ ServerInfo *mServerInfo;
+ CheckBox *mPersistentIPCheckBox;
+};
+
+#endif // GUI_SERVERDIALOG_H
diff --git a/src/gui/windows/setup.cpp b/src/gui/windows/setup.cpp
new file mode 100644
index 000000000..00001424b
--- /dev/null
+++ b/src/gui/windows/setup.cpp
@@ -0,0 +1,255 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/setup.h"
+
+#include "configuration.h"
+#include "game.h"
+#include "main.h"
+#include "touchmanager.h"
+
+#include "gui/windows/chatwindow.h"
+
+#include "gui/setup_audio.h"
+#include "gui/setup_chat.h"
+#include "gui/setup_colors.h"
+#include "gui/setup_joystick.h"
+#include "gui/setup_other.h"
+#include "gui/setup_theme.h"
+#include "gui/setup_input.h"
+#include "gui/setup_perfomance.h"
+#include "gui/setup_players.h"
+#include "gui/setup_relations.h"
+#include "gui/setup_touch.h"
+#include "gui/setup_video.h"
+#include "gui/setup_visual.h"
+
+#include "gui/widgets/label.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+extern Window *statusWindow;
+Setup *setupWindow;
+
+Setup::Setup() :
+ // TRANSLATORS: setup window name
+ Window(_("Setup"), false, nullptr, "setup.xml"),
+ gcn::ActionListener(),
+ mTabs(),
+ mWindowsToReset(),
+ mButtons(),
+ mResetWindows(nullptr),
+ mPanel(new TabbedArea(this)),
+ mVersion(new Label(this, FULL_VERSION))
+{
+ setCloseButton(true);
+ setResizable(true);
+ setStickyButtonLock(true);
+
+ int width = 620;
+ const int height = 450;
+
+ if (config.getIntValue("screenwidth") >= 730)
+ width += 100;
+
+ setContentSize(width, height);
+ setMinWidth(310);
+ setMinHeight(210);
+
+ static const char *buttonNames[] =
+ {
+ // TRANSLATORS: setup button
+ N_("Apply"),
+ // TRANSLATORS: setup button
+ N_("Cancel"),
+ // TRANSLATORS: setup button
+ N_("Store"),
+ // TRANSLATORS: setup button
+ N_("Reset Windows"),
+ nullptr
+ };
+ int x = width;
+ const int buttonPadding = getOption("buttonPadding", 5);
+ for (const char ** curBtn = buttonNames; *curBtn; ++ curBtn)
+ {
+ Button *const btn = new Button(this, gettext(*curBtn), *curBtn, this);
+ mButtons.push_back(btn);
+ x -= btn->getWidth() + buttonPadding;
+ btn->setPosition(x, height - btn->getHeight() - buttonPadding);
+ add(btn);
+
+ // Store this button, as it needs to be enabled/disabled
+ if (!strcmp(*curBtn, "Reset Windows"))
+ mResetWindows = btn;
+ }
+
+ mPanel->setDimension(gcn::Rectangle(5, 5, width - 10, height - 40));
+ mPanel->enableScrollButtons(true);
+
+ mTabs.push_back(new Setup_Video(this));
+ mTabs.push_back(new Setup_Visual(this));
+ mTabs.push_back(new Setup_Audio(this));
+ mTabs.push_back(new Setup_Perfomance(this));
+ mTabs.push_back(new Setup_Touch(this));
+ mTabs.push_back(new Setup_Input(this));
+ mTabs.push_back(new Setup_Joystick(this));
+ mTabs.push_back(new Setup_Colors(this));
+ mTabs.push_back(new Setup_Chat(this));
+ mTabs.push_back(new Setup_Players(this));
+ mTabs.push_back(new Setup_Relations(this));
+ mTabs.push_back(new Setup_Theme(this));
+ mTabs.push_back(new Setup_Other(this));
+
+ FOR_EACH (std::list<SetupTab*>::const_iterator, i, mTabs)
+ {
+ SetupTab *const tab = *i;
+ mPanel->addTab(tab->getName(), tab);
+ }
+ add(mPanel);
+
+ if (mResetWindows)
+ {
+ mVersion->setPosition(9,
+ height - mVersion->getHeight() - mResetWindows->getHeight() - 9);
+ }
+ else
+ {
+ mVersion->setPosition(9, height - mVersion->getHeight() - 30);
+ }
+ add(mVersion);
+
+ center();
+
+ widgetResized(gcn::Event(nullptr));
+ setInGame(false);
+ enableVisibleSound(true);
+}
+
+Setup::~Setup()
+{
+ delete_all(mTabs);
+ mButtons.clear();
+}
+
+void Setup::action(const gcn::ActionEvent &event)
+{
+ if (Game::instance())
+ Game::instance()->resetAdjustLevel();
+ const std::string &eventId = event.getId();
+
+ if (eventId == "Apply")
+ {
+ setVisible(false);
+ for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::apply));
+ }
+ else if (eventId == "Cancel")
+ {
+ doCancel();
+ }
+ else if (eventId == "Store")
+ {
+ if (chatWindow)
+ chatWindow->saveState();
+ config.write();
+ serverConfig.write();
+ }
+ else if (eventId == "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_EACH (std::list<Window*>::const_iterator, it, mWindowsToReset)
+ {
+ if (*it)
+ (*it)->resetToDefaultSize();
+ }
+ }
+}
+
+void Setup::setInGame(const bool inGame)
+{
+ mResetWindows->setEnabled(inGame);
+}
+
+void Setup::externalUpdate()
+{
+ FOR_EACH (std::list<SetupTab*>::const_iterator, it, mTabs)
+ {
+ if (*it)
+ (*it)->externalUpdated();
+ }
+}
+
+void Setup::registerWindowForReset(Window *const window)
+{
+ mWindowsToReset.push_back(window);
+}
+
+void Setup::doCancel()
+{
+ setVisible(false);
+ for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::cancel));
+}
+
+void Setup::activateTab(const std::string &name)
+{
+ std::string tmp = gettext(name.c_str());
+ mPanel->setSelectedTabByName(tmp);
+}
+
+void Setup::setVisible(bool visible)
+{
+ touchManager.setTempHide(visible);
+ Window::setVisible(visible);
+}
+
+void Setup::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+
+ const gcn::Rectangle area = getChildrenArea();
+ int x = area.width;
+ const int height = area.height;
+ const int width = area.width;
+ const int buttonPadding = getOption("buttonPadding", 5);
+ mPanel->setDimension(gcn::Rectangle(5, 5, width - 10, height - 40));
+ FOR_EACH (std::vector<Button*>::iterator, it, mButtons)
+ {
+ Button *const btn = *it;
+ x -= btn->getWidth() + buttonPadding;
+ btn->setPosition(x, height - btn->getHeight() - buttonPadding);
+ }
+ if (mResetWindows)
+ {
+ mVersion->setPosition(9,
+ height - mVersion->getHeight() - mResetWindows->getHeight() - 9);
+ }
+ else
+ {
+ mVersion->setPosition(9, height - mVersion->getHeight() - 30);
+ }
+}
diff --git a/src/gui/windows/setup.h b/src/gui/windows/setup.h
new file mode 100644
index 000000000..aeb7cc635
--- /dev/null
+++ b/src/gui/windows/setup.h
@@ -0,0 +1,82 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_H
+#define GUI_SETUP_H
+
+#include "gui/widgets/tabbedarea.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <list>
+
+class Label;
+class SetupTab;
+
+/**
+ * The setup dialog. Displays several tabs for configuring different aspects
+ * of the game.
+ *
+ * \ingroup GUI
+ */
+class Setup final : public Window, public gcn::ActionListener
+{
+ public:
+ Setup();
+
+ A_DELETE_COPY(Setup)
+
+ ~Setup();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void setInGame(const bool inGame);
+
+ void externalUpdate();
+
+ void registerWindowForReset(Window *const window);
+
+ void clearWindowsForReset()
+ { mWindowsToReset.clear(); }
+
+ void doCancel();
+
+ void activateTab(const std::string &name);
+
+ void setVisible(bool visible) override;
+
+ void widgetResized(const gcn::Event &event) override;
+
+ private:
+ std::list<SetupTab*> mTabs;
+ std::list<Window*> mWindowsToReset;
+ std::vector<Button*> mButtons;
+ Button *mResetWindows;
+ TabbedArea *mPanel;
+ Label *mVersion;
+};
+
+extern Setup* setupWindow;
+
+#endif // GUI_SETUP_H
diff --git a/src/gui/windows/shopwindow.cpp b/src/gui/windows/shopwindow.cpp
new file mode 100644
index 000000000..a495d5750
--- /dev/null
+++ b/src/gui/windows/shopwindow.cpp
@@ -0,0 +1,866 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/buydialog.h"
+#include "gui/windows/confirmdialog.h"
+#include "gui/windows/itemamountwindow.h"
+#include "gui/windows/shopwindow.h"
+#include "gui/windows/selldialog.h"
+#include "gui/windows/tradewindow.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 "actorspritemanager.h"
+#include "auctionmanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "inventory.h"
+#include "item.h"
+#include "shopitem.h"
+#include "soundconsts.h"
+#include "soundmanager.h"
+
+#include "being/localplayer.h"
+#include "being/playerinfo.h"
+#include "being/playerrelations.h"
+
+#include "net/net.h"
+#include "net/chathandler.h"
+#include "net/tradehandler.h"
+
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+
+#include <sstream>
+
+#include <sys/stat.h>
+
+#include "debug.h"
+
+extern std::string tradePartnerName;
+ShopWindow::DialogList ShopWindow::instances;
+
+ShopWindow::ShopWindow():
+ // TRANSLATORS: shop window name
+ Window(_("Personal Shop"), false, nullptr, "shop.xml"),
+ gcn::ActionListener(),
+ gcn::SelectionListener(),
+ // TRANSLATORS: shop window button
+ mCloseButton(new Button(this, _("Close"), "close", this)),
+ mBuyShopItems(new ShopItems),
+ mSellShopItems(new ShopItems),
+ mBuyShopItemList(new ShopListBox(this, mBuyShopItems, mBuyShopItems)),
+ mSellShopItemList(new ShopListBox(this, mSellShopItems, mSellShopItems)),
+ mBuyScrollArea(new ScrollArea(mBuyShopItemList,
+ getOptionBool("showbuybackground"), "shop_buy_background.xml")),
+ mSellScrollArea(new ScrollArea(mSellShopItemList,
+ getOptionBool("showsellbackground"), "shop_sell_background.xml")),
+ // TRANSLATORS: shop window label
+ mBuyLabel(new Label(this, _("Buy items"))),
+ // TRANSLATORS: shop window label
+ mSellLabel(new Label(this, _("Sell items"))),
+ // TRANSLATORS: shop window label
+ mBuyAddButton(new Button(this, _("Add"), "add buy", this)),
+ // TRANSLATORS: shop window label
+ mBuyDeleteButton(new Button(this, _("Delete"), "delete buy", this)),
+ // TRANSLATORS: shop window label
+ mBuyAnnounceButton(new Button(this, _("Announce"), "announce buy", this)),
+ mBuyAuctionButton(nullptr),
+ // TRANSLATORS: shop window button
+ mSellAddButton(new Button(this, _("Add"), "add sell", this)),
+ // TRANSLATORS: shop window button
+ mSellDeleteButton(new Button(this, _("Delete"), "delete sell", this)),
+ // TRANSLATORS: shop window button
+ mSellAnnounceButton(new Button(this, _("Announce"),
+ "announce sell", this)),
+ mSellAuctionButton(nullptr),
+ // TRANSLATORS: shop window checkbox
+ mAnnounceLinks(new CheckBox(this, _("Show links in announce"), false,
+ this, "link announce")),
+ mSelectedItem(-1),
+ mAnnonceTime(0),
+ mLastRequestTimeList(0),
+ mLastRequestTimeItem(0),
+ mRandCounter(0),
+ mAcceptPlayer(""),
+ mTradeItem(nullptr),
+ mTradeNick(""),
+ mTradeMoney(0)
+{
+ setWindowName("Personal Shop");
+ setResizable(true);
+ setCloseButton(true);
+ setStickyButtonLock(true);
+ setMinWidth(260);
+ setMinHeight(220);
+ if (mainGraphics->mWidth > 600)
+ setDefaultSize(500, 300, ImageRect::CENTER);
+ else
+ setDefaultSize(380, 300, ImageRect::CENTER);
+
+ mAnnounceCounter[BUY] = 0;
+ mAnnounceCounter[SELL] = 0;
+
+ loadList();
+
+ mBuyShopItemList->setPriceCheck(false);
+ mSellShopItemList->setPriceCheck(false);
+
+ mBuyScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mSellScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mBuyShopItemList->addSelectionListener(this);
+ mSellShopItemList->addSelectionListener(this);
+
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+
+ placer(0, 0, mBuyLabel, 8).setPadding(3);
+ placer(8, 0, mSellLabel, 8).setPadding(3);
+ placer(0, 1, mBuyScrollArea, 8, 5).setPadding(3);
+ placer(8, 1, mSellScrollArea, 8, 5).setPadding(3);
+ placer(0, 6, mBuyAddButton);
+ placer(1, 6, mBuyDeleteButton);
+ placer(3, 6, mBuyAnnounceButton);
+ placer(8, 6, mSellAddButton);
+ placer(9, 6, mSellDeleteButton);
+ placer(11, 6, mSellAnnounceButton);
+ placer(0, 7, mAnnounceLinks, 8);
+ placer(15, 7, mCloseButton);
+
+ if (auctionManager && auctionManager->getEnableAuctionBot())
+ {
+ mBuyAuctionButton = new Button(this,
+ // TRANSLATORS: shop window button
+ _("Auction"), "auction buy", this);
+ mSellAuctionButton = new Button(this,
+ // TRANSLATORS: shop window button
+ _("Auction"), "auction sell", this);
+ placer(4, 6, mBuyAuctionButton);
+ placer(12, 6, mSellAuctionButton);
+ }
+ else
+ {
+ mBuyAuctionButton = nullptr;
+ mSellAuctionButton = nullptr;
+ }
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ center();
+ loadWindowState();
+
+ instances.push_back(this);
+ setVisible(false);
+ enableVisibleSound(true);
+
+ updateButtonsAndLabels();
+}
+
+ShopWindow::~ShopWindow()
+{
+ saveList();
+
+ delete mBuyShopItems;
+ mBuyShopItems = nullptr;
+
+ delete mSellShopItems;
+ mSellShopItems = nullptr;
+
+ instances.remove(this);
+}
+
+void ShopWindow::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "close")
+ {
+ close();
+ return;
+ }
+ else if (eventId == "yes")
+ {
+ startTrade();
+ }
+ else if (eventId == "no")
+ {
+ mTradeNick.clear();
+ }
+ else if (eventId == "ignore")
+ {
+ player_relations.ignoreTrade(mTradeNick);
+ mTradeNick.clear();
+ }
+ else if (eventId == "delete buy" && mBuyShopItemList
+ && mBuyShopItemList->getSelected() >= 0)
+ {
+ mBuyShopItems->del(mBuyShopItemList->getSelected());
+ if (isShopEmpty() && player_node)
+ player_node->updateStatus();
+ }
+ else if (eventId == "delete sell" && mSellShopItemList
+ && mSellShopItemList->getSelected() >= 0)
+ {
+ mSellShopItems->del(mSellShopItemList->getSelected());
+ if (isShopEmpty() && player_node)
+ player_node->updateStatus();
+ }
+ else if (eventId == "announce buy" && mBuyShopItems
+ && mBuyShopItems->getNumberOfElements() > 0)
+ {
+ announce(mBuyShopItems, BUY);
+ }
+ else if (eventId == "announce sell" && mSellShopItems
+ && mSellShopItems->getNumberOfElements() > 0)
+ {
+ announce(mSellShopItems, SELL);
+ }
+ else if (eventId == "auction buy" && mBuyShopItems
+ && mBuyShopItems->getNumberOfElements() > 0)
+ {
+ Net::getChatHandler()->privateMessage("AuctionBot", "!pull4144 seek");
+ }
+ else if (eventId == "auction sell" && mSellShopItems
+ && mSellShopItems->getNumberOfElements() > 0)
+ {
+ Net::getChatHandler()->privateMessage("AuctionBot", "!pull4144 offer");
+ }
+
+ if (mSelectedItem < 1)
+ return;
+
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ // +++ need support for colors
+ Item *const item = inv->findItem(mSelectedItem, 0);
+ if (item)
+ {
+ if (eventId == "add buy")
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ShopBuyAdd,
+ this, item, sumAmount(item));
+ }
+ else if (eventId == "add sell")
+ {
+ ItemAmountWindow::showWindow(ItemAmountWindow::ShopSellAdd,
+ this, item, sumAmount(item));
+ }
+ }
+}
+
+void ShopWindow::startTrade()
+{
+ if (!actorSpriteManager || !tradeWindow)
+ return;
+
+ const Being *const 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.clear();
+}
+
+void ShopWindow::valueChanged(const gcn::SelectionEvent &event A_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(const Item *const item, const int amount,
+ const int price)
+{
+ if (!mBuyShopItems || !item)
+ return;
+ const bool emp = isShopEmpty();
+ mBuyShopItems->addItemNoDup(item->getId(),
+ item->getColor(), amount, price);
+ if (emp && player_node)
+ player_node->updateStatus();
+
+ updateButtonsAndLabels();
+}
+
+void ShopWindow::addSellItem(const Item *const item, const int amount,
+ const int price)
+{
+ if (!mBuyShopItems || !item)
+ return;
+ const bool emp = isShopEmpty();
+ mSellShopItems->addItemNoDup(item->getId(),
+ item->getColor(), amount, price);
+ if (emp && player_node)
+ player_node->updateStatus();
+
+ updateButtonsAndLabels();
+}
+
+void ShopWindow::loadList()
+{
+ if (!mBuyShopItems || !mSellShopItems)
+ return;
+
+ std::ifstream shopFile;
+ struct stat statbuf;
+
+ mBuyShopItems->clear();
+ mSellShopItems->clear();
+
+ const 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);
+ if (!shopFile.is_open())
+ {
+ shopFile.close();
+ return;
+ }
+ char line[101];
+ while (shopFile.getline(line, 100))
+ {
+ std::string buf;
+ const 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])
+ {
+ // +++ need impliment colors?
+ if (tokens[1] && tokens[2] && mBuyShopItems)
+ {
+ mBuyShopItems->addItem(
+ tokens[0], 1, tokens[1], tokens[2]);
+ }
+ if (tokens[3] && tokens[4] && mSellShopItems)
+ {
+ mSellShopItems->addItem(
+ tokens[0], 1, tokens[3], tokens[4]);
+ }
+ }
+ }
+ }
+ shopFile.close();
+ }
+}
+
+void ShopWindow::saveList() const
+{
+ if (!mBuyShopItems || !mSellShopItems)
+ return;
+
+ std::ofstream shopFile;
+ const std::string shopListName = client->getServerConfigDirectory()
+ + "/shoplist.txt";
+ 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();
+ FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
+ {
+ ShopItem *const item = *(it);
+ if (item)
+ mapItems[item->getId()] = item;
+ }
+
+ items = mSellShopItems->items();
+ FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
+ {
+ if (!(*it))
+ continue;
+ const ShopItem *const sellItem = *(it);
+ const ShopItem *const 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 ";
+ }
+
+ shopFile << strprintf("%d %d", sellItem->getQuantity(),
+ sellItem->getPrice()) << std::endl;
+ }
+
+ for (std::map<int, ShopItem*>::const_iterator mapIt = mapItems.begin(),
+ mapIt_end = mapItems.end(); mapIt != mapIt_end; ++mapIt)
+ {
+ const ShopItem *const 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 *const list, const int mode)
+{
+ if (!list)
+ return;
+
+ std::string data("\302\202");
+ if (mode == BUY)
+ data.append("Buy ");
+ else
+ data.append("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();
+
+ FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
+ {
+ const ShopItem *const item = *(it);
+ if (item->getQuantity() > 1)
+ {
+ if (mAnnounceLinks->isSelected())
+ {
+ data.append(strprintf("[@@%d|%s@@] (%dGP) %d, ", item->getId(),
+ item->getInfo().getName().c_str(),
+ item->getPrice(), item->getQuantity()));
+ }
+ else
+ {
+ data.append(strprintf("%s (%dGP) %d, ",
+ item->getInfo().getName().c_str(),
+ item->getPrice(), item->getQuantity()));
+ }
+ }
+ else
+ {
+ if (mAnnounceLinks->isSelected())
+ {
+ data.append(strprintf("[@@%d|%s@@] (%dGP), ", item->getId(),
+ item->getInfo().getName().c_str(), item->getPrice()));
+ }
+ else
+ {
+ data.append(strprintf("%s (%dGP), ",
+ item->getInfo().getName().c_str(), item->getPrice()));
+ }
+ }
+ }
+
+ Net::getChatHandler()->talk(data, GENERAL_CHANNEL);
+}
+
+void ShopWindow::giveList(const std::string &nick, const int mode)
+{
+ if (!checkFloodCounter(mLastRequestTimeList))
+ return;
+
+ std::string data("\302\202");
+
+ ShopItems *list;
+ if (mode == BUY)
+ {
+ list = mBuyShopItems;
+ data.append("S1");
+ }
+ else
+ {
+ list = mSellShopItems;
+ data.append("B1");
+ }
+ if (!list)
+ return;
+
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ std::vector<ShopItem*> items = list->items();
+
+ FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
+ {
+ const ShopItem *const item = *(it);
+ if (!item)
+ continue;
+
+ if (mode == SELL)
+ {
+ // +++ need support for colors
+ const Item *const item2 = inv->findItem(item->getId(), 0);
+ if (item2)
+ {
+ int amount = item->getQuantity();
+ if (item2->getQuantity() < amount)
+ amount = item2->getQuantity();
+
+ if (amount)
+ {
+ data.append(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(
+ PlayerInfo::MONEY))
+ {
+ amount = PlayerInfo::getAttribute(PlayerInfo::MONEY)
+ / item->getPrice();
+ }
+
+ if (amount > 0)
+ {
+ data.append(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, const bool random)
+{
+ if (!chatWindow)
+ return;
+
+ if (random)
+ {
+ mRandCounter ++;
+ if (mRandCounter > 200)
+ mRandCounter = 0;
+ data.append(encodeStr(mRandCounter, 2));
+ }
+
+ if (config.getBoolValue("hideShopMessages"))
+ Net::getChatHandler()->privateMessage(nick, data);
+ else if (chatWindow)
+ chatWindow->addWhisper(nick, data, BY_PLAYER);
+}
+
+void ShopWindow::showList(const std::string &nick, std::string data) const
+{
+ BuyDialog *buyDialog = nullptr;
+ SellDialog *sellDialog = nullptr;
+ 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;
+ }
+
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ if (buyDialog)
+ buyDialog->setMoney(PlayerInfo::getAttribute(PlayerInfo::MONEY));
+ if (sellDialog)
+ sellDialog->setMoney(PlayerInfo::getAttribute(PlayerInfo::MONEY));
+
+ for (unsigned f = 0; f < data.length(); f += 9)
+ {
+ if (f + 9 > data.length())
+ break;
+
+ const int id = decodeStr(data.substr(f, 2));
+ const int price = decodeStr(data.substr(f + 2, 4));
+ int amount = decodeStr(data.substr(f + 6, 3));
+ // +++ need impliment colors?
+ if (buyDialog && amount > 0)
+ buyDialog->addItem(id, 1, amount, price);
+ if (sellDialog)
+ {
+ // +++ need support for colors
+ const Item *const item = inv->findItem(id, 0);
+ if (item)
+ {
+ if (item->getQuantity() < amount)
+ amount = item->getQuantity();
+ if (amount > 0)
+ sellDialog->addItem(id, 1, amount, price);
+ else
+ sellDialog->addItem(id, 1, -1, price);
+ }
+ }
+ }
+}
+
+void ShopWindow::processRequest(const std::string &nick, std::string data,
+ const int mode)
+{
+ if (!player_node || !mTradeNick.empty() || PlayerInfo::isTrading()
+ || !actorSpriteManager
+ || !actorSpriteManager->findBeingByName(nick, Being::PLAYER))
+ {
+ return;
+ }
+
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ const size_t 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;
+ // +++ need impliment colors?
+ mTradeItem = new ShopItem(-1, id, 1, amount, price);
+
+ if (mode == BUY)
+ {
+ // +++ need support for colors
+ const Item *const item2 = inv->findItem(mTradeItem->getId(), 0);
+ if (!item2 || item2->getQuantity() < amount
+ || !findShopItem(mTradeItem, SELL))
+ {
+ sendMessage(nick, "error: Can't sell this item ", true);
+ return;
+ }
+ msg = "buy";
+ mTradeMoney = 0;
+ }
+ else
+ {
+ if (!findShopItem(mTradeItem, BUY))
+ {
+ sendMessage(nick, "error: Can't buy this item ", true);
+ return;
+ }
+ msg = "sell";
+ mTradeMoney = mTradeItem->getPrice() * mTradeItem->getQuantity();
+ }
+
+ mTradeNick = nick;
+
+ if (config.getBoolValue("autoShop"))
+ {
+ soundManager.playGuiSound(SOUND_TRADE);
+ startTrade();
+ }
+ else
+ {
+ ConfirmDialog *const confirmDlg = new ConfirmDialog
+ // TRANSLATORS: shop window dialog
+ (_("Request for Trade"), strprintf(_("%s wants to %s %s do you "
+ "accept?"), nick.c_str(), msg.c_str(),
+ mTradeItem->getInfo().getName().c_str()), SOUND_REQUEST, true);
+ confirmDlg->addActionListener(this);
+ }
+}
+
+void ShopWindow::updateTimes()
+{
+ BLOCK_START("ShopWindow::updateTimes")
+ if (mAnnonceTime + (2 * 60) < cur_time
+ || mAnnonceTime > cur_time)
+ {
+ mBuyAnnounceButton->setEnabled(true);
+ mSellAnnounceButton->setEnabled(true);
+ }
+ BLOCK_END("ShopWindow::updateTimes")
+}
+
+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(const ShopItem *const shopItem,
+ const int mode) const
+{
+ if (!shopItem)
+ return false;
+
+ std::vector<ShopItem*> items;
+ if (mode == SELL)
+ {
+ if (!mSellShopItems)
+ return false;
+ items = mSellShopItems->items();
+ }
+ else
+ {
+ if (!mBuyShopItems)
+ return false;
+ items = mBuyShopItems->items();
+ }
+
+ FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
+ {
+ const ShopItem *const item = *(it);
+ if (!item)
+ continue;
+
+ if (item->getId() == shopItem->getId()
+ && item->getPrice() == shopItem->getPrice()
+ && item->getQuantity() >= shopItem->getQuantity())
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+int ShopWindow::sumAmount(const Item *const shopItem)
+{
+ if (!player_node || !shopItem)
+ return 0;
+
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return 0;
+ int sum = 0;
+
+ for (unsigned f = 0; f < inv->getSize(); f ++)
+ {
+ const Item *const item = inv->getItem(f);
+ if (item && item->getId() == shopItem->getId())
+ sum += item->getQuantity();
+ }
+ return sum;
+}
+
+bool ShopWindow::isShopEmpty() const
+{
+ if (!mBuyShopItems || !mSellShopItems)
+ return true;
+ if (mBuyShopItems->empty() && mSellShopItems->empty())
+ return true;
+ return false;
+}
diff --git a/src/gui/windows/shopwindow.h b/src/gui/windows/shopwindow.h
new file mode 100644
index 000000000..6352f6721
--- /dev/null
+++ b/src/gui/windows/shopwindow.h
@@ -0,0 +1,179 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_SHOPWINDOW_H
+#define GUI_SHOPWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+class Button;
+class CheckBox;
+class Item;
+class Label;
+class ScrollArea;
+class ShopItem;
+class ShopItems;
+class ShopListBox;
+
+/**
+ * The buy dialog.
+ *
+ * \ingroup Interface
+ */
+class ShopWindow final : public Window,
+ public gcn::ActionListener,
+ public gcn::SelectionListener
+{
+ public:
+ enum ShopMode
+ {
+ BUY = 0,
+ SELL = 1
+ };
+
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ ShopWindow();
+
+ A_DELETE_COPY(ShopWindow)
+
+ /**
+ * Destructor
+ */
+ ~ShopWindow();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Updates the labels according to the selected item.
+ */
+ void valueChanged(const gcn::SelectionEvent &event) override;
+
+ /**
+ * Updates the state of buttons and labels.
+ */
+ void updateButtonsAndLabels();
+
+ /**
+ * Sets the visibility of this window.
+ */
+ void setVisible(bool visible) override;
+
+ /**
+ * Returns true if any instances exist.
+ */
+ static bool isActive() A_WARN_UNUSED
+ { return !instances.empty(); }
+
+ void setItemSelected(const int id)
+ { mSelectedItem = id; updateButtonsAndLabels(); }
+
+ void addBuyItem(const Item *const item, const int amount,
+ const int price);
+
+ void addSellItem(const Item *const item, const int amount,
+ const int price);
+
+ void loadList();
+
+ void saveList() const;
+
+ void announce(ShopItems *const list, const int mode);
+
+ void giveList(const std::string &nick, const int mode);
+
+ void setAcceptPlayer(const std::string &name)
+ { mAcceptPlayer = name; }
+
+ const std::string &getAcceptPlayer() const A_WARN_UNUSED
+ { return mAcceptPlayer; }
+
+ void sendMessage(const std::string &nick, std::string data,
+ const bool random = false);
+
+ void showList(const std::string &nick, std::string data) const;
+
+ void processRequest(const std::string &nick, std::string data,
+ const int mode);
+
+ bool findShopItem(const ShopItem *const shopItem,
+ const int mode) const A_WARN_UNUSED;
+
+ static int sumAmount(const Item *const shopItem) A_WARN_UNUSED;
+
+ void updateTimes();
+
+ static bool checkFloodCounter(int &counterTime) A_WARN_UNUSED;
+
+ bool isShopEmpty() const A_WARN_UNUSED;
+
+ private:
+ void startTrade();
+
+ typedef std::list<ShopWindow*> DialogList;
+ static DialogList instances;
+
+ Button *mCloseButton;
+
+ ShopItems *mBuyShopItems;
+ ShopItems *mSellShopItems;
+
+ ShopListBox *mBuyShopItemList;
+ ShopListBox *mSellShopItemList;
+ ScrollArea *mBuyScrollArea;
+ ScrollArea *mSellScrollArea;
+ Label *mBuyLabel;
+ Label *mSellLabel;
+ Button *mBuyAddButton;
+ Button *mBuyDeleteButton;
+ Button *mBuyAnnounceButton;
+ Button *mBuyAuctionButton;
+ Button *mSellAddButton;
+ Button *mSellDeleteButton;
+ Button *mSellAnnounceButton;
+ Button *mSellAuctionButton;
+ CheckBox *mAnnounceLinks;
+
+ 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 // GUI_SHOPWINDOW_H
diff --git a/src/gui/windows/shortcutwindow.cpp b/src/gui/windows/shortcutwindow.cpp
new file mode 100644
index 000000000..1ae74efb3
--- /dev/null
+++ b/src/gui/windows/shortcutwindow.cpp
@@ -0,0 +1,242 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/shortcutwindow.h"
+
+#include "gui/windows/setup.h"
+
+#include "gui/widgets/layout.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/shortcutcontainer.h"
+#include "gui/widgets/tab.h"
+
+#include "debug.h"
+
+static const int SCROLL_PADDING = 0;
+
+int ShortcutWindow::mBoxesWidth = 0;
+
+class ShortcutTab final : public Tab
+{
+ public:
+ ShortcutTab(const Widget2 *const widget,
+ std::string name, ShortcutContainer *const content) :
+ Tab(widget),
+ mContent(content)
+ {
+ setCaption(name);
+ }
+
+ A_DELETE_COPY(ShortcutTab)
+
+ ShortcutContainer* mContent;
+};
+
+ShortcutWindow::ShortcutWindow(const std::string &title,
+ ShortcutContainer *const content,
+ const std::string &skinFile,
+ int width, int height) :
+ Window("Window", false, nullptr, skinFile),
+ mItems(content),
+ mScrollArea(new ScrollArea(mItems, false)),
+ mTabs(nullptr),
+ mPages()
+{
+ setWindowName(title);
+ setTitleBarHeight(getPadding() + getTitlePadding());
+
+ setShowTitle(false);
+ setResizable(true);
+ setDefaultVisible(false);
+ setSaveVisible(true);
+
+ mDragOffsetX = 0;
+ mDragOffsetY = 0;
+
+ content->setWidget2(this);
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ 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->setPosition(SCROLL_PADDING, SCROLL_PADDING);
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ place(0, 0, mScrollArea, 5, 5).setPadding(0);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+ layout.setMargin(0);
+
+ loadWindowState();
+ enableVisibleSound(true);
+}
+
+ShortcutWindow::ShortcutWindow(const std::string &title,
+ const std::string &skinFile,
+ const int width, const int height) :
+ Window("Window", false, nullptr, skinFile),
+ mItems(nullptr),
+ mScrollArea(nullptr),
+ mTabs(new TabbedArea(this)),
+ mPages()
+{
+ setWindowName(title);
+ setTitleBarHeight(getPadding() + getTitlePadding());
+ setShowTitle(false);
+ setResizable(true);
+ setDefaultVisible(false);
+ setSaveVisible(true);
+
+ mDragOffsetX = 0;
+ mDragOffsetY = 0;
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ 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();
+ enableVisibleSound(true);
+}
+
+ShortcutWindow::~ShortcutWindow()
+{
+ if (mTabs)
+ mTabs->removeAll();
+ delete mTabs;
+ mTabs = nullptr;
+ delete mItems;
+ mItems = nullptr;
+}
+
+void ShortcutWindow::addTab(const std::string &name,
+ ShortcutContainer *const content)
+{
+ ScrollArea *const scroll = new ScrollArea(content, false);
+ scroll->setPosition(SCROLL_PADDING, SCROLL_PADDING);
+ scroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ content->setWidget2(this);
+ Tab *const tab = new ShortcutTab(this, name, content);
+ mTabs->addTab(tab, scroll);
+ mPages.push_back(content);
+}
+
+int ShortcutWindow::getTabIndex() const
+{
+ if (!mTabs)
+ return 0;
+ return mTabs->getSelectedTabIndex();
+}
+
+void ShortcutWindow::widgetHidden(const gcn::Event &event)
+{
+ if (mItems)
+ mItems->widgetHidden(event);
+ if (mTabs)
+ {
+ ScrollArea *const scroll = static_cast<ScrollArea *const>(
+ mTabs->getCurrentWidget());
+ if (scroll)
+ {
+ ShortcutContainer *const content = static_cast<ShortcutContainer*>(
+ scroll->getContent());
+
+ if (content)
+ content->widgetHidden(event);
+ }
+ }
+}
+
+void ShortcutWindow::mousePressed(gcn::MouseEvent &event)
+{
+ Window::mousePressed(event);
+
+ if (event.isConsumed())
+ return;
+
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ mDragOffsetX = event.getX();
+ mDragOffsetY = event.getY();
+ }
+}
+
+void ShortcutWindow::mouseDragged(gcn::MouseEvent &event)
+{
+ Window::mouseDragged(event);
+
+ if (event.isConsumed())
+ return;
+
+ if (canMove() && isMovable() && mMoved)
+ {
+ int newX = std::max(0, getX() + event.getX() - mDragOffsetX);
+ int newY = std::max(0, getY() + event.getY() - mDragOffsetY);
+ newX = std::min(mainGraphics->mWidth - getWidth(), newX);
+ newY = std::min(mainGraphics->mHeight - getHeight(), newY);
+ setPosition(newX, newY);
+ }
+}
+
+void ShortcutWindow::widgetMoved(const gcn::Event& event)
+{
+ Window::widgetMoved(event);
+ if (mItems)
+ mItems->setRedraw(true);
+ FOR_EACH (std::vector<ShortcutContainer*>::iterator, it, mPages)
+ (*it)->setRedraw(true);
+}
+
+#ifdef USE_PROFILER
+void ShortcutWindow::logicChildren()
+{
+ BLOCK_START("ShortcutWindow::logicChildren")
+ BasicContainer::logicChildren();
+ BLOCK_END("ShortcutWindow::logicChildren")
+}
+#endif
diff --git a/src/gui/windows/shortcutwindow.h b/src/gui/windows/shortcutwindow.h
new file mode 100644
index 000000000..cc2ba97b0
--- /dev/null
+++ b/src/gui/windows/shortcutwindow.h
@@ -0,0 +1,90 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_SHORTCUTWINDOW_H
+#define GUI_SHORTCUTWINDOW_H
+
+#include "gui/widgets/window.h"
+
+class ScrollArea;
+class ShortcutContainer;
+class TabbedArea;
+
+/**
+ * A window around a ShortcutContainer.
+ *
+ * \ingroup Interface
+ */
+class ShortcutWindow final : public Window
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ShortcutWindow(const std::string &title,
+ ShortcutContainer *const content,
+ const std::string &skinFile = "",
+ int width = 0, int height = 0);
+
+ ShortcutWindow(const std::string &title,
+ const std::string &skinFile = "",
+ const int width = 0, const int height = 0);
+
+ A_DELETE_COPY(ShortcutWindow)
+
+ /**
+ * Destructor.
+ */
+ ~ShortcutWindow();
+
+ void addTab(const std::string &name, ShortcutContainer *const content);
+
+ int getTabIndex() const A_WARN_UNUSED;
+
+ void widgetHidden(const gcn::Event &event) override;
+
+ void widgetMoved(const gcn::Event& event) override;
+
+ void mousePressed(gcn::MouseEvent &event) override;
+
+ void mouseDragged(gcn::MouseEvent &event) override;
+
+#ifdef USE_PROFILER
+ void logicChildren();
+#endif
+
+ private:
+ ShortcutWindow();
+ ShortcutContainer *mItems;
+
+ ScrollArea *mScrollArea;
+ TabbedArea *mTabs;
+ std::vector<ShortcutContainer*> mPages;
+
+ static int mBoxesWidth;
+};
+
+extern ShortcutWindow *itemShortcutWindow;
+extern ShortcutWindow *emoteShortcutWindow;
+extern ShortcutWindow *dropShortcutWindow;
+
+#endif // GUI_SHORTCUTWINDOW_H
diff --git a/src/gui/windows/skilldialog.cpp b/src/gui/windows/skilldialog.cpp
new file mode 100644
index 000000000..39cdc5d2f
--- /dev/null
+++ b/src/gui/windows/skilldialog.cpp
@@ -0,0 +1,722 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/skilldialog.h"
+
+#include "configuration.h"
+#include "dragdrop.h"
+#include "effectmanager.h"
+#include "itemshortcut.h"
+
+#include "being/localplayer.h"
+
+#include "gui/textpopup.h"
+#include "gui/viewport.h"
+
+#include "gui/windows/setup.h"
+#include "gui/windows/shortcutwindow.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+#include "gui/widgets/skillmodel.h"
+#include "gui/widgets/tab.h"
+#include "gui/widgets/tabbedarea.h"
+
+#include "net/net.h"
+#include "net/playerhandler.h"
+#include "net/skillhandler.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include "debug.h"
+
+class SkillListBox final : public ListBox
+{
+ public:
+ SkillListBox(const Widget2 *const widget, SkillModel *const model) :
+ ListBox(widget, model, "skilllistbox.xml"),
+ mModel(model),
+ mPopup(new TextPopup),
+ mHighlightColor(getThemeColor(Theme::HIGHLIGHT)),
+ mTextColor(getThemeColor(Theme::TEXT)),
+ mTextColor2(getThemeColor(Theme::TEXT_OUTLINE)),
+ mTextPadding(mSkin ? mSkin->getOption("textPadding", 34) : 34),
+ mSpacing(mSkin ? mSkin->getOption("spacing", 0) : 0),
+ mRowHeight(getFont()->getHeight() * 2 + mSpacing + 2 * mPadding),
+ mSkillClicked(false)
+ {
+ if (mRowHeight < 34)
+ mRowHeight = 34;
+ }
+
+ A_DELETE_COPY(SkillListBox)
+
+ ~SkillListBox()
+ {
+ delete mModel;
+ mModel = nullptr;
+ delete mPopup;
+ mPopup = nullptr;
+ }
+
+ SkillInfo *getSelectedInfo() const
+ {
+ const int selected = getSelected();
+ if (!mListModel || selected < 0
+ || selected > mListModel->getNumberOfElements())
+ {
+ return nullptr;
+ }
+
+ return static_cast<SkillModel*>(mListModel)->getSkillAt(selected);
+ }
+
+ void draw(gcn::Graphics *gcnGraphics) override
+ {
+ if (!mListModel)
+ return;
+
+ SkillModel *const model = static_cast<SkillModel*>(mListModel);
+ updateAlpha();
+ Graphics *const graphics = static_cast<Graphics *const>(
+ gcnGraphics);
+
+ mHighlightColor.a = static_cast<int>(mAlpha * 255.0F);
+ graphics->setColor(mHighlightColor);
+
+ // Draw filled rectangle around the selected list element
+ if (mSelected >= 0)
+ {
+ graphics->fillRectangle(gcn::Rectangle(mPadding, getRowHeight()
+ * mSelected + mPadding, getWidth() - 2 * mPadding,
+ getRowHeight()));
+ }
+
+ // Draw the list elements
+ graphics->setColorAll(mTextColor, mTextColor2);
+ gcn::Font *const font = getFont();
+ const int space = font->getHeight() + mSpacing;
+ const int width2 = getWidth() - mPadding;
+ for (int i = 0, y = 1;
+ i < model->getNumberOfElements();
+ ++i, y += getRowHeight())
+ {
+ SkillInfo *const e = model->getSkillAt(i);
+ if (e)
+ {
+ const SkillData *const data = e->data;
+ const int yPad = y + mPadding;
+ const std::string &description = data->description;
+ graphics->drawImage(data->icon, mPadding, yPad);
+ font->drawString(graphics, data->name, mTextPadding, yPad);
+ if (!description.empty())
+ {
+ font->drawString(graphics, description,
+ mTextPadding, yPad + space);
+ }
+
+ if (e->skillLevelWidth < 0)
+ {
+ // Add one for padding
+ e->skillLevelWidth = font->getWidth(e->skillLevel) + 1;
+ }
+
+ font->drawString(graphics, e->skillLevel, width2
+ - e->skillLevelWidth, yPad);
+ }
+ }
+ }
+
+ unsigned int getRowHeight() const override
+ { return mRowHeight; }
+
+ const SkillInfo *getSkillByEvent(const gcn::MouseEvent &event) const
+ {
+ const int y = (event.getY() + mPadding) / getRowHeight();
+ if (!mModel || y >= mModel->getNumberOfElements())
+ return nullptr;
+ const SkillInfo *const skill = mModel->getSkillAt(y);
+ if (!skill)
+ return nullptr;
+ return skill;
+ }
+
+ void mouseMoved(gcn::MouseEvent &event) override
+ {
+ ListBox::mouseMoved(event);
+ if (!viewport || !dragDrop.isEmpty())
+ return;
+
+ const SkillInfo *const skill = getSkillByEvent(event);
+ if (!skill)
+ return;
+
+ mPopup->show(viewport->getMouseX(), viewport->getMouseY(),
+ skill->data->dispName, skill->data->description);
+ }
+
+ void mouseDragged(gcn::MouseEvent &event)
+ {
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (dragDrop.isEmpty())
+ {
+ if (mSkillClicked)
+ {
+ mSkillClicked = false;
+ const SkillInfo *const skill = getSkillByEvent(event);
+ if (!skill)
+ return;
+ dragDrop.dragSkill(skill, DRAGDROP_SOURCE_SKILLS);
+ dragDrop.setItem(skill->id + SKILL_MIN_ID);
+ }
+ ListBox::mouseDragged(event);
+ }
+ }
+ else
+ {
+ ListBox::mouseDragged(event);
+ }
+ }
+
+ void mousePressed(gcn::MouseEvent &event)
+ {
+ ListBox::mousePressed(event);
+ if (event.getButton() == gcn::MouseEvent::LEFT)
+ {
+ const SkillInfo *const skill = getSkillByEvent(event);
+ if (!skill)
+ return;
+ mSkillClicked = true;
+ }
+ }
+
+ void mouseReleased(gcn::MouseEvent &event)
+ {
+ ListBox::mouseReleased(event);
+ }
+
+ void mouseExited(gcn::MouseEvent &event A_UNUSED) override
+ {
+ mPopup->hide();
+ }
+
+ private:
+ SkillModel *mModel;
+ TextPopup *mPopup;
+ gcn::Color mHighlightColor;
+ gcn::Color mTextColor;
+ gcn::Color mTextColor2;
+ int mTextPadding;
+ int mSpacing;
+ int mRowHeight;
+ bool mSkillClicked;
+};
+
+class SkillTab final : public Tab
+{
+ public:
+ SkillTab(const Widget2 *const widget,
+ const std::string &name, SkillListBox *const listBox) :
+ Tab(widget),
+ mListBox(listBox)
+ {
+ setCaption(name);
+ }
+
+ A_DELETE_COPY(SkillTab)
+
+ ~SkillTab()
+ {
+ delete mListBox;
+ mListBox = nullptr;
+ }
+
+ SkillInfo *getSelectedInfo() const
+ {
+ if (mListBox)
+ return mListBox->getSelectedInfo();
+ else
+ return nullptr;
+ }
+
+ protected:
+ void setCurrent() override
+ {
+ if (skillDialog)
+ skillDialog->updateTabSelection();
+ }
+
+ private:
+ SkillListBox *mListBox;
+};
+
+SkillDialog::SkillDialog() :
+ // TRANSLATORS: skills dialog name
+ Window(_("Skills"), false, nullptr, "skills.xml"),
+ gcn::ActionListener(),
+ mSkills(),
+ mTabs(new TabbedArea(this)),
+ mDeleteTabs(),
+ mPointsLabel(new Label(this, "0")),
+ // TRANSLATORS: skills dialog button
+ mUseButton(new Button(this, _("Use"), "use", this)),
+ // TRANSLATORS: skills dialog button
+ mIncreaseButton(new Button(this, _("Up"), "inc", this)),
+ mDefaultModel(nullptr)
+{
+ setWindowName("Skills");
+ setCloseButton(true);
+ setResizable(true);
+ setSaveVisible(true);
+ setStickyButtonLock(true);
+ setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425);
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ mUseButton->setEnabled(false);
+ mIncreaseButton->setEnabled(false);
+
+ place(0, 0, mTabs, 5, 5);
+ place(0, 5, mPointsLabel, 4);
+ place(3, 5, mUseButton);
+ place(4, 5, mIncreaseButton);
+
+ setLocationRelativeTo(getParent());
+ loadWindowState();
+ enableVisibleSound(true);
+}
+
+SkillDialog::~SkillDialog()
+{
+ clearSkills();
+}
+
+void SkillDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "inc")
+ {
+ const SkillTab *const tab = static_cast<const SkillTab *const>(
+ mTabs->getSelectedTab());
+ if (tab)
+ {
+ if (const SkillInfo *const info = tab->getSelectedInfo())
+ Net::getPlayerHandler()->increaseSkill(info->id);
+ }
+ }
+ else if (eventId == "sel")
+ {
+ const SkillTab *const tab = static_cast<const SkillTab *const>(
+ mTabs->getSelectedTab());
+ if (tab)
+ {
+ if (const SkillInfo *const info = tab->getSelectedInfo())
+ {
+ mUseButton->setEnabled(info->range > 0);
+ mIncreaseButton->setEnabled(info->id < SKILL_VAR_MIN_ID);
+ const int num = itemShortcutWindow->getTabIndex();
+ if (num >= 0 && num < static_cast<int>(SHORTCUT_TABS)
+ && itemShortcut[num])
+ {
+ itemShortcut[num]->setItemSelected(
+ info->id + SKILL_MIN_ID);
+ }
+ }
+ else
+ {
+ mUseButton->setEnabled(false);
+ mIncreaseButton->setEnabled(false);
+ }
+ }
+ }
+ else if (eventId == "use")
+ {
+ const SkillTab *const tab = static_cast<const SkillTab *const>(
+ mTabs->getSelectedTab());
+ if (tab)
+ {
+ const SkillInfo *const info = tab->getSelectedInfo();
+ if (info && player_node && player_node->getTarget())
+ {
+ const Being *const being = player_node->getTarget();
+ if (being)
+ {
+ Net::getSkillHandler()->useBeing(info->level,
+ info->id, being->getId());
+ }
+ }
+ }
+ }
+ else if (eventId == "close")
+ {
+ setVisible(false);
+ }
+}
+
+std::string SkillDialog::update(const int id)
+{
+ const SkillMap::const_iterator i = mSkills.find(id);
+
+ if (i != mSkills.end())
+ {
+ SkillInfo *const info = i->second;
+ if (info)
+ {
+ info->update();
+ return info->data->name;
+ }
+ }
+
+ return std::string();
+}
+
+void SkillDialog::update()
+{
+ // TRANSLATORS: skills dialog label
+ mPointsLabel->setCaption(strprintf(_("Skill points available: %d"),
+ PlayerInfo::getAttribute(PlayerInfo::SKILL_POINTS)));
+ mPointsLabel->adjustSize();
+
+ FOR_EACH (SkillMap::const_iterator, it, mSkills)
+ {
+ if ((*it).second && (*it).second->modifiable)
+ (*it).second->update();
+ }
+}
+
+void SkillDialog::clearSkills()
+{
+ mTabs->removeAll();
+ mDeleteTabs.clear();
+ mDefaultModel = nullptr;
+
+ delete_all(mSkills);
+ mSkills.clear();
+}
+
+void SkillDialog::loadSkills()
+{
+ clearSkills();
+
+ XML::Document doc(paths.getStringValue("skillsFile"));
+ XML::Document doc2(paths.getStringValue("skillsFile2"));
+ XmlNodePtr root = doc.rootNode();
+
+ int setCount = 0;
+ std::string setName;
+ ScrollArea *scroll;
+ SkillListBox *listbox;
+ SkillTab *tab;
+
+ if (!root || !xmlNameEqual(root, "skills"))
+ root = doc2.rootNode();
+
+ if (!root || !xmlNameEqual(root, "skills"))
+ {
+ logger->log("Error loading skills");
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ SkillModel *const model = new SkillModel();
+ if (!mDefaultModel)
+ mDefaultModel = model;
+
+ SkillInfo *const skill = new SkillInfo;
+ skill->id = 1;
+ // TRANSLATORS: skills dialog default skills tab
+ skill->data->name = _("basic");
+ skill->data->description.clear();
+ // TRANSLATORS: skills dialog default skill name
+ skill->data->dispName = _("basic, 1");
+ skill->data->shortName = "bas";
+ skill->data->setIcon("");
+ skill->modifiable = true;
+ skill->visible = true;
+ skill->model = model;
+ skill->update();
+
+ model->addSkill(skill);
+ mSkills[1] = skill;
+
+ model->updateVisibilities();
+
+ listbox = new SkillListBox(this, model);
+ listbox->setActionEventId("sel");
+ listbox->addActionListener(this);
+ scroll = new ScrollArea(listbox, false);
+ scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
+
+ tab = new SkillTab(this, "Skills", listbox);
+ mDeleteTabs.push_back(tab);
+
+ mTabs->addTab(tab, scroll);
+
+ update();
+ }
+ return;
+ }
+
+ for_each_xml_child_node(set, root)
+ {
+ if (xmlNameEqual(set, "set"))
+ {
+ setCount++;
+ setName = XML::getProperty(set, "name",
+ // TRANSLATORS: skills dialog default skill tab
+ strprintf(_("Skill Set %d"), setCount));
+
+ SkillModel *const model = new SkillModel();
+ if (!mDefaultModel)
+ mDefaultModel = model;
+
+ for_each_xml_child_node(node, set)
+ {
+ if (xmlNameEqual(node, "skill"))
+ {
+ int id = XML::getIntProperty(node, "id", -1, -1, 1000000);
+ if (id == -1)
+ {
+ id = XML::getIntProperty(node, "var", -1, -1, 100000);
+ if (id == -1)
+ continue;
+ id += SKILL_VAR_MIN_ID;
+ }
+
+ SkillInfo *skill = getSkill(id);
+ if (!skill)
+ {
+ skill = new SkillInfo;
+ skill->id = static_cast<unsigned int>(id);
+ skill->modifiable = false;
+ skill->visible = false;
+ skill->model = model;
+ skill->update();
+ model->addSkill(skill);
+ mSkills[id] = skill;
+ }
+
+ std::string name = XML::langProperty(node, "name",
+ // TRANSLATORS: skills dialog. skill id
+ strprintf(_("Skill %d"), id));
+ std::string icon = XML::getProperty(node, "icon", "");
+ const int level = XML::getProperty(node, "level", 0);
+ SkillData *data = skill->getData(level);
+ if (!data)
+ data = new SkillData();
+
+ data->name = name;
+ data->setIcon(icon);
+ if (skill->id < SKILL_VAR_MIN_ID)
+ {
+ data->dispName = strprintf("%s, %u",
+ name.c_str(), skill->id);
+ }
+ else
+ {
+ data->dispName = strprintf("%s, (%u)",
+ name.c_str(), skill->id - SKILL_VAR_MIN_ID);
+ }
+ data->shortName = XML::langProperty(node,
+ "shortName", name.substr(0, 3));
+ data->description = XML::langProperty(
+ node, "description", "");
+ data->particle = XML::getProperty(
+ node, "particle", "");
+
+ data->soundHit.sound = XML::getProperty(
+ node, "soundHit", "");
+ data->soundHit.delay = XML::getProperty(
+ node, "soundHitDelay", 0);
+ data->soundMiss.sound = XML::getProperty(
+ node, "soundMiss", "");
+ data->soundMiss.delay = XML::getProperty(
+ node, "soundMissDelay", 0);
+
+ skill->addData(level, data);
+ }
+ }
+
+ model->updateVisibilities();
+
+ // possible leak listbox, scroll
+ listbox = new SkillListBox(this, model);
+ listbox->setActionEventId("sel");
+ listbox->addActionListener(this);
+ scroll = new ScrollArea(listbox, false);
+ scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
+
+ tab = new SkillTab(this, setName, listbox);
+ mDeleteTabs.push_back(tab);
+
+ mTabs->addTab(tab, scroll);
+ }
+ }
+ update();
+}
+
+bool SkillDialog::updateSkill(const int id, const int range,
+ const bool modifiable)
+{
+ const SkillMap::const_iterator it = mSkills.find(id);
+
+ if (it != mSkills.end())
+ {
+ SkillInfo *const info = it->second;
+ if (info)
+ {
+ info->modifiable = modifiable;
+ info->range = range;
+ info->update();
+ }
+ return true;
+ }
+ return false;
+}
+
+void SkillDialog::addSkill(const int id, const int level, const int range,
+ const bool modifiable)
+{
+ if (mDefaultModel)
+ {
+ SkillInfo *const skill = new SkillInfo;
+ skill->id = static_cast<unsigned int>(id);
+ SkillData *const data = skill->data;
+ data->name = "Unknown skill Id: " + toString(id);
+ data->dispName = data->name;
+ data->description.clear();
+ data->setIcon("");
+ skill->modifiable = modifiable;
+ skill->visible = false;
+ skill->model = mDefaultModel;
+ skill->level = level;
+ // TRANSLATORS: skills dialog. skill level
+ skill->skillLevel = strprintf(_("Lvl: %d"), level);
+ skill->range = range;
+ skill->update();
+
+ mDefaultModel->addSkill(skill);
+
+ mSkills[id] = skill;
+ mDefaultModel->updateVisibilities();
+ }
+}
+
+SkillInfo* SkillDialog::getSkill(const int id) const
+{
+ SkillMap::const_iterator it = mSkills.find(id);
+ if (it != mSkills.end())
+ return (*it).second;
+ return nullptr;
+}
+
+SkillInfo* SkillDialog::getSkillByItem(const int itemId) const
+{
+ SkillMap::const_iterator it = mSkills.find(itemId - SKILL_MIN_ID);
+ if (it != mSkills.end())
+ return (*it).second;
+ return nullptr;
+}
+
+void SkillDialog::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+
+ if (mTabs)
+ mTabs->adjustSize();
+}
+
+void SkillDialog::useItem(const int itemId) const
+{
+ const std::map<int, SkillInfo*>::const_iterator
+ it = mSkills.find(itemId - SKILL_MIN_ID);
+ if (it == mSkills.end())
+ return;
+
+ const SkillInfo *const info = (*it).second;
+ if (info && player_node && player_node->getTarget())
+ {
+ const Being *const being = player_node->getTarget();
+ if (being)
+ {
+ Net::getSkillHandler()->useBeing(info->level,
+ info->id, being->getId());
+ }
+ }
+}
+
+void SkillDialog::updateTabSelection()
+{
+ const SkillTab *const tab = static_cast<SkillTab*>(
+ mTabs->getSelectedTab());
+ if (tab)
+ {
+ if (const SkillInfo *const info = tab->getSelectedInfo())
+ {
+ mUseButton->setEnabled(info->range > 0);
+ mIncreaseButton->setEnabled(info->id < SKILL_VAR_MIN_ID);
+ }
+ else
+ {
+ mUseButton->setEnabled(false);
+ }
+ }
+}
+
+void SkillDialog::updateQuest(const int var, const int val)
+{
+ const int id = var + SKILL_VAR_MIN_ID;
+ const SkillMap::const_iterator it = mSkills.find(id);
+
+ if (it != mSkills.end())
+ {
+ SkillInfo *const info = it->second;
+ if (info)
+ {
+ PlayerInfo::setSkillLevel(id, val);
+ info->level = val;
+ info->update();
+ }
+ }
+}
+
+void SkillDialog::playUpdateEffect(const int id) const
+{
+ const int effectId = paths.getIntValue("skillLevelUpEffectId");
+ if (!effectManager || effectId == -1)
+ return;
+ const SkillMap::const_iterator it = mSkills.find(id);
+ if (it != mSkills.end())
+ {
+ if (it->second)
+ effectManager->trigger(effectId, player_node);
+ }
+}
diff --git a/src/gui/windows/skilldialog.h b/src/gui/windows/skilldialog.h
new file mode 100644
index 000000000..9715a3bf3
--- /dev/null
+++ b/src/gui/windows/skilldialog.h
@@ -0,0 +1,109 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_SKILLDIALOG_H
+#define GUI_SKILLDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+const int SKILL_MIN_ID = 200000;
+const unsigned int SKILL_VAR_MIN_ID = 1000000;
+
+class Button;
+class Label;
+class SkillModel;
+class Tab;
+class TabbedArea;
+
+struct SkillInfo;
+
+/**
+ * The skill dialog.
+ *
+ * \ingroup Interface
+ */
+class SkillDialog final : public Window, public gcn::ActionListener
+{
+ public:
+ SkillDialog();
+
+ A_DELETE_COPY(SkillDialog)
+
+ ~SkillDialog();
+
+ /**
+ * Called when receiving actions from widget.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Update the given skill's display
+ */
+ std::string update(const int id);
+
+ /**
+ * Update other parts of the display
+ */
+ void update();
+
+ void clearSkills();
+
+ void loadSkills();
+
+ bool updateSkill(const int id, const int range, const bool modifiable);
+
+ void addSkill(const int id, const int level, const int range,
+ const bool modifiable);
+
+ SkillInfo* getSkill(const int id) const A_WARN_UNUSED;
+
+ SkillInfo* getSkillByItem(const int itemId) const A_WARN_UNUSED;
+
+ bool hasSkills() const A_WARN_UNUSED
+ { return !mSkills.empty(); }
+
+ void widgetResized(const gcn::Event &event) override;
+
+ void useItem(const int itemId) const;
+
+ void updateTabSelection();
+
+ void updateQuest(const int var, const int val);
+
+ void playUpdateEffect(const int id) const;
+
+ private:
+ typedef std::map<int, SkillInfo*> SkillMap;
+ SkillMap mSkills;
+ TabbedArea *mTabs;
+ std::list<Tab*> mDeleteTabs;
+ Label *mPointsLabel;
+ Button *mUseButton;
+ Button *mIncreaseButton;
+ SkillModel *mDefaultModel;
+};
+
+extern SkillDialog *skillDialog;
+
+#endif // GUI_SKILLDIALOG_H
diff --git a/src/gui/windows/socialwindow.cpp b/src/gui/windows/socialwindow.cpp
new file mode 100644
index 000000000..687b1af69
--- /dev/null
+++ b/src/gui/windows/socialwindow.cpp
@@ -0,0 +1,1895 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/socialwindow.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+#include "guild.h"
+#include "guildmanager.h"
+#include "maplayer.h"
+#include "party.h"
+
+#include "being/localplayer.h"
+#include "being/playerrelations.h"
+
+#include "input/keyboardconfig.h"
+
+#include "gui/windows/confirmdialog.h"
+#include "gui/windows/okdialog.h"
+#include "gui/windows/setup.h"
+#include "gui/windows/textdialog.h"
+#include "gui/windows/whoisonline.h"
+
+#include "gui/windows/outfitwindow.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/chattab.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/popup.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "net/net.h"
+#include "net/guildhandler.h"
+#include "net/partyhandler.h"
+
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+extern unsigned int tmwServerVersion;
+
+namespace
+{
+ static class SortFriendsFunctor final
+ {
+ public:
+ bool operator() (const Avatar *const m1,
+ const Avatar *const m2) const
+ {
+ if (!m1 || !m2)
+ return false;
+
+ if (m1->getOnline() != m2->getOnline())
+ return m1->getOnline() > m2->getOnline();
+
+ if (m1->getName() != m2->getName())
+ {
+ std::string s1 = m1->getName();
+ std::string s2 = m2->getName();
+ toLower(s1);
+ toLower(s2);
+ return s1 < s2;
+ }
+ return false;
+ }
+ } friendSorter;
+} // namespace
+
+class SocialTab : public Tab
+{
+public:
+ A_DELETE_COPY(SocialTab)
+
+ virtual void invite()
+ {
+ }
+
+ virtual void leave()
+ {
+ }
+
+ virtual void updateList()
+ {
+ }
+
+ virtual void updateAvatar(const std::string &name A_UNUSED)
+ {
+ }
+
+ virtual void resetDamage(const std::string &name A_UNUSED)
+ {
+ }
+
+ virtual void selectIndex(const unsigned num A_UNUSED)
+ { }
+
+protected:
+ friend class SocialWindow;
+
+ explicit SocialTab(const Widget2 *const widget):
+ Tab(widget),
+ mInviteDialog(nullptr),
+ mConfirmDialog(nullptr),
+ mScroll(nullptr),
+ mList(nullptr),
+ mCounterString()
+ {
+ }
+
+ virtual ~SocialTab()
+ {
+ // Cleanup dialogs
+ if (mInviteDialog)
+ {
+ mInviteDialog->close();
+ mInviteDialog->scheduleDelete();
+ mInviteDialog = nullptr;
+ }
+
+ if (mConfirmDialog)
+ {
+ mConfirmDialog->close();
+ mConfirmDialog->scheduleDelete();
+ mConfirmDialog = nullptr;
+ }
+ }
+
+ void setCurrent() override
+ {
+ updateCounter();
+ }
+
+ void updateCounter() const
+ {
+ if (socialWindow)
+ socialWindow->setCounter(this, mCounterString);
+ }
+
+ virtual void buildCounter(const int online A_UNUSED = 0,
+ const int total A_UNUSED = 0)
+ {
+ }
+
+ TextDialog *mInviteDialog;
+ ConfirmDialog *mConfirmDialog;
+ ScrollArea *mScroll;
+ AvatarListBox *mList;
+ std::string mCounterString;
+};
+
+class SocialGuildTab final : public SocialTab, public gcn::ActionListener
+{
+public:
+ SocialGuildTab(const Widget2 *const widget,
+ Guild *const guild, const bool showBackground) :
+ SocialTab(widget),
+ gcn::ActionListener(),
+ mGuild(guild)
+ {
+ // TRANSLATORS: tab in social window
+ setCaption(_("Guild"));
+
+ setTabColor(&getThemeColor(Theme::GUILD_SOCIAL_TAB),
+ &getThemeColor(Theme::GUILD_SOCIAL_TAB_OUTLINE));
+ setHighlightedTabColor(&getThemeColor(
+ Theme::GUILD_SOCIAL_TAB_HIGHLIGHTED), &getThemeColor(
+ Theme::GUILD_SOCIAL_TAB_HIGHLIGHTED_OUTLINE));
+ setSelectedTabColor(&getThemeColor(Theme::GUILD_SOCIAL_TAB_SELECTED),
+ &getThemeColor(Theme::GUILD_SOCIAL_TAB_SELECTED_OUTLINE));
+
+ mList = new AvatarListBox(this, guild);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+ }
+
+ A_DELETE_COPY(SocialGuildTab)
+
+ ~SocialGuildTab()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ }
+
+ void action(const gcn::ActionEvent &event) override
+ {
+ const std::string &eventId = event.getId();
+ if (eventId == "do invite")
+ {
+ const std::string name = mInviteDialog->getText();
+ Net::getGuildHandler()->invite(mGuild->getId(), name);
+
+ if (localChatTab)
+ {
+ localChatTab->chatLog(strprintf(
+ // TRANSLATORS: chat message
+ _("Invited user %s to guild %s."),
+ name.c_str(), mGuild->getName().c_str()), BY_SERVER);
+ }
+ mInviteDialog = nullptr;
+ }
+ else if (eventId == "~do invite")
+ {
+ mInviteDialog = nullptr;
+ }
+ else if (eventId == "yes")
+ {
+ Net::getGuildHandler()->leave(mGuild->getId());
+ if (localChatTab)
+ {
+ // TRANSLATORS: chat message
+ localChatTab->chatLog(strprintf(_("Guild %s quit requested."),
+ mGuild->getName().c_str()), BY_SERVER);
+ }
+ mConfirmDialog = nullptr;
+ }
+ else if (eventId == "~yes")
+ {
+ mConfirmDialog = nullptr;
+ }
+ }
+
+ void invite() override
+ {
+ // TRANSLATORS: guild invite message
+ mInviteDialog = new TextDialog(_("Member Invite to Guild"),
+ // TRANSLATORS: guild invite message
+ strprintf(_("Who would you like to invite to guild %s?"),
+ mGuild->getName().c_str()), socialWindow);
+ mInviteDialog->setActionEventId("do invite");
+ mInviteDialog->addActionListener(this);
+ }
+
+ void leave() override
+ {
+ // TRANSLATORS: guild leave message
+ mConfirmDialog = new ConfirmDialog(_("Leave Guild?"),
+ // TRANSLATORS: guild leave message
+ strprintf(_("Are you sure you want to leave guild %s?"),
+ mGuild->getName().c_str()), SOUND_REQUEST, socialWindow);
+
+ mConfirmDialog->addActionListener(this);
+ }
+
+ void buildCounter(const int online0, const int total0)
+ {
+ if (online0 || total0)
+ {
+ // TRANSLATORS: social window label
+ mCounterString = strprintf(_("Members: %u/%u"), online0, total0);
+ }
+ else
+ {
+ if (!player_node)
+ return;
+
+ const Guild *const guild = player_node->getGuild();
+ if (!guild)
+ return;
+
+ const Guild::MemberList *const members = guild->getMembers();
+ int online = 0;
+ int total = 0;
+ FOR_EACHP (Guild::MemberList::const_iterator, it, members)
+ {
+ if ((*it)->getOnline())
+ online ++;
+ total ++;
+ }
+
+ // TRANSLATORS: social window label
+ mCounterString = strprintf(_("Players: %u/%u"), online, total);
+ }
+ updateCounter();
+ }
+
+private:
+ Guild *mGuild;
+};
+
+class SocialGuildTab2 final : public SocialTab, public gcn::ActionListener
+{
+public:
+ SocialGuildTab2(const Widget2 *const widget, Guild *const guild,
+ const bool showBackground) :
+ SocialTab(widget),
+ gcn::ActionListener()
+ {
+ // TRANSLATORS: tab in social window
+ setCaption(_("Guild"));
+
+ setTabColor(&getThemeColor(Theme::GUILD_SOCIAL_TAB),
+ &getThemeColor(Theme::GUILD_SOCIAL_TAB_OUTLINE));
+ setHighlightedTabColor(&getThemeColor(
+ Theme::GUILD_SOCIAL_TAB_HIGHLIGHTED), &getThemeColor(
+ Theme::GUILD_SOCIAL_TAB_HIGHLIGHTED_OUTLINE));
+ setSelectedTabColor(&getThemeColor(Theme::GUILD_SOCIAL_TAB_SELECTED),
+ &getThemeColor(Theme::GUILD_SOCIAL_TAB_SELECTED_OUTLINE));
+
+ mList = new AvatarListBox(this, guild);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+ }
+
+ A_DELETE_COPY(SocialGuildTab2)
+
+ ~SocialGuildTab2()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ }
+
+ void action(const gcn::ActionEvent &event A_UNUSED) override
+ {
+ }
+
+ void buildCounter(const int online0 A_UNUSED, const int total0 A_UNUSED)
+ {
+ if (!player_node)
+ return;
+
+ const Guild *const guild = player_node->getGuild();
+ if (!guild)
+ return;
+
+ const Guild::MemberList *const members = guild->getMembers();
+ int online = 0;
+ int total = 0;
+ FOR_EACHP (Guild::MemberList::const_iterator, it, members)
+ {
+ if ((*it)->getOnline())
+ online ++;
+ total ++;
+ }
+
+ // TRANSLATORS: social window label
+ mCounterString = strprintf(_("Players: %u/%u"), online, total);
+ updateCounter();
+ }
+};
+
+class SocialPartyTab final : public SocialTab, public gcn::ActionListener
+{
+public:
+ SocialPartyTab(const Widget2 *const widget,
+ Party *const party, const bool showBackground) :
+ SocialTab(widget),
+ gcn::ActionListener(),
+ mParty(party)
+ {
+ // TRANSLATORS: tab in social window
+ setCaption(_("Party"));
+
+ setTabColor(&getThemeColor(Theme::PARTY_SOCIAL_TAB),
+ &getThemeColor(Theme::PARTY_SOCIAL_TAB_OUTLINE));
+ setHighlightedTabColor(&getThemeColor(
+ Theme::PARTY_SOCIAL_TAB_HIGHLIGHTED), &getThemeColor(
+ Theme::PARTY_SOCIAL_TAB_HIGHLIGHTED_OUTLINE));
+ setSelectedTabColor(&getThemeColor(Theme::PARTY_SOCIAL_TAB_SELECTED),
+ &getThemeColor(Theme::PARTY_SOCIAL_TAB_SELECTED_OUTLINE));
+
+ mList = new AvatarListBox(this, party);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+ }
+
+ A_DELETE_COPY(SocialPartyTab)
+
+ ~SocialPartyTab()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ }
+
+ void action(const gcn::ActionEvent &event) override
+ {
+ const std::string &eventId = event.getId();
+ if (eventId == "do invite")
+ {
+ const std::string name = mInviteDialog->getText();
+ Net::getPartyHandler()->invite(name);
+
+ if (localChatTab)
+ {
+ // TRANSLATORS: chat message
+ localChatTab->chatLog(strprintf(_("Invited user %s to party."),
+ name.c_str()), BY_SERVER);
+ }
+ mInviteDialog = nullptr;
+ }
+ else if (eventId == "~do invite")
+ {
+ mInviteDialog = nullptr;
+ }
+ else if (eventId == "yes")
+ {
+ Net::getPartyHandler()->leave();
+ if (localChatTab)
+ {
+ // TRANSLATORS: tab in social window
+ localChatTab->chatLog(strprintf(_("Party %s quit requested."),
+ mParty->getName().c_str()), BY_SERVER);
+ }
+ mConfirmDialog = nullptr;
+ }
+ else if (eventId == "~yes")
+ {
+ mConfirmDialog = nullptr;
+ }
+ }
+
+ void invite() override
+ {
+ // TRANSLATORS: party invite message
+ mInviteDialog = new TextDialog(_("Member Invite to Party"),
+ // TRANSLATORS: party invite message
+ strprintf(_("Who would you like to invite to party %s?"),
+ mParty->getName().c_str()), socialWindow);
+ mInviteDialog->setActionEventId("do invite");
+ mInviteDialog->addActionListener(this);
+ }
+
+ void leave() override
+ {
+ // TRANSLATORS: party leave message
+ mConfirmDialog = new ConfirmDialog(_("Leave Party?"),
+ // TRANSLATORS: party leave message
+ strprintf(_("Are you sure you want to leave party %s?"),
+ mParty->getName().c_str()), SOUND_REQUEST, socialWindow);
+
+ mConfirmDialog->addActionListener(this);
+ }
+
+ void buildCounter(const int online0 A_UNUSED, const int total0 A_UNUSED)
+ {
+ if (!player_node)
+ return;
+
+ const Party *const party = player_node->getParty();
+ if (!party)
+ return;
+
+ const Party::MemberList *const members = party->getMembers();
+ int online = 0;
+ int total = 0;
+ FOR_EACHP (Party::MemberList::const_iterator, it, members)
+ {
+ if ((*it)->getOnline())
+ online ++;
+ total ++;
+ }
+
+ // TRANSLATORS: social window label
+ mCounterString = strprintf(_("Players: %u/%u"), online, total);
+ updateCounter();
+ }
+
+private:
+ Party *mParty;
+};
+
+class BeingsListModal final : public AvatarListModel
+{
+public:
+ BeingsListModal() :
+ AvatarListModel(),
+ mMembers()
+ {
+ }
+
+ A_DELETE_COPY(BeingsListModal)
+
+ ~BeingsListModal()
+ {
+ delete_all(mMembers);
+ mMembers.clear();
+ }
+
+ std::vector<Avatar*> *getMembers()
+ {
+ return &mMembers;
+ }
+
+ Avatar *getAvatarAt(int index) override
+ {
+ return mMembers[index];
+ }
+
+ int getNumberOfElements() override
+ {
+ return static_cast<int>(mMembers.size());
+ }
+
+ std::vector<Avatar*> mMembers;
+};
+
+class SocialPlayersTab final : public SocialTab
+{
+public:
+ SocialPlayersTab(const Widget2 *const widget,
+ std::string name, const bool showBackground) :
+ SocialTab(widget),
+ mBeings(new BeingsListModal)
+ {
+ mList = new AvatarListBox(this, mBeings);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ updateList();
+ setCaption(name);
+ }
+
+ A_DELETE_COPY(SocialPlayersTab)
+
+ ~SocialPlayersTab()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ delete mBeings;
+ mBeings = nullptr;
+ }
+
+ void updateList() override
+ {
+ getPlayersAvatars();
+ }
+
+ void updateAvatar(const std::string &name) override
+ {
+ if (!actorSpriteManager)
+ return;
+
+ Avatar *const avatar = findAvatarbyName(name);
+ if (!avatar)
+ return;
+ if (Party::getParty(1))
+ {
+ const PartyMember *const pm = Party::getParty(1)->getMember(name);
+ if (pm && pm->getMaxHp() > 0)
+ {
+ avatar->setMaxHp(pm->getMaxHp());
+ avatar->setHp(pm->getHp());
+ }
+ }
+ const Being *const 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(const std::string &name) override
+ {
+ if (!actorSpriteManager)
+ return;
+
+ Avatar *const avatar = findAvatarbyName(name);
+ if (!avatar)
+ return;
+ avatar->setDamageHp(0);
+ Being *const being = actorSpriteManager->findBeingByName(
+ name, Being::PLAYER);
+
+ if (being)
+ being->setDamageTaken(0);
+ }
+
+ Avatar* findAvatarbyName(std::string name)
+ {
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+ if (!avatars)
+ return nullptr;
+
+ Avatar *ava = nullptr;
+ std::vector<Avatar*>::const_iterator i = avatars->begin();
+ const std::vector<Avatar*>::const_iterator i_end = avatars->end();
+ while (i != i_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*> *const avatars = mBeings->getMembers();
+ if (!avatars)
+ return;
+
+ if (actorSpriteManager)
+ {
+ StringVect names;
+ actorSpriteManager->getPlayerNames(names, false);
+
+ std::vector<Avatar*>::iterator ai = avatars->begin();
+ while (ai != avatars->end())
+ {
+ bool finded = false;
+ const Avatar *const ava = (*ai);
+ if (!ava)
+ break;
+
+ StringVectCIter i = names.begin();
+ const StringVectCIter i_end = names.end();
+ while (i != i_end)
+ {
+ if (ava->getName() == (*i) && (*i) != "")
+ {
+ finded = true;
+ break;
+ }
+ ++i;
+ }
+
+ if (!finded)
+ {
+ delete *ai;
+ ai = avatars->erase(ai);
+ }
+ else
+ {
+ ++ai;
+ }
+ }
+
+ StringVectCIter i = names.begin();
+ const StringVectCIter i_end = names.end();
+
+ while (i != i_end)
+ {
+ if ((*i) != "")
+ updateAvatar(*i);
+ ++i;
+ }
+ }
+ // TRANSLATORS: social window label
+ mCounterString = strprintf(_("Visible players: %d"),
+ static_cast<int>(avatars->size()));
+ updateCounter();
+ }
+
+private:
+ BeingsListModal *mBeings;
+};
+
+
+class SocialNavigationTab final : public SocialTab
+{
+public:
+ SocialNavigationTab(const Widget2 *const widget,
+ const bool showBackground) :
+ SocialTab(widget),
+ mBeings(new BeingsListModal)
+ {
+ mList = new AvatarListBox(this, mBeings);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ // TRANSLATORS: Navigation tab name in social window. Should be small
+ setCaption(_("Nav"));
+ }
+
+ A_DELETE_COPY(SocialNavigationTab)
+
+ ~SocialNavigationTab()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ delete mBeings;
+ mBeings = nullptr;
+ }
+
+ void updateList() override
+ {
+ if (!socialWindow || !player_node)
+ return;
+
+ const Map *const map = socialWindow->getMap();
+ if (!map || map->empty())
+ return;
+
+ if (socialWindow->getProcessedPortals())
+ return;
+
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+ std::vector<MapItem*> portals = map->getPortals();
+
+ std::vector<MapItem*>::const_iterator i = portals.begin();
+ const SpecialLayer *const specialLayer = map->getSpecialLayer();
+
+ std::vector<Avatar*>::iterator ia = avatars->begin();
+
+ while (ia != avatars->end())
+ {
+ delete *ia;
+ ++ ia;
+ }
+
+ avatars->clear();
+
+ int online = 0;
+ int total = 0;
+
+ int idx = 0;
+ while (i != portals.end())
+ {
+ MapItem *portal = *i;
+ if (!portal)
+ continue;
+
+ const int x = portal->getX();
+ const int y = portal->getY();
+
+ const std::string name = strprintf("%s [%d %d]",
+ portal->getComment().c_str(), x, y);
+
+ Avatar *const ava = new Avatar(name);
+ if (player_node)
+ ava->setOnline(player_node->isReachable(x, y, true));
+ else
+ ava->setOnline(false);
+ ava->setLevel(-1);
+ ava->setType(portal->getType());
+ ava->setX(x);
+ ava->setY(y);
+ avatars->push_back(ava);
+
+ if (ava->getOnline())
+ online ++;
+ total ++;
+
+ if (config.getBoolValue("drawHotKeys") && idx < 80 && outfitWindow)
+ {
+ Being *const 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);
+
+ // TRANSLATORS: social window label
+ mCounterString = strprintf(_("Portals: %u/%u"), online, total);
+ updateCounter();
+ }
+
+
+ void selectIndex(const unsigned num) override
+ {
+ if (!player_node)
+ return;
+
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+ if (!avatars || avatars->size() <= num)
+ return;
+
+ const Avatar *const ava = avatars->at(num);
+ if (ava && player_node)
+ player_node->navigateTo(ava->getX(), ava->getY());
+ }
+
+ void updateNames()
+ {
+ if (!socialWindow)
+ return;
+
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+ if (!avatars)
+ return;
+
+ const Map *const map = socialWindow->getMap();
+ if (!map)
+ return;
+
+ std::vector<Avatar*>::const_iterator i = avatars->begin();
+ const std::vector<Avatar*>::const_iterator i_end = avatars->end();
+ while (i != i_end)
+ {
+ Avatar *const ava = *i;
+ if (!ava)
+ break;
+
+ const MapItem *const item = map->findPortalXY(
+ ava->getX(), ava->getY());
+ if (item)
+ {
+ const std::string name = strprintf("%s [%d %d]",
+ item->getComment().c_str(), item->getX(), item->getY());
+ ava->setName(name);
+ ava->setOriginalName(name);
+ }
+
+ ++i;
+ }
+ }
+
+ int getPortalIndex(const int x, const int y)
+ {
+ if (!socialWindow)
+ return -1;
+
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+ if (!avatars)
+ return -1;
+
+ const Map *const map = socialWindow->getMap();
+ if (!map)
+ return 01;
+
+ std::vector<Avatar*>::const_iterator i = avatars->begin();
+ const std::vector<Avatar*>::const_iterator i_end = avatars->end();
+ unsigned num = 0;
+ while (i != i_end)
+ {
+ const Avatar *const ava = *i;
+ if (!ava)
+ break;
+
+ if (ava->getX() == x && ava->getY() == y)
+ return num;
+
+ ++i;
+ num ++;
+ }
+ return -1;
+ }
+
+ void addPortal(const int x, const int y)
+ {
+ if (!socialWindow || !player_node)
+ return;
+
+ const Map *const map = socialWindow->getMap();
+ if (!map)
+ return;
+
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+
+ if (!avatars)
+ return;
+
+ const MapItem *const portal = map->findPortalXY(x, y);
+ if (!portal)
+ return;
+
+ const std::string name = strprintf("%s [%d %d]",
+ portal->getComment().c_str(), x, y);
+
+ Avatar *const ava = new Avatar(name);
+ if (player_node)
+ ava->setOnline(player_node->isReachable(x, y, true));
+ else
+ ava->setOnline(false);
+ ava->setLevel(-1);
+ ava->setType(portal->getType());
+ ava->setX(x);
+ ava->setY(y);
+ avatars->push_back(ava);
+ }
+
+ void removePortal(const int x, const int y)
+ {
+ if (!socialWindow || !player_node)
+ return;
+
+ const Map *const map = socialWindow->getMap();
+ if (!map)
+ return;
+
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+
+ if (!avatars)
+ return;
+
+ std::vector<Avatar*>::iterator i = avatars->begin();
+ const std::vector<Avatar*>::iterator i_end = avatars->end();
+
+ while (i != i_end)
+ {
+ Avatar *ava = (*i);
+
+ if (!ava)
+ break;
+
+ if (ava->getX() == x && ava->getY() == y)
+ {
+ delete ava;
+ avatars->erase(i);
+ return;
+ }
+
+ ++ i;
+ }
+ }
+
+private:
+ BeingsListModal *mBeings;
+};
+
+
+#define addAvatars(mob, str, type) \
+{\
+ ava = new Avatar(str);\
+ ava->setOnline(false);\
+ ava->setLevel(-1);\
+ ava->setType(MapItem::SEPARATOR);\
+ ava->setX(0);\
+ ava->setY(0);\
+ avatars->push_back(ava);\
+ mobs = actorSpriteManager->get##mob##s();\
+ i = mobs.begin();\
+ i_end = mobs.end();\
+ while (i != i_end)\
+ {\
+ std::string name;\
+ int level = -1;\
+ if (*i == "")\
+ {\
+ name = _("(default)");\
+ level = 0;\
+ }\
+ else\
+ {\
+ name = *i;\
+ }\
+ ava = new Avatar(name);\
+ ava->setOnline(true);\
+ ava->setLevel(level);\
+ ava->setType(MapItem::type);\
+ ava->setX(0);\
+ ava->setY(0);\
+ avatars->push_back(ava);\
+ ++ i;\
+ }\
+}
+
+#define updateAtkListStart() \
+ if (!socialWindow || !player_node || !actorSpriteManager)\
+ return;\
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();\
+ std::vector<Avatar*>::iterator ia = avatars->begin();\
+ while (ia != avatars->end())\
+ {\
+ delete *ia;\
+ ++ ia;\
+ }\
+ avatars->clear();\
+ Avatar *ava;\
+ std::list<std::string> mobs;\
+ std::list<std::string>::const_iterator i;\
+ std::list<std::string>::const_iterator i_end;
+
+class SocialAttackTab final : public SocialTab
+{
+public:
+ SocialAttackTab(const Widget2 *const widget,
+ const bool showBackground) :
+ SocialTab(widget),
+ mBeings(new BeingsListModal)
+ {
+ mList = new AvatarListBox(this, mBeings);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ // TRANSLATORS: Attack filter tab name in social window. Should be small
+ setCaption(_("Atk"));
+ }
+
+ A_DELETE_COPY(SocialAttackTab)
+
+ ~SocialAttackTab()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ delete mBeings;
+ mBeings = nullptr;
+ }
+
+ void updateList() override
+ {
+ updateAtkListStart();
+ // TRANSLATORS: mobs group name in social window
+ addAvatars(PriorityAttackMob, _("Priority mobs"), PRIORITY);
+ // TRANSLATORS: mobs group name in social window
+ addAvatars(AttackMob, _("Attack mobs"), ATTACK);
+ // TRANSLATORS: mobs group name in social window
+ addAvatars(IgnoreAttackMob, _("Ignore mobs"), IGNORE_);
+ }
+
+private:
+ BeingsListModal *mBeings;
+};
+
+class SocialPickupTab final : public SocialTab
+{
+public:
+ SocialPickupTab(const Widget2 *const widget,
+ const bool showBackground) :
+ SocialTab(widget),
+ mBeings(new BeingsListModal)
+ {
+ mList = new AvatarListBox(this, mBeings);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ // TRANSLATORS: Pickup filter tab name in social window. Should be small
+ setCaption(_("Pik"));
+ }
+
+ A_DELETE_COPY(SocialPickupTab)
+
+ ~SocialPickupTab()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ delete mBeings;
+ mBeings = nullptr;
+ }
+
+ void updateList() override
+ {
+ updateAtkListStart();
+ // TRANSLATORS: items group name in social window
+ addAvatars(PickupItem, _("Pickup items"), PICKUP);
+ // TRANSLATORS: items group name in social window
+ addAvatars(IgnorePickupItem, _("Ignore items"), NOPICKUP);
+ }
+
+private:
+ BeingsListModal *mBeings;
+};
+
+
+class SocialFriendsTab final : public SocialTab
+{
+public:
+ SocialFriendsTab(const Widget2 *const widget,
+ std::string name, const bool showBackground) :
+ SocialTab(widget),
+ mBeings(new BeingsListModal)
+ {
+ mList = new AvatarListBox(this, mBeings);
+ mScroll = new ScrollArea(mList, showBackground,
+ "social_background.xml");
+
+ mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
+ mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
+
+ updateList();
+ setCaption(name);
+ }
+
+ A_DELETE_COPY(SocialFriendsTab)
+
+ ~SocialFriendsTab()
+ {
+ delete mList;
+ mList = nullptr;
+ delete mScroll;
+ mScroll = nullptr;
+ delete mBeings;
+ mBeings = nullptr;
+ }
+
+ void updateList() override
+ {
+ getPlayersAvatars();
+ }
+
+ void getPlayersAvatars()
+ {
+ if (!actorSpriteManager)
+ return;
+
+ std::vector<Avatar*> *const avatars = mBeings->getMembers();
+ if (!avatars)
+ return;
+
+ std::vector<Avatar*>::iterator ia = avatars->begin();
+ while (ia != avatars->end())
+ {
+ delete *ia;
+ ++ ia;
+ }
+ avatars->clear();
+
+ const StringVect *const players
+ = player_relations.getPlayersByRelation(PlayerRelation::FRIEND);
+
+ const std::set<std::string> &players2 = whoIsOnline->getOnlineNicks();
+
+ if (!players)
+ return;
+
+ int online = 0;
+ int total = 0;
+
+ FOR_EACHP (StringVectCIter, it, players)
+ {
+ Avatar *const ava = new Avatar(*it);
+ if (actorSpriteManager->findBeingByName(*it, Being::PLAYER)
+ || players2.find(*it) != players2.end())
+ {
+ ava->setOnline(true);
+ online ++;
+ }
+ total ++;
+ avatars->push_back(ava);
+ }
+ std::sort(avatars->begin(), avatars->end(), friendSorter);
+ delete players;
+
+ // TRANSLATORS: social window label
+ mCounterString = strprintf(_("Friends: %u/%u"), online, total);
+ updateCounter();
+ }
+
+private:
+ BeingsListModal *mBeings;
+};
+
+
+class CreatePopup final : public Popup, public LinkHandler
+{
+public:
+ CreatePopup() :
+ Popup("SocialCreatePopup"),
+ LinkHandler(),
+ mBrowserBox(new BrowserBox(this))
+ {
+ mBrowserBox->setPosition(4, 4);
+ mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
+ mBrowserBox->setOpaque(false);
+ mBrowserBox->setLinkHandler(this);
+
+ // TRANSLATORS: party popup item
+ mBrowserBox->addRow(strprintf("@@party|%s@@", _("Create Party")));
+ mBrowserBox->addRow("##3---");
+ // TRANSLATORS: party popup item
+ mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));
+
+ add(mBrowserBox);
+
+ setContentSize(mBrowserBox->getWidth() + 8,
+ mBrowserBox->getHeight() + 8);
+ }
+
+ A_DELETE_COPY(CreatePopup)
+
+ void handleLink(const std::string &link,
+ gcn::MouseEvent *event A_UNUSED) override
+ {
+ 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() :
+ // TRANSLATORS: social window name
+ Window(_("Social"), false, nullptr, "social.xml"),
+ gcn::ActionListener(),
+ mGuildInvited(0),
+ mGuildAcceptDialog(nullptr),
+ mGuildCreateDialog(nullptr),
+ mPartyInviter(),
+ mPartyAcceptDialog(nullptr),
+ mPartyCreateDialog(nullptr),
+ mGuilds(),
+ mParties(),
+ mAttackFilter(nullptr),
+ mPickupFilter(nullptr),
+ // TRANSLATORS: here P is title for visible players tab in social window
+ mPlayers(new SocialPlayersTab(this, _("P"),
+ getOptionBool("showtabbackground"))),
+ mNavigation(new SocialNavigationTab(this,
+ getOptionBool("showtabbackground"))),
+ // TRANSLATORS: here F is title for friends tab in social window
+ mFriends(new SocialFriendsTab(this, _("F"),
+ getOptionBool("showtabbackground"))),
+ mCreatePopup(new CreatePopup),
+ // TRANSLATORS: social window button
+ mCreateButton(new Button(this, _("Create"), "create", this)),
+ // TRANSLATORS: social window button
+ mInviteButton(new Button(this, _("Invite"), "invite", this)),
+ // TRANSLATORS: social window button
+ mLeaveButton(new Button(this, _("Leave"), "leave", this)),
+ mCountLabel(new Label(this, "1000 / 1000")),
+ mTabs(new TabbedArea(this)),
+ mMap(nullptr),
+ mLastUpdateTime(0),
+ mNeedUpdate(false),
+ mProcessedPortals(false)
+{
+ setWindowName("Social");
+ setVisible(false);
+ setSaveVisible(true);
+ setResizable(true);
+ setSaveVisible(true);
+ setCloseButton(true);
+ setStickyButtonLock(true);
+
+ setMinWidth(120);
+ setMinHeight(55);
+ setDefaultSize(590, 200, 180, 300);
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ place(0, 0, mCreateButton);
+ place(1, 0, mInviteButton);
+ place(2, 0, mLeaveButton);
+ place(0, 1, mCountLabel);
+ place(0, 2, mTabs, 4, 4);
+
+ widgetResized(gcn::Event(nullptr));
+
+ loadWindowState();
+
+ mTabs->addTab(mPlayers, mPlayers->mScroll);
+ mTabs->addTab(mFriends, mFriends->mScroll);
+ mTabs->addTab(mNavigation, mNavigation->mScroll);
+
+ if (config.getBoolValue("enableAttackFilter"))
+ {
+ mAttackFilter = new SocialAttackTab(this,
+ getOptionBool("showtabbackground"));
+ mTabs->addTab(mAttackFilter, mAttackFilter->mScroll);
+ }
+ else
+ {
+ mAttackFilter = nullptr;
+ }
+
+ if (config.getBoolValue("enablePickupFilter"))
+ {
+ mPickupFilter = new SocialPickupTab(this,
+ getOptionBool("showtabbackground"));
+ mTabs->addTab(mPickupFilter, mPickupFilter->mScroll);
+ }
+ else
+ {
+ mPickupFilter = nullptr;
+ }
+
+ if (player_node && player_node->getParty())
+ addTab(player_node->getParty());
+
+ if (player_node && player_node->getGuild())
+ addTab(player_node->getGuild());
+
+ enableVisibleSound(true);
+ updateButtons();
+}
+
+SocialWindow::~SocialWindow()
+{
+ if (mGuildAcceptDialog)
+ {
+ mGuildAcceptDialog->close();
+ mGuildAcceptDialog->scheduleDelete();
+ mGuildAcceptDialog = nullptr;
+
+ mGuildInvited = 0;
+ }
+
+ if (mPartyAcceptDialog)
+ {
+ mPartyAcceptDialog->close();
+ mPartyAcceptDialog->scheduleDelete();
+ mPartyAcceptDialog = nullptr;
+
+ mPartyInviter.clear();
+ }
+ delete mCreatePopup;
+ mCreatePopup = nullptr;
+ delete mPlayers;
+ mPlayers = nullptr;
+ delete mNavigation;
+ mNavigation = nullptr;
+ delete mAttackFilter;
+ mAttackFilter = nullptr;
+ delete mPickupFilter;
+ mPickupFilter = nullptr;
+ delete mFriends;
+ mFriends = nullptr;
+}
+
+bool SocialWindow::addTab(Guild *const guild)
+{
+ if (mGuilds.find(guild) != mGuilds.end())
+ return false;
+
+ SocialTab *tab = nullptr;
+ if (guild->getServerGuild())
+ {
+ tab = new SocialGuildTab(this, guild,
+ getOptionBool("showtabbackground"));
+ }
+ else
+ {
+ tab = new SocialGuildTab2(this, guild,
+ getOptionBool("showtabbackground"));
+ }
+
+ mGuilds[guild] = tab;
+ mTabs->addTab(tab, tab->mScroll);
+
+ updateButtons();
+
+ return true;
+}
+
+bool SocialWindow::removeTab(Guild *const guild)
+{
+ const 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 *const party)
+{
+ if (mParties.find(party) != mParties.end())
+ return false;
+
+ SocialPartyTab *const tab = new SocialPartyTab(this, party,
+ getOptionBool("showtabbackground"));
+ mParties[party] = tab;
+
+ mTabs->addTab(tab, tab->mScroll);
+
+ updateButtons();
+
+ return true;
+}
+
+bool SocialWindow::removeTab(Party *const party)
+{
+ const 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)
+ {
+ if (eventId == "yes")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ // TRANSLATORS: chat message
+ strprintf(_("Accepted party invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ Net::getPartyHandler()->inviteResponse(mPartyInviter, true);
+ }
+ else if (eventId == "no")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ // TRANSLATORS: chat message
+ strprintf(_("Rejected party invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ Net::getPartyHandler()->inviteResponse(mPartyInviter, false);
+ }
+
+ mPartyInviter.clear();
+ mPartyAcceptDialog = nullptr;
+ }
+ else if (event.getSource() == mGuildAcceptDialog)
+ {
+ if (eventId == "yes")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ // TRANSLATORS: chat message
+ strprintf(_("Accepted guild invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ if (!guildManager || !GuildManager::getEnableGuildBot())
+ Net::getGuildHandler()->inviteResponse(mGuildInvited, true);
+ else
+ guildManager->inviteResponse(true);
+ }
+ else if (eventId == "no")
+ {
+ if (localChatTab)
+ {
+ localChatTab->chatLog(
+ // TRANSLATORS: chat message
+ strprintf(_("Rejected guild invite from %s."),
+ mPartyInviter.c_str()));
+ }
+ if (!guildManager || !GuildManager::getEnableGuildBot())
+ Net::getGuildHandler()->inviteResponse(mGuildInvited, false);
+ else
+ guildManager->inviteResponse(false);
+ }
+
+ mGuildInvited = 0;
+ mGuildAcceptDialog = nullptr;
+ }
+ else if (eventId == "create")
+ {
+ showPartyCreate();
+ }
+ else if (eventId == "invite" && mTabs->getSelectedTabIndex() > -1)
+ {
+ if (mTabs->getSelectedTab())
+ static_cast<SocialTab*>(mTabs->getSelectedTab())->invite();
+ }
+ else if (eventId == "leave" && mTabs->getSelectedTabIndex() > -1)
+ {
+ if (mTabs->getSelectedTab())
+ static_cast<SocialTab*>(mTabs->getSelectedTab())->leave();
+ }
+ else if (eventId == "create guild")
+ {
+ if (tmwServerVersion > 0)
+ return;
+
+ std::string name = mGuildCreateDialog->getText();
+
+ if (name.size() > 16)
+ return;
+
+ Net::getGuildHandler()->create(name);
+ if (localChatTab)
+ {
+ // TRANSLATORS: chat message
+ localChatTab->chatLog(strprintf(_("Creating guild called %s."),
+ name.c_str()), BY_SERVER);
+ }
+
+ mGuildCreateDialog = nullptr;
+ }
+ else if (eventId == "~create guild")
+ {
+ mGuildCreateDialog = nullptr;
+ }
+ else if (eventId == "create party")
+ {
+ std::string name = mPartyCreateDialog->getText();
+
+ if (name.size() > 16)
+ return;
+
+ Net::getPartyHandler()->create(name);
+ if (localChatTab)
+ {
+ // TRANSLATORS: chat message
+ localChatTab->chatLog(strprintf(_("Creating party called %s."),
+ name.c_str()), BY_SERVER);
+ }
+
+ mPartyCreateDialog = nullptr;
+ }
+ else if (eventId == "~create party")
+ {
+ mPartyCreateDialog = nullptr;
+ }
+}
+
+void SocialWindow::showGuildCreate()
+{
+ // TRANSLATORS: guild creation message
+ mGuildCreateDialog = new TextDialog(_("Guild Name"),
+ // TRANSLATORS: guild creation message
+ _("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)
+ {
+ // TRANSLATORS: chat message
+ localChatTab->chatLog(_("Received guild request, but one already "
+ "exists."), BY_SERVER);
+ }
+ return;
+ }
+
+ const std::string msg = strprintf(
+ // TRANSLATORS: chat message
+ _("%s has invited you to join the guild %s."),
+ inviterName.c_str(), guildName.c_str());
+
+ if (localChatTab)
+ localChatTab->chatLog(msg, BY_SERVER);
+
+ // TRANSLATORS: guild invite message
+ mGuildAcceptDialog = new ConfirmDialog(_("Accept Guild Invite"),
+ msg, SOUND_REQUEST, 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.empty())
+ {
+ if (localChatTab)
+ {
+ // TRANSLATORS: chat message
+ localChatTab->chatLog(_("Received party request, but one already "
+ "exists."), BY_SERVER);
+ }
+ return;
+ }
+
+ std::string msg;
+ if (inviter.empty())
+ {
+ if (partyName.empty())
+ {
+ // TRANSLATORS: party invite message
+ msg = _("You have been invited you to join a party.");
+ }
+ else
+ {
+ // TRANSLATORS: party invite message
+ msg = strprintf(_("You have been invited to join the %s party."),
+ partyName.c_str());
+ }
+ }
+ else
+ {
+ if (partyName.empty())
+ {
+ // TRANSLATORS: party invite message
+ msg = strprintf(_("%s has invited you to join their party."),
+ inviter.c_str());
+ }
+ else
+ {
+ // TRANSLATORS: party invite message
+ 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
+ // TRANSLATORS: party invite message
+ mPartyAcceptDialog = new ConfirmDialog(_("Accept Party Invite"),
+ msg, SOUND_REQUEST, false, false, this);
+ mPartyAcceptDialog->addActionListener(this);
+ mPartyInviter = inviter;
+}
+
+void SocialWindow::showPartyCreate()
+{
+ if (!player_node)
+ return;
+
+ if (player_node->getParty())
+ {
+ // TRANSLATORS: party creation message
+ new OkDialog(_("Create Party"),
+ _("Cannot create party. You are already in a party"),
+ DIALOG_ERROR, true, true, this);
+ return;
+ }
+
+ // TRANSLATORS: party creation message
+ mPartyCreateDialog = new TextDialog(_("Party Name"),
+ // TRANSLATORS: party creation message
+ _("Choose your party's name."), this);
+ mPartyCreateDialog->setActionEventId("create party");
+ mPartyCreateDialog->addActionListener(this);
+}
+
+void SocialWindow::updateActiveList()
+{
+ mNeedUpdate = true;
+}
+
+void SocialWindow::slowLogic()
+{
+ BLOCK_START("SocialWindow::slowLogic")
+ const unsigned int nowTime = cur_time;
+ if (mNeedUpdate && nowTime - mLastUpdateTime > 1)
+ {
+ mPlayers->updateList();
+ mFriends->updateList();
+ mNeedUpdate = false;
+ mLastUpdateTime = nowTime;
+ }
+ else if (nowTime - mLastUpdateTime > 5)
+ {
+ mPlayers->updateList();
+ mNeedUpdate = false;
+ mLastUpdateTime = nowTime;
+ }
+ BLOCK_END("SocialWindow::slowLogic")
+}
+
+void SocialWindow::updateAvatar(const std::string &name)
+{
+ mPlayers->updateAvatar(name);
+}
+
+void SocialWindow::resetDamage(const std::string &name)
+{
+ mPlayers->resetDamage(name);
+}
+
+void SocialWindow::updateButtons()
+{
+ if (!mTabs)
+ return;
+
+ const 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<SocialNavigationTab*>(mNavigation)->updateNames();
+}
+
+void SocialWindow::selectPortal(const unsigned num)
+{
+ if (mNavigation)
+ mNavigation->selectIndex(num);
+}
+
+int SocialWindow::getPortalIndex(const int x, const int y)
+{
+ if (mNavigation)
+ {
+ return static_cast<SocialNavigationTab*>(
+ mNavigation)->getPortalIndex(x, y);
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+void SocialWindow::addPortal(const int x, const int y)
+{
+ if (mNavigation)
+ static_cast<SocialNavigationTab*>(mNavigation)->addPortal(x, y);
+}
+
+void SocialWindow::removePortal(const int x, const int y)
+{
+ if (mNavigation)
+ static_cast<SocialNavigationTab*>(mNavigation)->removePortal(x, y);
+}
+
+void SocialWindow::nextTab()
+{
+ if (!mTabs)
+ return;
+
+ int tab = mTabs->getSelectedTabIndex();
+
+ tab++;
+ if (tab == mTabs->getNumberOfTabs())
+ tab = 0;
+
+ mTabs->setSelectedTabByPos(tab);
+}
+
+void SocialWindow::prevTab()
+{
+ if (!mTabs)
+ return;
+
+ int tab = mTabs->getSelectedTabIndex();
+
+ if (tab == 0)
+ tab = mTabs->getNumberOfTabs();
+ tab--;
+
+ mTabs->setSelectedTabByPos(tab);
+}
+
+void SocialWindow::updateAttackFilter()
+{
+ if (mAttackFilter)
+ mAttackFilter->updateList();
+}
+
+void SocialWindow::updatePickupFilter()
+{
+ if (mPickupFilter)
+ mPickupFilter->updateList();
+}
+
+void SocialWindow::updateParty()
+{
+ if (!player_node)
+ return;
+
+ Party *const party = player_node->getParty();
+ if (party)
+ {
+ PartyMap::iterator it = mParties.find(party);
+ if (it != mParties.end())
+ {
+ SocialTab *const tab = (*it).second;
+ tab->buildCounter();
+ }
+ }
+}
+
+void SocialWindow::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+ if (mTabs)
+ mTabs->adjustSize();
+}
+
+void SocialWindow::setCounter(const SocialTab *const tab,
+ const std::string &str)
+{
+ if (mTabs->getSelectedTab() == tab)
+ {
+ mCountLabel->setCaption(str);
+ mCountLabel->adjustSize();
+ }
+}
+
+void SocialWindow::updateGuildCounter(const int online, const int total)
+{
+ if (!player_node)
+ return;
+
+ Guild *const guild = player_node->getGuild();
+ if (guild)
+ {
+ GuildMap::iterator it = mGuilds.find(guild);
+ if (it != mGuilds.end())
+ {
+ SocialTab *const tab = (*it).second;
+ tab->buildCounter(online, total);
+ }
+ }
+}
+
+#ifdef USE_PROFILER
+void SocialWindow::logicChildren()
+{
+ BLOCK_START("SocialWindow::logicChildren")
+ BasicContainer::logicChildren();
+ BLOCK_END("SocialWindow::logicChildren")
+}
+#endif
diff --git a/src/gui/windows/socialwindow.h b/src/gui/windows/socialwindow.h
new file mode 100644
index 000000000..31d1b676a
--- /dev/null
+++ b/src/gui/windows/socialwindow.h
@@ -0,0 +1,170 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_SOCIALWINDOW_H
+#define GUI_SOCIALWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <string>
+#include <map>
+
+class Button;
+class ConfirmDialog;
+class CreatePopup;
+class Guild;
+class Label;
+class Map;
+class Party;
+class SocialTab;
+class TabbedArea;
+class TextDialog;
+
+/**
+ * Party window.
+ *
+ * \ingroup Interface
+ */
+class SocialWindow final : public Window, private gcn::ActionListener
+{
+public:
+ SocialWindow();
+
+ A_DELETE_COPY(SocialWindow)
+
+ ~SocialWindow();
+
+ bool addTab(Guild *const guild);
+
+ bool removeTab(Guild *const guild);
+
+ bool addTab(Party *const party);
+
+ bool removeTab(Party *const party);
+
+ void action(const gcn::ActionEvent &event) override;
+
+ 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(const std::string &name);
+
+ void resetDamage(const std::string &name);
+
+ void slowLogic();
+
+ void updatePortals();
+
+ void updatePortalNames();
+
+ void updateParty();
+
+ int getPortalIndex(const int x, const int y) A_WARN_UNUSED;
+
+ void addPortal(const int x, const int y);
+
+ void removePortal(const int x, const int y);
+
+ void nextTab();
+
+ void prevTab();
+
+ const Map* getMap() const A_WARN_UNUSED
+ { return mMap; }
+
+ void setMap(Map *const map)
+ { mMap = map; mProcessedPortals = false; }
+
+ bool getProcessedPortals() const A_WARN_UNUSED
+ { return mProcessedPortals; }
+
+ void setProcessedPortals(const bool n)
+ { mProcessedPortals = n; }
+
+ void selectPortal(const unsigned num);
+
+ void updateAttackFilter();
+
+ void updatePickupFilter();
+
+ void widgetResized(const gcn::Event &event) override;
+
+ void setCounter(const SocialTab *const tab, const std::string &str);
+
+ void updateGuildCounter(const int online = 0, const int total = 0);
+
+#ifdef USE_PROFILER
+ void logicChildren();
+#endif
+
+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 *mAttackFilter;
+ SocialTab *mPickupFilter;
+ SocialTab *mPlayers;
+ SocialTab *mNavigation;
+ SocialTab *mFriends;
+
+ CreatePopup *mCreatePopup;
+
+ Button *mCreateButton;
+ Button *mInviteButton;
+ Button *mLeaveButton;
+ Label *mCountLabel;
+ TabbedArea *mTabs;
+ Map *mMap;
+
+ int mLastUpdateTime;
+ bool mNeedUpdate;
+ bool mProcessedPortals;
+};
+
+extern SocialWindow *socialWindow;
+
+#endif // GUI_SOCIALWINDOW_H
diff --git a/src/gui/windows/statuswindow.cpp b/src/gui/windows/statuswindow.cpp
new file mode 100644
index 000000000..bbc4558b0
--- /dev/null
+++ b/src/gui/windows/statuswindow.cpp
@@ -0,0 +1,888 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/statuswindow.h"
+
+#include "configuration.h"
+#include "equipment.h"
+#include "inventory.h"
+#include "item.h"
+#include "units.h"
+
+#include "gui/windows/chatwindow.h"
+
+#include "being/localplayer.h"
+#include "being/playerinfo.h"
+
+#include "gui/viewport.h"
+
+#include "gui/windows/equipmentwindow.h"
+#include "gui/windows/setup.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 "net/net.h"
+#include "net/playerhandler.h"
+#include "net/gamehandler.h"
+
+#include "utils/gettext.h"
+
+#include <SDL_timer.h>
+
+#include "debug.h"
+
+class AttrDisplay : public Container
+{
+ public:
+ enum Type
+ {
+ DERIVED = 0,
+ CHANGEABLE,
+ UNKNOWN
+ };
+
+ A_DELETE_COPY(AttrDisplay)
+
+ virtual ~AttrDisplay();
+
+ virtual std::string update();
+
+ virtual Type getType() const
+ { return UNKNOWN; }
+
+ std::string getValue() const
+ {
+ if (!mValue)
+ return "-";
+ else
+ return mValue->getCaption();
+ }
+
+ const std::string &getShortName() const
+ { return mShortName; }
+
+ protected:
+ AttrDisplay(const Widget2 *const widget,
+ const int id, const std::string &name,
+ const std::string &shortName);
+
+ const int mId;
+ const std::string mName;
+ const std::string mShortName;
+
+ LayoutHelper *mLayout;
+ Label *mLabel;
+ Label *mValue;
+};
+
+class DerDisplay final : public AttrDisplay
+{
+ public:
+ DerDisplay(const Widget2 *const widget,
+ const int id, const std::string &name,
+ const std::string &shortName);
+
+ A_DELETE_COPY(DerDisplay)
+
+ Type getType() const override
+ { return DERIVED; }
+};
+
+class ChangeDisplay final : public AttrDisplay, gcn::ActionListener
+{
+ public:
+ ChangeDisplay(const Widget2 *const widget,
+ const int id, const std::string &name,
+ const std::string &shortName);
+
+ A_DELETE_COPY(ChangeDisplay)
+
+ std::string update() override;
+
+ Type getType() const override
+ { return CHANGEABLE; }
+
+ void setPointsNeeded(int needed);
+
+ void action(const gcn::ActionEvent &event) override;
+
+ private:
+ int mNeeded;
+
+ Label *mPoints;
+ Button *mDec;
+ Button *mInc;
+};
+
+StatusWindow::StatusWindow() :
+ Window(player_node ? player_node->getName() :
+ "?", false, nullptr, "status.xml"),
+ gcn::ActionListener(),
+ // TRANSLATORS: status window label
+ mLvlLabel(new Label(this, strprintf(_("Level: %d"), 0))),
+ // TRANSLATORS: status window label
+ mMoneyLabel(new Label(this, strprintf(_("Money: %s"), ""))),
+ // TRANSLATORS: status window label
+ mHpLabel(new Label(this, _("HP:"))),
+ mMpLabel(nullptr),
+ // TRANSLATORS: status window label
+ mXpLabel(new Label(this, _("Exp:"))),
+ mHpBar(nullptr),
+ mMpBar(nullptr),
+ mXpBar(nullptr),
+ mJobLvlLabel(nullptr),
+ mJobLabel(nullptr),
+ mJobBar(nullptr),
+ mAttrCont(new VertContainer(this, 32)),
+ mAttrScroll(new ScrollArea(mAttrCont, false)),
+ mDAttrCont(new VertContainer(this, 32)),
+ mDAttrScroll(new ScrollArea(mDAttrCont, false)),
+ mCharacterPointsLabel(new Label(this, "C")),
+ mCorrectionPointsLabel(nullptr),
+ // TRANSLATORS: status window button
+ mCopyButton(new Button(this, _("Copy to chat"), "copy", this)),
+ mAttrs()
+{
+ listen(CHANNEL_ATTRIBUTES);
+
+ setWindowName("Status");
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+ setResizable(true);
+ setCloseButton(true);
+ setSaveVisible(true);
+ setStickyButtonLock(true);
+ setDefaultSize((windowContainer->getWidth() - 480) / 2,
+ (windowContainer->getHeight() - 500) / 2, 480, 500);
+
+ if (player_node && !player_node->getRaceName().empty())
+ {
+ setCaption(strprintf("%s (%s)", player_node->getName().c_str(),
+ player_node->getRaceName().c_str()));
+ }
+
+ int max = PlayerInfo::getAttribute(PlayerInfo::MAX_HP);
+ if (!max)
+ max = 1;
+
+ mHpBar = new ProgressBar(this, static_cast<float>(PlayerInfo::getAttribute(
+ PlayerInfo::HP)) / static_cast<float>(max), 80, 0, Theme::PROG_HP);
+
+ max = PlayerInfo::getAttribute(PlayerInfo::EXP_NEEDED);
+ mXpBar = new ProgressBar(this, max ?
+ static_cast<float>(PlayerInfo::getAttribute(PlayerInfo::EXP))
+ / static_cast<float>(max):
+ static_cast<float>(0), 80, 0, Theme::PROG_EXP);
+
+ const bool magicBar = Net::getGameHandler()->canUseMagicBar();
+ const int job = Net::getPlayerHandler()->getJobLocation()
+ && serverConfig.getValueBool("showJob", true);
+
+ if (magicBar)
+ {
+ max = PlayerInfo::getAttribute(PlayerInfo::MAX_MP);
+ // TRANSLATORS: status window label
+ mMpLabel = new Label(this, _("MP:"));
+ mMpBar = new ProgressBar(this, max ? static_cast<float>(
+ PlayerInfo::getAttribute(PlayerInfo::MAX_MP))
+ / static_cast<float>(max) : static_cast<float>(0),
+ 80, 0, Net::getPlayerHandler()->canUseMagic() ?
+ Theme::PROG_MP : Theme::PROG_NO_MP);
+ }
+ else
+ {
+ mMpLabel = nullptr;
+ mMpBar = nullptr;
+ }
+
+ place(0, 0, mLvlLabel, 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)
+ {
+ // TRANSLATORS: status window label
+ mJobLvlLabel = new Label(this, strprintf(_("Job: %d"), 0));
+ // TRANSLATORS: status window label
+ mJobLabel = new Label(this, _("Job:"));
+ mJobBar = new ProgressBar(this, 0.0F, 80, 0, Theme::PROG_JOB);
+
+ place(3, 0, mJobLvlLabel, 3);
+ place(5, 2, mJobLabel).setPadding(3);
+ place(6, 2, mJobBar, 5);
+ place(6, 0, mMoneyLabel, 3);
+ }
+ else
+ {
+ mJobLvlLabel = nullptr;
+ mJobLabel = nullptr;
+ mJobBar = nullptr;
+ place(3, 0, mMoneyLabel, 3);
+ }
+
+ // ----------------------
+ // Stats Part
+ // ----------------------
+
+ mAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ mAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO);
+ place(0, 3, mAttrScroll, 5, 3);
+
+ mDAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+ mDAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO);
+ place(6, 3, mDAttrScroll, 5, 3);
+
+ getLayout().setRowHeight(3, Layout::AUTO_SET);
+
+ place(0, 6, mCharacterPointsLabel, 5);
+ place(0, 5, mCopyButton);
+
+ if (Net::getPlayerHandler()->canCorrectAttributes())
+ {
+ mCorrectionPointsLabel = new Label(this, "C");
+ place(0, 7, mCorrectionPointsLabel, 5);
+ }
+
+ loadWindowState();
+ enableVisibleSound(true);
+
+ // Update bars
+ updateHPBar(mHpBar, true);
+ if (magicBar)
+ updateMPBar(mMpBar, true);
+ updateXPBar(mXpBar, false);
+
+ // TRANSLATORS: status window label
+ mMoneyLabel->setCaption(strprintf(_("Money: %s"), Units::formatCurrency(
+ PlayerInfo::getAttribute(PlayerInfo::MONEY)).c_str()));
+ mMoneyLabel->adjustSize();
+ // TRANSLATORS: status window label
+ mCharacterPointsLabel->setCaption(strprintf(_("Character points: %d"),
+ PlayerInfo::getAttribute(PlayerInfo::CHAR_POINTS)));
+ mCharacterPointsLabel->adjustSize();
+
+ if (player_node && player_node->isGM())
+ {
+ // TRANSLATORS: status window label
+ mLvlLabel->setCaption(strprintf(_("Level: %d (GM %d)"),
+ PlayerInfo::getAttribute(PlayerInfo::LEVEL),
+ player_node->getGMLevel()));
+ }
+ else
+ {
+ // TRANSLATORS: status window label
+ mLvlLabel->setCaption(strprintf(_("Level: %d"),
+ PlayerInfo::getAttribute(PlayerInfo::LEVEL)));
+ }
+ mLvlLabel->adjustSize();
+}
+
+void StatusWindow::processEvent(const Channels channel A_UNUSED,
+ const DepricatedEvent &event)
+{
+ static bool blocked = false;
+ if (blocked)
+ return;
+
+ const DepricatedEvents &eventName = event.getName();
+ if (eventName == EVENT_UPDATEATTRIBUTE)
+ {
+ switch (event.getInt("id"))
+ {
+ case PlayerInfo::HP:
+ case PlayerInfo::MAX_HP:
+ updateHPBar(mHpBar, true);
+ break;
+
+ case PlayerInfo::MP:
+ case PlayerInfo::MAX_MP:
+ updateMPBar(mMpBar, true);
+ break;
+
+ case PlayerInfo::EXP:
+ case PlayerInfo::EXP_NEEDED:
+ updateXPBar(mXpBar, false);
+ break;
+
+ case PlayerInfo::MONEY:
+ // TRANSLATORS: status window label
+ mMoneyLabel->setCaption(strprintf(_("Money: %s"),
+ Units::formatCurrency(event.getInt("newValue")).c_str()));
+ mMoneyLabel->adjustSize();
+ break;
+
+ case PlayerInfo::CHAR_POINTS:
+ mCharacterPointsLabel->setCaption(strprintf(
+ // TRANSLATORS: status window label
+ _("Character points: %d"), event.getInt("newValue")));
+
+ mCharacterPointsLabel->adjustSize();
+ // Update all attributes
+ for (Attrs::const_iterator it = mAttrs.begin();
+ it != mAttrs.end(); ++it)
+ {
+ if (it->second)
+ it->second->update();
+ }
+ break;
+
+ case PlayerInfo::CORR_POINTS:
+ mCorrectionPointsLabel->setCaption(strprintf(
+ // TRANSLATORS: status window label
+ _("Correction points: %d"), event.getInt("newValue")));
+ mCorrectionPointsLabel->adjustSize();
+ // Update all attributes
+ for (Attrs::const_iterator it = mAttrs.begin();
+ it != mAttrs.end(); ++it)
+ {
+ if (it->second)
+ it->second->update();
+ }
+ break;
+
+ case PlayerInfo::LEVEL:
+ // TRANSLATORS: status window label
+ mLvlLabel->setCaption(strprintf(_("Level: %d"),
+ event.getInt("newValue")));
+ mLvlLabel->adjustSize();
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (eventName == EVENT_UPDATESTAT)
+ {
+ const int id = event.getInt("id");
+ if (id == Net::getPlayerHandler()->getJobLocation())
+ {
+ if (mJobLvlLabel)
+ {
+ int lvl = PlayerInfo::getStatBase(id);
+ const int oldExp = event.getInt("oldValue1");
+ const std::pair<int, int> exp
+ = PlayerInfo::getStatExperience(id);
+
+ if (!lvl)
+ {
+ // possible server broken and don't send job level,
+ // then we fixing it :)
+ if (exp.second < 20000)
+ {
+ lvl = 0;
+ }
+ else
+ {
+ lvl = (exp.second - 20000) / 150;
+ blocked = true;
+ PlayerInfo::setStatBase(id, lvl);
+ blocked = false;
+ }
+ }
+
+ if (exp.first < oldExp && exp.second >= 20000)
+ { // possible job level up. but server broken and don't send
+ // new job exp limit, we fixing it
+ lvl ++;
+ blocked = true;
+ PlayerInfo::setStatExperience(
+ id, exp.first, 20000 + lvl * 150);
+ PlayerInfo::setStatBase(id, lvl);
+ blocked = false;
+ }
+
+ // TRANSLATORS: status window label
+ mJobLvlLabel->setCaption(strprintf(_("Job: %d"), lvl));
+ mJobLvlLabel->adjustSize();
+
+ updateProgressBar(mJobBar, id, false);
+ }
+ }
+ else
+ {
+ updateMPBar(mMpBar, true);
+ const Attrs::const_iterator it = mAttrs.find(id);
+ if (it != mAttrs.end() && it->second)
+ it->second->update();
+ }
+ }
+}
+
+void StatusWindow::setPointsNeeded(const int id, const int needed)
+{
+ const Attrs::const_iterator it = mAttrs.find(id);
+
+ if (it != mAttrs.end())
+ {
+ AttrDisplay *const disp = it->second;
+ if (disp && disp->getType() == AttrDisplay::CHANGEABLE)
+ static_cast<ChangeDisplay*>(disp)->setPointsNeeded(needed);
+ }
+}
+
+void StatusWindow::addAttribute(const int id, const std::string &name,
+ const std::string &shortName,
+ const bool modifiable,
+ const std::string &description A_UNUSED)
+{
+ AttrDisplay *disp;
+
+ if (modifiable)
+ {
+ disp = new ChangeDisplay(this, id, name, shortName);
+ mAttrCont->add1(disp);
+ }
+ else
+ {
+ disp = new DerDisplay(this, id, name, shortName);
+ mDAttrCont->add1(disp);
+ }
+ mAttrs[id] = disp;
+}
+
+void StatusWindow::clearAttributes()
+{
+ mAttrCont->clear();
+ mDAttrCont->clear();
+ FOR_EACH (Attrs::iterator, it, mAttrs)
+ delete (*it).second;
+ mAttrs.clear();
+}
+
+void StatusWindow::updateHPBar(ProgressBar *const bar, const bool showMax)
+{
+ if (!bar)
+ return;
+
+ const int hp = PlayerInfo::getAttribute(PlayerInfo::HP);
+ const int maxHp = PlayerInfo::getAttribute(PlayerInfo::MAX_HP);
+ if (showMax)
+ bar->setText(toString(hp).append("/").append(toString(maxHp)));
+ else
+ bar->setText(toString(hp));
+
+ float prog = 1.0;
+ if (maxHp > 0)
+ prog = static_cast<float>(hp) / static_cast<float>(maxHp);
+ bar->setProgress(prog);
+}
+
+void StatusWindow::updateMPBar(ProgressBar *const bar, const bool showMax)
+{
+ if (!bar)
+ return;
+
+ const int mp = PlayerInfo::getAttribute(PlayerInfo::MP);
+ const int maxMp = PlayerInfo::getAttribute(PlayerInfo::MAX_MP);
+ if (showMax)
+ bar->setText(toString(mp).append("/").append(toString(maxMp)));
+ else
+ bar->setText(toString(mp));
+
+ float prog = 1.0F;
+ if (maxMp > 0)
+ prog = static_cast<float>(mp) / static_cast<float>(maxMp);
+
+ if (Net::getPlayerHandler()->canUseMagic())
+ bar->setProgressPalette(Theme::PROG_MP);
+ else
+ bar->setProgressPalette(Theme::PROG_NO_MP);
+
+ bar->setProgress(prog);
+}
+
+void StatusWindow::updateProgressBar(ProgressBar *const bar, const int value,
+ const int max, const bool percent)
+{
+ if (!bar)
+ return;
+
+ if (max == 0)
+ {
+ // TRANSLATORS: status bar label
+ bar->setText(_("Max"));
+ bar->setProgress(1);
+ bar->setText(toString(value));
+ }
+ else
+ {
+ const float progress = static_cast<float>(value)
+ / static_cast<float>(max);
+ if (percent)
+ {
+ bar->setText(strprintf("%2.5f%%",
+ static_cast<double>(100 * progress)));
+ }
+ else
+ {
+ bar->setText(toString(value).append("/").append(toString(max)));
+ }
+ bar->setProgress(progress);
+ }
+}
+
+void StatusWindow::updateXPBar(ProgressBar *const bar, const bool percent)
+{
+ if (!bar)
+ return;
+
+ updateProgressBar(bar, PlayerInfo::getAttribute(PlayerInfo::EXP),
+ PlayerInfo::getAttribute(PlayerInfo::EXP_NEEDED), percent);
+}
+
+void StatusWindow::updateJobBar(ProgressBar *const bar, const bool percent)
+{
+ if (!bar)
+ return;
+
+ const std::pair<int, int> exp = PlayerInfo::getStatExperience(
+ Net::getPlayerHandler()->getJobLocation());
+ updateProgressBar(bar, exp.first, exp.second, percent);
+}
+
+void StatusWindow::updateProgressBar(ProgressBar *const bar, const int id,
+ const bool percent) const
+{
+ const std::pair<int, int> exp = PlayerInfo::getStatExperience(id);
+ updateProgressBar(bar, exp.first, exp.second, percent);
+}
+
+void StatusWindow::updateWeightBar(ProgressBar *const bar)
+{
+ if (!bar)
+ return;
+
+ if (PlayerInfo::getAttribute(PlayerInfo::MAX_WEIGHT) == 0)
+ {
+ // TRANSLATORS: status bar label
+ bar->setText(_("Max"));
+ bar->setProgress(1.0);
+ }
+ else
+ {
+ const int totalWeight = PlayerInfo::getAttribute(
+ PlayerInfo::TOTAL_WEIGHT);
+ const int maxWeight = PlayerInfo::getAttribute(PlayerInfo::MAX_WEIGHT);
+ const float progress = static_cast<float>(totalWeight)
+ / static_cast<float>(maxWeight);
+ bar->setText(strprintf("%s/%s", Units::formatWeight(
+ totalWeight).c_str(), Units::formatWeight(maxWeight).c_str()));
+ bar->setProgress(progress);
+ }
+}
+
+void StatusWindow::updateMoneyBar(ProgressBar *const bar)
+{
+ if (!bar)
+ return;
+
+ const int money = PlayerInfo::getAttribute(PlayerInfo::MONEY);
+ bar->setText(Units::formatCurrency(money).c_str());
+ if (money > 0)
+ {
+ const float progress = static_cast<float>(money)
+ / static_cast<float>(1000000000);
+ bar->setProgress(progress);
+ }
+ else
+ {
+ bar->setProgress(1.0);
+ }
+}
+
+void StatusWindow::updateArrowsBar(ProgressBar *const bar)
+{
+ if (!bar || !equipmentWindow)
+ return;
+
+ const Item *const item = equipmentWindow->getEquipment(
+ Equipment::EQUIP_PROJECTILE_SLOT);
+
+ if (item && item->getQuantity() > 0)
+ bar->setText(toString(item->getQuantity()));
+ else
+ bar->setText("0");
+}
+
+void StatusWindow::updateInvSlotsBar(ProgressBar *const bar)
+{
+ if (!bar)
+ return;
+
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ const int usedSlots = inv->getNumberOfSlotsUsed();
+ const int maxSlots = inv->getSize();
+
+ if (maxSlots)
+ {
+ bar->setProgress(static_cast<float>(usedSlots)
+ / static_cast<float>(maxSlots));
+ }
+
+ bar->setText(strprintf("%d", usedSlots));
+}
+
+std::string StatusWindow::translateLetter(const char *const letters)
+{
+ char buf[2];
+ char *const str = gettext(letters);
+ if (strlen(str) != 3)
+ return letters;
+
+ buf[0] = str[1];
+ buf[1] = 0;
+ return std::string(buf);
+}
+
+std::string StatusWindow::translateLetter2(std::string letters)
+{
+ if (letters.size() < 5)
+ return "";
+
+ return std::string(gettext(letters.substr(1, 1).c_str()));
+}
+
+void StatusWindow::updateStatusBar(ProgressBar *const bar,
+ const bool percent A_UNUSED)
+{
+ if (!player_node || !viewport)
+ return;
+
+ bar->setText(translateLetter2(player_node->getInvertDirectionString())
+ .append(translateLetter2(player_node->getCrazyMoveTypeString()))
+ .append(translateLetter2(player_node->getMoveToTargetTypeString()))
+ .append(translateLetter2(player_node->getFollowModeString()))
+ .append(" ").append(translateLetter2(
+ player_node->getAttackWeaponTypeString()))
+ .append(translateLetter2(player_node->getAttackTypeString()))
+ .append(translateLetter2(player_node->getMagicAttackString()))
+ .append(translateLetter2(player_node->getPvpAttackString()))
+ .append(" ").append(translateLetter2(
+ player_node->getQuickDropCounterString()))
+ .append(translateLetter2(player_node->getPickUpTypeString()))
+ .append(" ").append(translateLetter2(
+ player_node->getDebugPathString()))
+ .append(" ").append(translateLetter2(
+ player_node->getImitationModeString()))
+ .append(translateLetter2(player_node->getCameraModeString()))
+ .append(translateLetter2(player_node->getAwayModeString())));
+
+ bar->setProgress(50);
+ if (player_node->getDisableGameModifiers())
+ bar->setColor(Theme::getThemeColor(Theme::STATUSBAR_ON));
+ else
+ bar->setColor(Theme::getThemeColor(Theme::STATUSBAR_OFF));
+}
+
+void StatusWindow::action(const gcn::ActionEvent &event)
+{
+ if (!chatWindow)
+ return;
+
+ if (event.getId() == "copy")
+ {
+ Attrs::const_iterator it = mAttrs.begin();
+ const Attrs::const_iterator it_end = mAttrs.end();
+ std::string str;
+ while (it != it_end)
+ {
+ const ChangeDisplay *const attr = dynamic_cast<ChangeDisplay*>(
+ (*it).second);
+ if (attr)
+ {
+ str.append(strprintf("%s:%s ", attr->getShortName().c_str(),
+ attr->getValue().c_str()));
+ }
+ ++ it;
+ }
+ chatWindow->addInputText(str);
+ }
+}
+
+AttrDisplay::AttrDisplay(const Widget2 *const widget,
+ const int id, const std::string &name,
+ const std::string &shortName) :
+ Container(widget),
+ mId(id),
+ mName(name),
+ mShortName(shortName),
+ mLayout(new LayoutHelper(this)),
+ mLabel(new Label(this, name)),
+ mValue(new Label(this, "1 "))
+{
+ setSize(100, 32);
+
+ mLabel->setAlignment(Graphics::CENTER);
+ mValue->setAlignment(Graphics::CENTER);
+}
+
+AttrDisplay::~AttrDisplay()
+{
+ delete mLayout;
+ mLayout = nullptr;
+}
+
+std::string AttrDisplay::update()
+{
+ const int base = PlayerInfo::getStatBase(mId);
+ const int bonus = PlayerInfo::getStatMod(mId);
+ std::string value = toString(base + bonus);
+ if (bonus)
+ value.append(strprintf("=%d%+d", base, bonus));
+ mValue->setCaption(value);
+ return mName;
+}
+
+DerDisplay::DerDisplay(const Widget2 *const widget,
+ const int id, const std::string &name,
+ const std::string &shortName) :
+ AttrDisplay(widget, id, name, shortName)
+{
+ ContainerPlacer place = mLayout->getPlacer(0, 0);
+
+ place(0, 0, mLabel, 3);
+ place(3, 0, mValue, 2);
+
+ update();
+}
+
+ChangeDisplay::ChangeDisplay(const Widget2 *const widget,
+ const int id, const std::string &name,
+ const std::string &shortName) :
+ AttrDisplay(widget, id, name, shortName),
+ gcn::ActionListener(),
+ mNeeded(1),
+ // TRANSLATORS: status window label
+ mPoints(new Label(this, _("Max"))),
+ mDec(nullptr),
+ // TRANSLATORS: status window label (plus sign)
+ mInc(new Button(this, _("+"), "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())
+ {
+ // TRANSLATORS: status window label (minus sign)
+ mDec = new Button(this, _("-"), "dec", this);
+ mDec->setWidth(mInc->getWidth());
+
+ place(3, 0, mDec);
+ }
+
+ update();
+}
+
+std::string ChangeDisplay::update()
+{
+ if (mNeeded > 0)
+ {
+ mPoints->setCaption(toString(mNeeded));
+ }
+ else
+ {
+ // TRANSLATORS: status bar label
+ mPoints->setCaption(_("Max"));
+ }
+
+ if (mDec)
+ mDec->setEnabled(PlayerInfo::getAttribute(PlayerInfo::CORR_POINTS));
+
+ mInc->setEnabled(PlayerInfo::getAttribute(PlayerInfo::CHAR_POINTS)
+ >= mNeeded && mNeeded > 0);
+
+ return AttrDisplay::update();
+}
+
+void ChangeDisplay::setPointsNeeded(const int needed)
+{
+ mNeeded = needed;
+ update();
+}
+
+void ChangeDisplay::action(const gcn::ActionEvent &event)
+{
+ if (Net::getPlayerHandler()->canCorrectAttributes() &&
+ event.getSource() == mDec)
+ {
+ const int newcorrpoints = PlayerInfo::getAttribute(
+ PlayerInfo::CORR_POINTS);
+ PlayerInfo::setAttribute(PlayerInfo::CORR_POINTS, newcorrpoints - 1);
+
+ const int newpoints = PlayerInfo::getAttribute(
+ PlayerInfo::CHAR_POINTS) + 1;
+ PlayerInfo::setAttribute(PlayerInfo::CHAR_POINTS, newpoints);
+
+ const 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;
+ }
+
+ const int newpoints = PlayerInfo::getAttribute(
+ PlayerInfo::CHAR_POINTS) - cnt;
+ PlayerInfo::setAttribute(PlayerInfo::CHAR_POINTS, newpoints);
+
+ const 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/windows/statuswindow.h b/src/gui/windows/statuswindow.h
new file mode 100644
index 000000000..eebcd12ca
--- /dev/null
+++ b/src/gui/windows/statuswindow.h
@@ -0,0 +1,125 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_STATUSWINDOW_H
+#define GUI_STATUSWINDOW_H
+
+#include "depricatedlistener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <map>
+
+class AttrDisplay;
+class Button;
+class Label;
+class ProgressBar;
+class ScrollArea;
+class VertContainer;
+
+/**
+ * The player status dialog.
+ *
+ * \ingroup Interface
+ */
+class StatusWindow final : public Window,
+ public gcn::ActionListener,
+ public DepricatedListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ StatusWindow();
+
+ A_DELETE_COPY(StatusWindow)
+
+ void processEvent(const Channels channel,
+ const DepricatedEvent &event) override;
+
+ void setPointsNeeded(const int id, const int needed);
+
+ void addAttribute(const int id, const std::string &name,
+ const std::string &shortName = "",
+ const bool modifiable = false,
+ const std::string &description = "");
+
+ static void updateHPBar(ProgressBar *const bar,
+ const bool showMax = false);
+ static void updateMPBar(ProgressBar *bar, bool showMax = false);
+ static void updateJobBar(ProgressBar *const bar,
+ const bool percent = true);
+ static void updateXPBar(ProgressBar *const bar,
+ const bool percent = true);
+ static void updateWeightBar(ProgressBar *const bar);
+ static void updateInvSlotsBar(ProgressBar *const bar);
+ static void updateMoneyBar(ProgressBar *const bar);
+ static void updateArrowsBar(ProgressBar *const bar);
+ static void updateStatusBar(ProgressBar *const bar,
+ const bool percent = true);
+ static void updateProgressBar(ProgressBar *const bar, const int value,
+ const int max, const bool percent);
+ void updateProgressBar(ProgressBar *const bar, const int id,
+ const bool percent = true) const;
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void clearAttributes();
+
+ private:
+ static std::string translateLetter(const char *const letters);
+ static std::string translateLetter2(std::string letters);
+
+ /**
+ * Status Part
+ */
+ Label *mLvlLabel;
+ Label *mMoneyLabel;
+ Label *mHpLabel;
+ Label *mMpLabel;
+ Label *mXpLabel;
+ ProgressBar *mHpBar;
+ ProgressBar *mMpBar;
+ ProgressBar *mXpBar;
+
+ Label *mJobLvlLabel;
+ Label *mJobLabel;
+ ProgressBar *mJobBar;
+
+ VertContainer *mAttrCont;
+ ScrollArea *mAttrScroll;
+ VertContainer *mDAttrCont;
+ ScrollArea *mDAttrScroll;
+
+ Label *mCharacterPointsLabel;
+ Label *mCorrectionPointsLabel;
+ Button *mCopyButton;
+
+ typedef std::map<int, AttrDisplay*> Attrs;
+ Attrs mAttrs;
+};
+
+extern StatusWindow *statusWindow;
+
+#endif // GUI_STATUSWINDOW_H
diff --git a/src/gui/windows/textcommandeditor.cpp b/src/gui/windows/textcommandeditor.cpp
new file mode 100644
index 000000000..1b4fb3440
--- /dev/null
+++ b/src/gui/windows/textcommandeditor.cpp
@@ -0,0 +1,396 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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/windows/textcommandeditor.h"
+
+#include "main.h"
+#include "spellmanager.h"
+
+#include "input/keyboardconfig.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/dropdown.h"
+#include "gui/widgets/inttextfield.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/radiobutton.h"
+
+#include "utils/gettext.h"
+
+#include "resources/itemdb.h"
+#include "resources/iteminfo.h"
+
+#include "debug.h"
+
+class IconsModal final : public gcn::ListModel
+{
+public:
+ IconsModal() :
+ mStrings()
+ {
+ const std::map<int, ItemInfo*> &items = ItemDB::getItemInfos();
+ std::list<std::string> tempStrings;
+
+ for (std::map<int, ItemInfo*>::const_iterator
+ i = items.begin(), i_end = items.end();
+ i != i_end; ++i)
+ {
+ if (i->first < 0)
+ continue;
+
+ const ItemInfo &info = (*i->second);
+ const std::string name = info.getName();
+ if (name != "unnamed" && !info.getName().empty()
+ && info.getName() != "unnamed")
+ {
+ tempStrings.push_back(name);
+ }
+ }
+ tempStrings.sort();
+ mStrings.push_back("");
+ FOR_EACH (std::list<std::string>::const_iterator, i, tempStrings)
+ mStrings.push_back(*i);
+ }
+
+ A_DELETE_COPY(IconsModal)
+
+ ~IconsModal()
+ { }
+
+ int getNumberOfElements() override
+ {
+ return static_cast<int>(mStrings.size());
+ }
+
+ std::string getElementAt(int i) override
+ {
+ if (i < 0 || i >= getNumberOfElements())
+ return "???";
+ return mStrings.at(i);
+ }
+private:
+ StringVect mStrings;
+};
+
+
+const char *TARGET_TYPE_TEXT[3] =
+{
+ // TRANSLATORS: target type
+ N_("No Target"),
+ // TRANSLATORS: target type
+ N_("Allow Target"),
+ // TRANSLATORS: target type
+ N_("Need Target"),
+};
+
+const char *MAGIC_SCHOOL_TEXT[6] =
+{
+ // TRANSLATORS: magic school
+ N_("General Magic"),
+ // TRANSLATORS: magic school
+ N_("Life Magic"),
+ // TRANSLATORS: magic school
+ N_("War Magic"),
+ // TRANSLATORS: magic school
+ N_("Transmute Magic"),
+ // TRANSLATORS: magic school
+ N_("Nature Magic"),
+ // TRANSLATORS: magic school
+ N_("Astral Magic")
+};
+
+class TargetTypeModel final : public gcn::ListModel
+{
+public:
+ ~TargetTypeModel()
+ { }
+
+ int getNumberOfElements() override
+ {
+ return 3;
+ }
+
+ std::string getElementAt(int i) override
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "???";
+ return TARGET_TYPE_TEXT[i];
+ }
+};
+
+class MagicSchoolModel final : public gcn::ListModel
+{
+public:
+ ~MagicSchoolModel()
+ { }
+
+ int getNumberOfElements() override
+ {
+ return 6;
+ }
+
+ std::string getElementAt(int i) override
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "???";
+ return MAGIC_SCHOOL_TEXT[i];
+ }
+};
+
+TextCommandEditor::TextCommandEditor(TextCommand *const command) :
+ // TRANSLATORS: command editor name
+ Window(_("Command Editor"), false, nullptr, "commandeditor.xml"),
+ gcn::ActionListener(),
+ mIsMagicCommand(command->getCommandType() == TEXT_COMMAND_MAGIC),
+ mCommand(command),
+ // TRANSLATORS: command editor button
+ mIsMagic(new RadioButton(this, _("magic"), "magic", mIsMagicCommand)),
+ // TRANSLATORS: command editor button
+ mIsOther(new RadioButton(this, _("other"), "magic", !mIsMagicCommand)),
+ // TRANSLATORS: command editor label
+ mSymbolLabel(new Label(this, _("Symbol:"))),
+ mSymbolTextField(new TextField(this)),
+ // TRANSLATORS: command editor label
+ mCommandLabel(new Label(this, _("Command:"))),
+ mCommandTextField(new TextField(this)),
+ // TRANSLATORS: command editor label
+ mCommentLabel(new Label(this, _("Comment:"))),
+ mCommentTextField(new TextField(this)),
+ mTargetTypeModel(new TargetTypeModel),
+ // TRANSLATORS: command editor label
+ mTypeLabel(new Label(this, _("Target Type:"))),
+ mTypeDropDown(new DropDown(this, mTargetTypeModel)),
+ mIconsModal(new IconsModal),
+ // TRANSLATORS: command editor label
+ mIconLabel(new Label(this, _("Icon:"))),
+ mIconDropDown(new DropDown(this, mIconsModal)),
+ // TRANSLATORS: command editor label
+ mManaLabel(new Label(this, _("Mana:"))),
+ mManaField(new IntTextField(this, 0)),
+ // TRANSLATORS: command editor label
+ mMagicLvlLabel(new Label(this, _("Magic level:"))),
+ mMagicLvlField(new IntTextField(this, 0)),
+ mMagicSchoolModel(new MagicSchoolModel),
+ // TRANSLATORS: command editor label
+ mSchoolLabel(new Label(this, _("Magic School:"))),
+ mSchoolDropDown(new DropDown(this, mMagicSchoolModel)),
+ // TRANSLATORS: command editor label
+ mSchoolLvlLabel(new Label(this, _("School level:"))),
+ mSchoolLvlField(new IntTextField(this, 0)),
+ // TRANSLATORS: command editor button
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ // TRANSLATORS: command editor button
+ mSaveButton(new Button(this, _("Save"), "save", this)),
+ // TRANSLATORS: command editor button
+ mDeleteButton(new Button(this, _("Delete"), "delete", this)),
+ mEnabledKeyboard(keyboard.isEnabled())
+{
+ const int w = 350;
+ const int h = 370;
+
+ keyboard.setEnabled(false);
+
+ setWindowName("TextCommandEditor");
+ setDefaultSize(w, h, ImageRect::CENTER);
+
+ mIsMagic->setActionEventId("magic");
+ mIsMagic->addActionListener(this);
+
+ mIsOther->setActionEventId("other");
+ mIsOther->addActionListener(this);
+
+ mManaField->setRange(0, 500);
+ mManaField->setWidth(20);
+
+ mTypeDropDown->setActionEventId("type");
+ mTypeDropDown->addActionListener(this);
+
+ mIconDropDown->setActionEventId("icon");
+ mIconDropDown->addActionListener(this);
+ mIconDropDown->setSelectedString(mCommand->getIcon());
+
+ mMagicLvlField->setRange(0, 5);
+ mMagicLvlField->setWidth(20);
+
+ mSchoolDropDown->setActionEventId("school");
+ mSchoolDropDown->addActionListener(this);
+ mSchoolDropDown->setSelected(0);
+
+ mSchoolLvlField->setRange(0, 5);
+ mSchoolLvlField->setWidth(20);
+
+ mSaveButton->adjustSize();
+ mCancelButton->adjustSize();
+ mDeleteButton->adjustSize();
+
+ if (command->getCommandType() == TEXT_COMMAND_MAGIC)
+ showControls(true);
+ else
+ showControls(false);
+
+ mSymbolTextField->setText(command->getSymbol());
+ mCommandTextField->setText(command->getCommand());
+ mCommentTextField->setText(command->getComment());
+ mManaField->setValue(command->getMana());
+ mTypeDropDown->setSelected(command->getTargetType());
+ mMagicLvlField->setValue(command->getBaseLvl());
+ mSchoolDropDown->setSelected(command->getSchool() - MAGIC_START_ID);
+ mSchoolLvlField->setValue(command->getSchoolLvl());
+
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+
+ placer(0, 0, mIsMagic, 1);
+ placer(2, 0, mIsOther, 1);
+ placer(0, 1, mSymbolLabel, 2).setPadding(3);
+ placer(2, 1, mSymbolTextField, 3).setPadding(3);
+ placer(0, 2, mCommandLabel, 2).setPadding(3);
+ placer(2, 2, mCommandTextField, 4).setPadding(3);
+
+ placer(0, 3, mCommentLabel, 2).setPadding(3);
+ placer(2, 3, mCommentTextField, 4).setPadding(3);
+
+ placer(0, 4, mTypeLabel, 2).setPadding(3);
+ placer(2, 4, mTypeDropDown, 3).setPadding(3);
+
+ placer(0, 5, mIconLabel, 2).setPadding(3);
+ placer(2, 5, mIconDropDown, 3).setPadding(3);
+
+ placer(0, 6, mManaLabel, 2).setPadding(3);
+ placer(2, 6, mManaField, 3).setPadding(3);
+ placer(0, 7, mMagicLvlLabel, 2).setPadding(3);
+ placer(2, 7, mMagicLvlField, 3).setPadding(3);
+
+ placer(0, 8, mSchoolLabel, 2).setPadding(3);
+ placer(2, 8, mSchoolDropDown, 3).setPadding(3);
+ placer(0, 9, mSchoolLvlLabel, 2).setPadding(3);
+ placer(2, 9, mSchoolLvlField, 3).setPadding(3);
+
+ placer(0, 10, mSaveButton, 2).setPadding(3);
+ placer(2, 10, mCancelButton, 2).setPadding(3);
+ placer(4, 10, mDeleteButton, 2).setPadding(3);
+
+ setWidth(w);
+ setHeight(h);
+
+ reflowLayout(w);
+
+ center();
+
+ enableVisibleSound(true);
+ setVisible(true);
+}
+
+TextCommandEditor::~TextCommandEditor()
+{
+ delete mIconsModal;
+ mIconsModal = nullptr;
+ delete mTargetTypeModel;
+ mTargetTypeModel = nullptr;
+ delete mMagicSchoolModel;
+ mMagicSchoolModel = nullptr;
+}
+
+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::showControls(const 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->setComment(mCommentTextField->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->setComment("");
+ 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/windows/textcommandeditor.h b/src/gui/windows/textcommandeditor.h
new file mode 100644
index 000000000..610b014d5
--- /dev/null
+++ b/src/gui/windows/textcommandeditor.h
@@ -0,0 +1,103 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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_TEXTCOMMANDEDITOR_H
+#define GUI_TEXTCOMMANDEDITOR_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Button;
+class DropDown;
+class IconsModal;
+class IntTextField;
+class Label;
+class MagicSchoolModel;
+class RadioButton;
+class TargetTypeModel;
+class TextCommand;
+class TextField;
+
+class TextCommandEditor final : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ explicit TextCommandEditor(TextCommand *const command);
+
+ A_DELETE_COPY(TextCommandEditor)
+
+ /**
+ * Destructor.
+ */
+ ~TextCommandEditor();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void scheduleDelete() override;
+
+ private:
+ void showControls(const bool show);
+
+ void save();
+
+ void deleteCommand();
+
+ bool mIsMagicCommand;
+ TextCommand *mCommand;
+
+ RadioButton *mIsMagic;
+ RadioButton *mIsOther;
+ Label *mSymbolLabel;
+ TextField *mSymbolTextField;
+ Label *mCommandLabel;
+ TextField *mCommandTextField;
+
+ Label *mCommentLabel;
+ TextField *mCommentTextField;
+
+ TargetTypeModel *mTargetTypeModel;
+ Label *mTypeLabel;
+ DropDown *mTypeDropDown;
+ IconsModal *mIconsModal;
+ Label *mIconLabel;
+ DropDown *mIconDropDown;
+ Label *mManaLabel;
+ IntTextField *mManaField;
+ Label *mMagicLvlLabel;
+ IntTextField *mMagicLvlField;
+ MagicSchoolModel *mMagicSchoolModel;
+ Label *mSchoolLabel;
+ DropDown *mSchoolDropDown;
+ Label *mSchoolLvlLabel;
+ IntTextField *mSchoolLvlField;
+
+ Button *mCancelButton;
+ Button *mSaveButton;
+ Button *mDeleteButton;
+
+ bool mEnabledKeyboard;
+};
+
+#endif // GUI_TEXTCOMMANDEDITOR_H
diff --git a/src/gui/windows/textdialog.cpp b/src/gui/windows/textdialog.cpp
new file mode 100644
index 000000000..3f62cef08
--- /dev/null
+++ b/src/gui/windows/textdialog.cpp
@@ -0,0 +1,131 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/textdialog.h"
+
+#include "input/keyboardconfig.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/passwordfield.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include "debug.h"
+
+int TextDialog::instances = 0;
+
+TextDialog::TextDialog(const std::string &title, const std::string &msg,
+ Window *const parent, const bool isPassword):
+ Window(title, true, parent, "textdialog.xml"),
+ gcn::ActionListener(),
+ mTextField(nullptr),
+ mPasswordField(nullptr),
+ // TRANSLATORS: text dialog button
+ mOkButton(new Button(this, _("OK"), "OK", this)),
+ mEnabledKeyboard(keyboard.isEnabled())
+{
+ keyboard.setEnabled(false);
+
+ Label *const textLabel = new Label(this, msg);
+ // TRANSLATORS: text dialog button
+ Button *const cancelButton = new Button(this, _("Cancel"), "CANCEL", this);
+
+ place(0, 0, textLabel, 4);
+ if (isPassword)
+ {
+ mPasswordField = new PasswordField(this);
+ place(0, 1, mPasswordField, 4);
+ }
+ else
+ {
+ mTextField = new TextField(this);
+ place(0, 1, mTextField, 4);
+ }
+ place(2, 2, mOkButton);
+ place(3, 2, cancelButton);
+
+ const gcn::Font *const font = getFont();
+ if (font)
+ {
+ int width = font->getWidth(title);
+ if (width < textLabel->getWidth())
+ width = textLabel->getWidth();
+ reflowLayout(static_cast<int>(width + 20));
+ }
+ else
+ {
+ reflowLayout(static_cast<int>(textLabel->getWidth() + 20));
+ }
+
+ if (getParent())
+ {
+ setLocationRelativeTo(getParent());
+ getParent()->moveToTop(this);
+ }
+ setVisible(true);
+ requestModalFocus();
+ if (isPassword)
+ mPasswordField->requestFocus();
+ else
+ 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
+{
+ if (mTextField)
+ return mTextField->getText();
+ else
+ return mPasswordField->getText();
+}
+
+void TextDialog::setText(const std::string &text)
+{
+ if (mTextField)
+ mTextField->setText(text);
+ else
+ mPasswordField->setText(text);
+}
+
+void TextDialog::close()
+{
+ keyboard.setEnabled(mEnabledKeyboard);
+ scheduleDelete();
+}
diff --git a/src/gui/windows/textdialog.h b/src/gui/windows/textdialog.h
new file mode 100644
index 000000000..9f9292ef6
--- /dev/null
+++ b/src/gui/windows/textdialog.h
@@ -0,0 +1,80 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_TEXTDIALOG_H
+#define GUI_TEXTDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Button;
+class PasswordField;
+class TextField;
+
+/**
+* An option dialog.
+ *
+ * \ingroup GUI
+ */
+class TextDialog final : public Window, public gcn::ActionListener
+{
+public:
+ /**
+ * Constructor.
+ *
+ * @see Window::Window
+ */
+ TextDialog(const std::string &title, const std::string &msg,
+ Window *const parent = nullptr, const bool isPassword = false);
+
+ A_DELETE_COPY(TextDialog)
+
+ ~TextDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Get the text in the textfield
+ */
+ const std::string &getText() const A_WARN_UNUSED;
+
+ void setText(const std::string &text);
+
+ static bool isActive() A_WARN_UNUSED
+ { return instances; }
+
+ void close() override;
+
+private:
+ static int instances;
+
+ TextField *mTextField;
+ PasswordField *mPasswordField;
+ Button *mOkButton;
+ bool mEnabledKeyboard;
+};
+
+#endif // GUI_TEXTDIALOG_H
diff --git a/src/gui/windows/tradewindow.cpp b/src/gui/windows/tradewindow.cpp
new file mode 100644
index 000000000..bd3bf088f
--- /dev/null
+++ b/src/gui/windows/tradewindow.cpp
@@ -0,0 +1,486 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/tradewindow.h"
+
+#include "configuration.h"
+#include "inventory.h"
+#include "item.h"
+#include "units.h"
+
+#include "being/localplayer.h"
+#include "being/playerinfo.h"
+#include "being/playerrelations.h"
+
+#include "gui/gui.h"
+#include "gui/sdlfont.h"
+
+#include "gui/windows/inventorywindow.h"
+#include "gui/windows/itemamountwindow.h"
+#include "gui/windows/setup.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/net.h"
+#include "net/tradehandler.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include "debug.h"
+
+// TRANSLATORS: trade window button
+#define CAPTION_PROPOSE _("Propose trade")
+// TRANSLATORS: trade window button
+#define CAPTION_CONFIRMED _("Confirmed. Waiting...")
+// TRANSLATORS: trade window button
+#define CAPTION_ACCEPT _("Agree trade")
+// TRANSLATORS: trade window button
+#define CAPTION_ACCEPTED _("Agreed. Waiting...")
+
+TradeWindow::TradeWindow():
+ // TRANSLATORS: trade window caption
+ Window(_("Trade: You"), false, nullptr, "trade.xml"),
+ gcn::ActionListener(),
+ gcn::SelectionListener(),
+ mMyInventory(new Inventory(Inventory::TRADE)),
+ mPartnerInventory(new Inventory(Inventory::TRADE)),
+ mMyItemContainer(new ItemContainer(this, mMyInventory.get())),
+ mPartnerItemContainer(new ItemContainer(this, mPartnerInventory.get())),
+ // TRANSLATORS: trade window money label
+ mMoneyLabel(new Label(this, strprintf(_("You get %s"), ""))),
+ // TRANSLATORS: trade window button
+ mAddButton(new Button(this, _("Add"), "add", this)),
+ mOkButton(new Button(this, "", "", this)), // Will be filled in later
+ // TRANSLATORS: trade window money change button
+ mMoneyChangeButton(new Button(this, _("Change"), "money", this)),
+ mMoneyField(new TextField(this)),
+ mStatus(PROPOSING),
+ mAutoAddItem(nullptr),
+ mAutoAddToNick(""),
+ mGotMoney(0),
+ mGotMaxMoney(0),
+ mAutoMoney(0),
+ mAutoAddAmount(0),
+ mOkOther(false),
+ mOkMe(false)
+{
+ setWindowName("Trade");
+ setResizable(true);
+ setCloseButton(true);
+ setStickyButtonLock(true);
+ setDefaultSize(386, 180, ImageRect::CENTER);
+ setMinWidth(310);
+ setMinHeight(180);
+
+ if (setupWindow)
+ setupWindow->registerWindowForReset(this);
+
+ const gcn::Font *const fnt = mOkButton->getFont();
+ int width = std::max(fnt->getWidth(CAPTION_PROPOSE),
+ fnt->getWidth(CAPTION_CONFIRMED));
+ width = std::max(width, fnt->getWidth(CAPTION_ACCEPT));
+ width = std::max(width, fnt->getWidth(CAPTION_ACCEPTED));
+
+ mOkButton->setWidth(8 + width);
+
+ mMyItemContainer->addSelectionListener(this);
+
+ ScrollArea *const myScroll = new ScrollArea(mMyItemContainer,
+ true, "trade_background.xml");
+ myScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+
+ mPartnerItemContainer->addSelectionListener(this);
+
+ ScrollArea *const partnerScroll = new ScrollArea(mPartnerItemContainer,
+ true, "trade_background.xml");
+ partnerScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
+
+ // TRANSLATORS: trade window money label
+ Label *const moneyLabel2 = new Label(this, _("You give:"));
+
+ mMoneyField->setWidth(40);
+
+ place(1, 0, mMoneyLabel);
+ place(0, 1, myScroll).setPadding(3);
+ place(1, 1, partnerScroll).setPadding(3);
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+ placer(0, 0, moneyLabel2);
+ placer(1, 0, mMoneyField, 2);
+ placer(3, 0, mMoneyChangeButton).setHAlign(LayoutCell::LEFT);
+ placer = getPlacer(0, 2);
+ placer(0, 0, mAddButton);
+ placer(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();
+ enableVisibleSound(true);
+
+ reset();
+}
+
+TradeWindow::~TradeWindow()
+{
+}
+
+void TradeWindow::setMoney(const int amount)
+{
+ if (amount < 0 || amount < mGotMaxMoney)
+ {
+ if (config.getBoolValue("securetrades"))
+ {
+ close();
+ return;
+ }
+ else
+ {
+ mMoneyLabel->setForegroundColorAll(getThemeColor(
+ static_cast<int>(Theme::WARNING)), getThemeColor(
+ static_cast<int>(Theme::WARNING_OUTLINE)));
+ }
+ }
+ else
+ {
+ mMoneyLabel->setForegroundColorAll(getThemeColor(
+ static_cast<int>(Theme::LABEL)), getThemeColor(
+ static_cast<int>(Theme::LABEL_OUTLINE)));
+ mGotMaxMoney = amount;
+ }
+
+ mGotMoney = amount;
+ // TRANSLATORS: trade window money label
+ mMoneyLabel->setCaption(strprintf(_("You get %s"),
+ Units::formatCurrency(amount).c_str()));
+ mMoneyLabel->adjustSize();
+}
+
+void TradeWindow::addItem(const int id, const bool own, const int quantity,
+ const int refine, const unsigned char color) const
+{
+ if (own)
+ mMyInventory->addItem(id, quantity, refine, color);
+ else
+ mPartnerInventory->addItem(id, quantity, refine, color);
+}
+
+void TradeWindow::addItem2(const int id, const bool own, const int quantity,
+ const int refine, const unsigned char color,
+ const bool equipment) const
+{
+ if (own)
+ mMyInventory->addItem(id, quantity, refine, color, equipment);
+ else
+ mPartnerInventory->addItem(id, quantity, refine, color, equipment);
+}
+
+void TradeWindow::changeQuantity(const int index, const bool own,
+ const int quantity) const
+{
+ Item *item;
+ if (own)
+ item = mMyInventory->getItem(index);
+ else
+ item = mPartnerInventory->getItem(index);
+ if (item)
+ item->setQuantity(quantity);
+}
+
+void TradeWindow::increaseQuantity(const int index, const bool own,
+ const int quantity) const
+{
+ Item *item;
+ if (own)
+ item = mMyInventory->getItem(index);
+ else
+ item = mPartnerInventory->getItem(index);
+ if (item)
+ item->increaseQuantity(quantity);
+}
+
+void TradeWindow::reset()
+{
+ mMyInventory->clear();
+ mPartnerInventory->clear();
+ mOkOther = false;
+ mOkMe = false;
+ setMoney(0);
+ mMoneyField->setEnabled(true);
+ mMoneyField->setText("");
+ mMoneyLabel->setForegroundColorAll(getThemeColor(
+ static_cast<int>(Theme::LABEL)), getThemeColor(
+ static_cast<int>(Theme::LABEL_OUTLINE)));
+ mAddButton->setEnabled(true);
+ mMoneyChangeButton->setEnabled(true);
+ mGotMoney = 0;
+ mGotMaxMoney = 0;
+ setStatus(PREPARING);
+}
+
+void TradeWindow::receivedOk(const bool own)
+{
+ if (own)
+ mOkMe = true;
+ else
+ mOkOther = true;
+
+ if (mOkMe && mOkOther)
+ setStatus(ACCEPTING);
+}
+
+void TradeWindow::tradeItem(const Item *const item, const int quantity,
+ const bool check) const
+{
+ if (check && !checkItem(item))
+ return;
+
+ 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(const 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 *const item = inventoryWindow->getSelectedItem();
+ const std::string &eventId = event.getId();
+
+ if (eventId == "add")
+ {
+ if (mStatus != PREPARING)
+ return;
+
+ if (!inventoryWindow->isWindowVisible())
+ {
+ inventoryWindow->setVisible(true);
+ return;
+ }
+
+ if (!item)
+ return;
+
+ if (mMyInventory->getFreeSlot() == -1)
+ return;
+
+
+ if (!checkItem(item))
+ return;
+
+ // Choose amount of items to trade
+ ItemAmountWindow::showWindow(ItemAmountWindow::TradeAdd, this, item);
+
+ setStatus(PREPARING);
+ }
+ else if (eventId == "cancel")
+ {
+ setVisible(false);
+ reset();
+ PlayerInfo::setTrading(false);
+ Net::getTradeHandler()->cancel();
+ }
+ else if (eventId == "ok")
+ {
+ mMoneyField->setEnabled(false);
+ mAddButton->setEnabled(false);
+ mMoneyChangeButton->setEnabled(false);
+ receivedOk(true);
+ setStatus(PROPOSING);
+ Net::getTradeHandler()->confirm();
+ }
+ else if (eventId == "trade")
+ {
+ receivedOk(true);
+ setStatus(ACCEPTED);
+ Net::getTradeHandler()->finish();
+ }
+ else if (eventId == "money")
+ {
+ if (mStatus != PREPARING)
+ return;
+
+ int v = atoi(mMoneyField->getText().c_str());
+ const int curMoney = PlayerInfo::getAttribute(PlayerInfo::MONEY);
+ if (v > curMoney)
+ {
+ if (localChatTab)
+ {
+ // TRANSLATORS: trade error
+ 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 = nullptr;
+ mAutoAddToNick.clear();
+ mAutoMoney = 0;
+ mAutoAddAmount = 0;
+ mGotMoney = 0;
+ mGotMaxMoney = 0;
+ mMoneyLabel->setForegroundColorAll(getThemeColor(
+ static_cast<int>(Theme::LABEL)), getThemeColor(
+ static_cast<int>(Theme::LABEL_OUTLINE)));
+}
+
+void TradeWindow::addAutoItem(const std::string &nick, Item* const item,
+ const int amount)
+{
+ mAutoAddToNick = nick;
+ mAutoAddItem = item;
+ mAutoAddAmount = amount;
+}
+
+void TradeWindow::addAutoMoney(const std::string &nick, const int money)
+{
+ mAutoAddToNick = nick;
+ mAutoMoney = money;
+}
+
+void TradeWindow::initTrade(const std::string &nick)
+{
+ if (!player_node)
+ return;
+
+ if (!mAutoAddToNick.empty() && mAutoAddToNick == nick)
+ {
+ if (mAutoAddItem && mAutoAddItem->getQuantity())
+ {
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (inv)
+ {
+ Item *const item = inv->findItem(mAutoAddItem->getId(),
+ mAutoAddItem->getColor());
+ if (item)
+ tradeItem(item, mAutoAddItem->getQuantity());
+ }
+ }
+ if (mAutoMoney)
+ {
+ Net::getTradeHandler()->setMoney(mAutoMoney);
+ mMoneyField->setText(strprintf("%d", mAutoMoney));
+ }
+ }
+ clear();
+ if (!player_relations.isGoodName(nick))
+ setCaptionFont(gui->getSecureFont());
+}
+
+bool TradeWindow::checkItem(const Item *const item) const
+{
+ const int itemId = item->getId();
+ if (PlayerInfo::isItemProtected(itemId))
+ return false;
+ const Item *const tItem = mMyInventory->findItem(
+ itemId, item->getColor());
+
+ if (tItem && (tItem->getQuantity() > 1
+ || item->getQuantity() > 1))
+ {
+ if (localChatTab)
+ {
+ // TRANSLATORS: trade error
+ localChatTab->chatLog(_("Failed adding item. You can not "
+ "overlap one kind of item on the window."), BY_SERVER);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool TradeWindow::isInpupFocused() const
+{
+ return (mMoneyField && mMoneyField->isFocused());
+}
diff --git a/src/gui/windows/tradewindow.h b/src/gui/windows/tradewindow.h
new file mode 100644
index 000000000..df6d8a80a
--- /dev/null
+++ b/src/gui/windows/tradewindow.h
@@ -0,0 +1,187 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_TRADEWINDOW_H
+#define GUI_TRADEWINDOW_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+#include <memory>
+
+class Button;
+class Inventory;
+class Item;
+class ItemContainer;
+class Label;
+class TextField;
+
+/**
+ * Trade dialog.
+ *
+ * \ingroup Interface
+ */
+class TradeWindow final : public Window,
+ private gcn::ActionListener,
+ private gcn::SelectionListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ TradeWindow();
+
+ A_DELETE_COPY(TradeWindow)
+
+ /**
+ * Destructor.
+ */
+ ~TradeWindow();
+
+ /**
+ * Displays expected money in the trade window.
+ */
+ void setMoney(const int quantity);
+
+ /**
+ * Add an item to the trade window.
+ */
+ void addItem(const int id, const bool own, const int quantity,
+ const int refine, const unsigned char color) const;
+
+ /**
+ * Reset both item containers
+ */
+ void reset();
+
+ /**
+ * Add an item to the trade window.
+ */
+ void addItem2(const int id, const bool own, const int quantity,
+ const int refine, const unsigned char color,
+ const bool equipment) const;
+
+ /**
+ * Change quantity of an item.
+ */
+ void changeQuantity(const int index, const bool own,
+ const int quantity) const;
+
+ /**
+ * Increase quantity of an item.
+ */
+ void increaseQuantity(const int index, const bool own,
+ const int quantity) const;
+
+ /**
+ * Player received ok message from server
+ */
+ void receivedOk(const bool own);
+
+ /**
+ * Send trade packet.
+ */
+ void tradeItem(const Item *const item, const int quantity,
+ const bool check = false) const;
+
+ /**
+ * Updates the labels and makes sure only one item is selected in
+ * either my inventory or partner inventory.
+ */
+ void valueChanged(const gcn::SelectionEvent &event) override;
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ /**
+ * Closes the Trade Window, as well as telling the server that the
+ * window has been closed.
+ */
+ void close() override;
+
+ /**
+ * Clear auto trade items.
+ */
+ void clear();
+
+ /**
+ * Add item what will be added to trade.
+ */
+ void addAutoItem(const std::string &nick, Item *const item,
+ const int amount);
+
+ void addAutoMoney(const std::string &nick, const int money);
+
+ void initTrade(const std::string &nick);
+
+ std::string getAutoTradeNick() const A_WARN_UNUSED
+ { return mAutoAddToNick; }
+
+ bool checkItem(const Item *const item) const A_WARN_UNUSED;
+
+ bool isInpupFocused() const A_WARN_UNUSED;
+
+ 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(const Status s);
+
+ typedef const std::auto_ptr<Inventory> InventoryPtr;
+ InventoryPtr mMyInventory;
+ InventoryPtr mPartnerInventory;
+
+ ItemContainer *mMyItemContainer;
+ ItemContainer *mPartnerItemContainer;
+
+ Label *mMoneyLabel;
+ Button *mAddButton;
+ Button *mOkButton;
+ Button *mMoneyChangeButton;
+ TextField *mMoneyField;
+
+ Status mStatus;
+ Item* mAutoAddItem;
+ std::string mAutoAddToNick;
+ int mGotMoney;
+ int mGotMaxMoney;
+ int mAutoMoney;
+ int mAutoAddAmount;
+ bool mOkOther;
+ bool mOkMe;
+};
+
+extern TradeWindow *tradeWindow;
+
+#endif // GUI_TRADEWINDOW_H
diff --git a/src/gui/windows/unregisterdialog.cpp b/src/gui/windows/unregisterdialog.cpp
new file mode 100644
index 000000000..9c445ebdd
--- /dev/null
+++ b/src/gui/windows/unregisterdialog.cpp
@@ -0,0 +1,154 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/unregisterdialog.h"
+
+#include "client.h"
+
+#include "gui/windows/okdialog.h"
+#include "gui/windows/registerdialog.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/label.h"
+#include "gui/widgets/passwordfield.h"
+
+#include "net/logindata.h"
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+
+#include <string>
+#include <sstream>
+
+#include "debug.h"
+
+UnRegisterDialog::UnRegisterDialog(LoginData *const data) :
+ // TRANSLATORS: unregister dialog name
+ Window(_("Unregister"), true, nullptr, "unregister.xml"),
+ gcn::ActionListener(),
+ mLoginData(data),
+ mPasswordField(new PasswordField(this, mLoginData->password)),
+ // TRANSLATORS: unregister dialog. button.
+ mUnRegisterButton(new Button(this, _("Unregister"), "unregister", this)),
+ // TRANSLATORS: unregister dialog. button.
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ mWrongDataNoticeListener(new WrongDataNoticeListener)
+{
+ // TRANSLATORS: unregister dialog. label.
+ Label *const userLabel = new Label(this, strprintf(_("Name: %s"),
+ mLoginData->username.c_str()));
+ // TRANSLATORS: unregister dialog. label.
+ Label *const passwordLabel = new Label(this, _("Password:"));
+
+ 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 = nullptr;
+}
+
+void UnRegisterDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "cancel")
+ {
+ client->setState(STATE_CHAR_SELECT);
+ }
+ else if (eventId == "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 errorMsg;
+ bool error = false;
+
+ const unsigned int min = Net::getLoginHandler()
+ ->getMinPasswordLength();
+ const unsigned int max = Net::getLoginHandler()
+ ->getMaxPasswordLength();
+
+ if (password.length() < min)
+ {
+ // TRANSLATORS: unregister dialog. error message.
+ errorMsg << strprintf(_("The password needs to be at least %u "
+ "characters long."), min);
+ error = true;
+ }
+ else if (password.length() > max)
+ {
+ // TRANSLATORS: unregister dialog. error message.
+ errorMsg << strprintf(_("The password needs to be less than "
+ "%u characters long."), max);
+ error = true;
+ }
+
+ if (error)
+ {
+ mWrongDataNoticeListener->setTarget(this->mPasswordField);
+
+ // TRANSLATORS: unregister dialog. error message.
+ OkDialog *const dlg = new OkDialog(_("Error"),
+ errorMsg.str(), DIALOG_ERROR);
+ 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/windows/unregisterdialog.h b/src/gui/windows/unregisterdialog.h
new file mode 100644
index 000000000..de0da27bb
--- /dev/null
+++ b/src/gui/windows/unregisterdialog.h
@@ -0,0 +1,70 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_UNREGISTERDIALOG_H
+#define GUI_UNREGISTERDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+
+class Button;
+class LoginData;
+class TextField;
+class WrongDataNoticeListener;
+
+/**
+ * The Unregister dialog.
+ *
+ * \ingroup Interface
+ */
+class UnRegisterDialog final : public Window, public gcn::ActionListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ explicit UnRegisterDialog(LoginData *const loginData);
+
+ A_DELETE_COPY(UnRegisterDialog)
+
+ ~UnRegisterDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ private:
+ LoginData *mLoginData;
+
+ TextField *mPasswordField;
+
+ Button *mUnRegisterButton;
+ Button *mCancelButton;
+
+ WrongDataNoticeListener *mWrongDataNoticeListener;
+};
+
+#endif // GUI_UNREGISTERDIALOG_H
diff --git a/src/gui/windows/updaterwindow.cpp b/src/gui/windows/updaterwindow.cpp
new file mode 100644
index 000000000..d025f83b6
--- /dev/null
+++ b/src/gui/windows/updaterwindow.cpp
@@ -0,0 +1,943 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/updaterwindow.h"
+
+#include "client.h"
+#include "configuration.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.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/logindata.h"
+
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+#include "utils/mkdir.h"
+#include "utils/paths.h"
+#include "utils/process.h"
+
+#include <fstream>
+
+#include <sys/stat.h>
+
+#include "debug.h"
+
+const std::string xmlUpdateFile("resources.xml");
+const std::string txtUpdateFile("resources2.txt");
+const std::string updateServer2
+ ("http://download.evolonline.org/manaplus/updates/");
+
+/**
+ * Load the given file into a vector of updateFiles.
+ */
+static std::vector<UpdateFile> loadXMLFile(const std::string &fileName)
+{
+ std::vector<UpdateFile> files;
+ XML::Document doc(fileName, false);
+ const XmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlNameEqual(rootNode, "updates"))
+ {
+ logger->log("Error loading update file: %s", fileName.c_str());
+ return files;
+ }
+
+ for_each_xml_child_node(fileNode, rootNode)
+ {
+ if (!xmlNameEqual(fileNode, "update"))
+ continue;
+
+ if (XML::getProperty(fileNode, "group", "default") != "default")
+ 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", "");
+ const std::string version = XML::getProperty(
+ fileNode, "version", "");
+ if (!version.empty())
+ {
+ if (version > CHECK_VERSION)
+ continue;
+ }
+ const std::string notVersion = XML::getProperty(
+ fileNode, "notVersion", "");
+ if (!notVersion.empty())
+ {
+ if (notVersion <= CHECK_VERSION)
+ continue;
+ }
+ if (XML::getProperty(fileNode, "required", "yes") == "yes")
+ file.required = true;
+ else
+ file.required = false;
+
+ if (checkPath(file.name))
+ files.push_back(file);
+ }
+
+ return files;
+}
+
+static 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];
+ char 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.clear();
+
+ if (!thisFile.name.empty() && checkPath(thisFile.name))
+ 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,
+ const bool applyUpdates,
+ const int updateType):
+ // TRANSLATORS: updater window name
+ Window(_("Updating..."), false, nullptr, "update.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mDownloadStatus(UPDATE_NEWS),
+ mUpdateHost(updateHost),
+ mUpdatesDir(updatesDir),
+ mUpdatesDirReal(updatesDir),
+ mCurrentFile("news.txt"),
+ mNewLabelCaption(),
+ mDownloadProgress(0.0F),
+ mDownloadMutex(),
+ mCurrentChecksum(0),
+ mStoreInMemory(true),
+ mDownloadComplete(true),
+ mUserCancel(false),
+ mDownloadedBytes(0),
+ mMemoryBuffer(nullptr),
+ mDownload(nullptr),
+ mUpdateFiles(),
+ mTempUpdateFiles(),
+ mUpdateIndex(0),
+ mUpdateIndexOffset(0),
+ mLoadUpdates(applyUpdates),
+ mUpdateType(updateType),
+ // TRANSLATORS: updater window label
+ mLabel(new Label(this, _("Connecting..."))),
+ // TRANSLATORS: updater window button
+ mCancelButton(new Button(this, _("Cancel"), "cancel", this)),
+ // TRANSLATORS: updater window button
+ mPlayButton(new Button(this, _("Play"), "play", this)),
+ mProgressBar(new ProgressBar(this, 0.0, 310, 0)),
+ mBrowserBox(new BrowserBox(this)),
+ mScrollArea(new ScrollArea(mBrowserBox, true, "update_background.xml")),
+ mUpdateServerPath(mUpdateHost)
+{
+ setWindowName("UpdaterWindow");
+ setResizable(true);
+ setDefaultSize(450, 400, ImageRect::CENTER);
+ setMinWidth(310);
+ setMinHeight(220);
+
+ mProgressBar->setSmoothProgress(false);
+ mBrowserBox->setOpaque(false);
+ mBrowserBox->setLinkHandler(this);
+ mBrowserBox->setEnableKeys(true);
+ mBrowserBox->setEnableTabs(true);
+ mPlayButton->setEnabled(false);
+
+ ContainerPlacer placer;
+ placer = getPlacer(0, 0);
+
+ placer(0, 0, mScrollArea, 5, 3).setPadding(3);
+ placer(0, 3, mLabel, 5);
+ placer(0, 4, mProgressBar, 5);
+ placer(3, 5, mCancelButton);
+ placer(4, 5, mPlayButton);
+
+ Layout &layout = getLayout();
+ layout.setRowHeight(0, Layout::AUTO_SET);
+
+ addKeyListener(this);
+
+ loadWindowState();
+ setVisible(true);
+ mCancelButton->requestFocus();
+ removeProtocol(mUpdateServerPath);
+
+ download();
+}
+
+UpdaterWindow::~UpdaterWindow()
+{
+ if (mLoadUpdates)
+ loadUpdates();
+
+ if (mDownload)
+ {
+ mDownload->cancel();
+
+ delete mDownload;
+ mDownload = nullptr;
+ }
+ free(mMemoryBuffer);
+}
+
+void UpdaterWindow::setProgress(const 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)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "cancel")
+ {
+ // Register the user cancel
+ mUserCancel = true;
+ // Skip the updating process
+ if (mDownloadStatus != UPDATE_COMPLETE)
+ {
+ mDownload->cancel();
+ mDownloadStatus = UPDATE_ERROR;
+ }
+ }
+ else if (eventId == "play")
+ {
+ client->setState(STATE_LOAD_DATA);
+ }
+}
+
+void UpdaterWindow::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ const int actionId = static_cast<KeyEvent*>(&keyEvent)->getActionId();
+ if (actionId == static_cast<int>(Input::KEY_GUI_CANCEL))
+ {
+ action(gcn::ActionEvent(nullptr, mCancelButton->getActionEventId()));
+ client->setState(STATE_LOGIN);
+ }
+ else if (actionId == static_cast<int>(Input::KEY_GUI_SELECT)
+ || actionId == static_cast<int>(Input::KEY_GUI_SELECT2))
+ {
+ if (mDownloadStatus == UPDATE_COMPLETE ||
+ mDownloadStatus == UPDATE_ERROR)
+ {
+ action(gcn::ActionEvent(nullptr, mPlayButton->getActionEventId()));
+ }
+ else
+ {
+ action(gcn::ActionEvent(nullptr,
+ 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();
+
+ std::string newsName = mUpdatesDir + "/local/help/news.txt";
+ mkdir_r((mUpdatesDir + "/local/help/").c_str());
+ bool firstLine(true);
+ std::ofstream file;
+ std::stringstream ss(mMemoryBuffer);
+ std::string line;
+ file.open(newsName.c_str(), std::ios::out);
+ while (std::getline(ss, line, '\n'))
+ {
+ if (firstLine)
+ {
+ firstLine = false;
+ const size_t i = line.find("##9 Latest client version: ##6");
+ if (!i)
+ continue;
+
+ if (file.is_open())
+ file << line << std::endl;
+ mBrowserBox->addRow(line);
+ }
+ else
+ {
+ if (file.is_open())
+ file << line << std::endl;
+ mBrowserBox->addRow(line);
+ }
+ }
+
+ file.close();
+ // Free the memory buffer now that we don't need it anymore
+ free(mMemoryBuffer);
+ mMemoryBuffer = nullptr;
+ 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 (serverVersion < 1)
+ {
+ line = strtok(nullptr, "\n");
+ if (line)
+ {
+ mBrowserBox->addRow(strprintf("##9 Latest client version: "
+ "##6ManaPlus %s##0", line), true);
+ }
+ }
+ if (version > CHECK_VERSION)
+ {
+#if defined(ANDROID)
+ mBrowserBox->addRow("", true);
+ mBrowserBox->addRow("##1You can download from [[@@"
+ "https://play.google.com/store/apps/details?id=org.evolonline"
+ ".beta.manaplus|Google Play@@]", true);
+ mBrowserBox->addRow("##1ManaPlus updated.", true);
+#elif defined(WIN32)
+ mBrowserBox->addRow("", true);
+ mBrowserBox->addRow(" ##1[@@http://download.evolonline.org/"
+ "manaplus/download/manaplus-win32.exe|download here@@]", true);
+#else
+ mBrowserBox->addRow("", true);
+ mBrowserBox->addRow(" ##1@@http://manaplus.org/|"
+ "http://manaplus.org/@@", true);
+ mBrowserBox->addRow("##1You can download it from", true);
+ mBrowserBox->addRow("##1ManaPlus updated.", true);
+#endif
+ }
+ else
+ {
+ mBrowserBox->addRow("You have latest client version.", true);
+ }
+ }
+
+ // Free the memory buffer now that we don't need it anymore
+ free(mMemoryBuffer);
+ mMemoryBuffer = nullptr;
+ mDownloadedBytes = 0;
+
+ mScrollArea->setVerticalScrollAmount(0);
+}
+
+int UpdaterWindow::updateProgress(void *ptr, DownloadStatus status,
+ size_t dt, size_t dn)
+{
+ UpdaterWindow *const 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)
+ {
+ if (uw->mDownloadStatus == UPDATE_COMPLETE)
+ { // ignoring error in last state (was UPDATE_PATCH)
+ uw->mDownloadStatus = UPDATE_COMPLETE;
+ uw->mDownloadComplete = true;
+ free(uw->mMemoryBuffer);
+ uw->mMemoryBuffer = nullptr;
+ }
+ else
+ {
+ 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(std::string(uw->mCurrentFile).append(" (")
+ .append(toString(static_cast<int>(progress * 100))).append("%)"));
+
+ 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 *const uw = reinterpret_cast<UpdaterWindow *>(stream);
+ const 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://manaplus.org/update/" + mCurrentFile,
+ updateProgress, true);
+ }
+ else
+ {
+ mDownload = new Net::Download(this, std::string(mUpdateHost).append(
+ "/").append(mCurrentFile), updateProgress);
+ }
+
+ if (mStoreInMemory)
+ {
+ mDownload->setWriteFunction(UpdaterWindow::memoryWrite);
+ }
+ else
+ {
+ if (mDownloadStatus == UPDATE_RESOURCES)
+ {
+ mDownload->setFile(std::string(mUpdatesDir).append("/").append(
+ mCurrentFile), mCurrentChecksum);
+ }
+ else
+ {
+ mDownload->setFile(std::string(mUpdatesDir).append(
+ "/").append(mCurrentFile));
+ }
+ }
+
+ if (mDownloadStatus != UPDATE_RESOURCES)
+ mDownload->noCache();
+
+ setLabel(mCurrentFile + " (0%)");
+ mDownloadComplete = false;
+
+ mDownload->start();
+}
+
+void UpdaterWindow::loadUpdates()
+{
+ const ResourceManager *const resman = ResourceManager::getInstance();
+ if (mUpdateFiles.empty())
+ { // updates not downloaded
+ mUpdateFiles = loadXMLFile(std::string(mUpdatesDir).append(
+ "/").append(xmlUpdateFile));
+ if (mUpdateFiles.empty())
+ {
+ logger->log("Warning this server does not have a"
+ " %s file falling back to %s", xmlUpdateFile.c_str(),
+ txtUpdateFile.c_str());
+ mUpdateFiles = loadTxtFile(std::string(mUpdatesDir).append(
+ "/").append(txtUpdateFile));
+ }
+ }
+
+ std::string fixPath = mUpdatesDir + "/fix";
+ const unsigned sz = static_cast<unsigned>(mUpdateFiles.size());
+ for (mUpdateIndex = 0; mUpdateIndex < sz; mUpdateIndex++)
+ {
+ UpdaterWindow::addUpdateFile(resman, mUpdatesDir, fixPath,
+ mUpdateFiles[mUpdateIndex].name, false);
+ }
+ loadManaPlusUpdates(mUpdatesDir, resman);
+}
+
+void UpdaterWindow::loadLocalUpdates(const std::string &dir)
+{
+ const ResourceManager *const resman = ResourceManager::getInstance();
+
+ std::vector<UpdateFile> updateFiles
+ = loadXMLFile(std::string(dir).append("/").append(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(std::string(dir).append(
+ "/").append(txtUpdateFile));
+ }
+
+ const std::string fixPath = dir + "/fix";
+ for (unsigned int updateIndex = 0, sz = static_cast<unsigned int>(
+ updateFiles.size()); updateIndex < sz; updateIndex ++)
+ {
+ UpdaterWindow::addUpdateFile(resman, dir, fixPath,
+ updateFiles[updateIndex].name, false);
+ }
+ loadManaPlusUpdates(dir, resman);
+}
+
+void UpdaterWindow::unloadUpdates(const std::string &dir)
+{
+ const ResourceManager *const resman = ResourceManager::getInstance();
+ std::vector<UpdateFile> updateFiles
+ = loadXMLFile(std::string(dir).append("/").append(xmlUpdateFile));
+
+ if (updateFiles.empty())
+ {
+ updateFiles = loadTxtFile(std::string(dir).append(
+ "/").append(txtUpdateFile));
+ }
+
+ const std::string fixPath = dir + "/fix";
+ for (unsigned int updateIndex = 0, sz = static_cast<unsigned int>(
+ updateFiles.size()); updateIndex < sz; updateIndex ++)
+ {
+ UpdaterWindow::removeUpdateFile(resman, dir, fixPath,
+ updateFiles[updateIndex].name);
+ }
+ unloadManaPlusUpdates(dir, resman);
+}
+
+void UpdaterWindow::loadManaPlusUpdates(const std::string &dir,
+ const ResourceManager *const resman)
+{
+ std::string fixPath = dir + "/fix";
+ std::vector<UpdateFile> updateFiles
+ = loadXMLFile(std::string(fixPath).append("/").append(xmlUpdateFile));
+
+ for (unsigned int updateIndex = 0, sz = static_cast<unsigned int>(
+ updateFiles.size()); updateIndex < sz; updateIndex ++)
+ {
+ std::string name = updateFiles[updateIndex].name;
+ if (strStartWith(name, "manaplus_"))
+ {
+ struct stat statbuf;
+ std::string file = std::string(fixPath).append("/").append(name);
+ if (!stat(file.c_str(), &statbuf))
+ resman->addToSearchPath(file, false);
+ }
+ }
+}
+
+void UpdaterWindow::unloadManaPlusUpdates(const std::string &dir,
+ const ResourceManager *const resman)
+{
+ const std::string fixPath = dir + "/fix";
+ const std::vector<UpdateFile> updateFiles
+ = loadXMLFile(std::string(fixPath).append("/").append(xmlUpdateFile));
+
+ for (unsigned int updateIndex = 0, sz = static_cast<unsigned int>(
+ updateFiles.size()); updateIndex < sz; updateIndex ++)
+ {
+ std::string name = updateFiles[updateIndex].name;
+ if (strStartWith(name, "manaplus_"))
+ {
+ struct stat statbuf;
+ const std::string file = std::string(
+ fixPath).append("/").append(name);
+ if (!stat(file.c_str(), &statbuf))
+ resman->removeFromSearchPath(file);
+ }
+ }
+}
+
+void UpdaterWindow::addUpdateFile(const ResourceManager *const resman,
+ const std::string &path,
+ const std::string &fixPath,
+ const std::string &file,
+ const bool append)
+{
+ const std::string tmpPath = std::string(path).append("/").append(file);
+ if (!append)
+ resman->addToSearchPath(tmpPath, append);
+
+ const std::string fixFile = std::string(fixPath).append("/").append(file);
+ struct stat statbuf;
+ if (!stat(fixFile.c_str(), &statbuf))
+ resman->addToSearchPath(fixFile, append);
+
+ if (append)
+ resman->addToSearchPath(tmpPath, append);
+}
+
+void UpdaterWindow::removeUpdateFile(const ResourceManager *const resman,
+ const std::string &path,
+ const std::string &fixPath,
+ const std::string &file)
+{
+ resman->removeFromSearchPath(std::string(path).append("/").append(file));
+ const std::string fixFile = std::string(fixPath).append("/").append(file);
+ struct stat statbuf;
+ if (!stat(fixFile.c_str(), &statbuf))
+ resman->removeFromSearchPath(fixFile);
+}
+
+void UpdaterWindow::logic()
+{
+ BLOCK_START("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);
+ if (mUpdateFiles.size() && mUpdateIndex <= mUpdateFiles.size())
+ {
+ mProgressBar->setText(strprintf("%u/%u", mUpdateIndex
+ + mUpdateIndexOffset + 1, static_cast<unsigned>(
+ mUpdateFiles.size()) + static_cast<int>(
+ mTempUpdateFiles.size()) + 1));
+ }
+ else
+ {
+ mProgressBar->setText("");
+ }
+ }
+
+ switch (mDownloadStatus)
+ {
+ case UPDATE_ERROR:
+ mBrowserBox->addRow("");
+ // TRANSLATORS: update message
+ mBrowserBox->addRow(_("##1 The update process is incomplete."));
+ // TRANSLATORS: Continues "The update process is incomplete.".
+ 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();
+
+ mUpdateHost = updateServer2 + mUpdateServerPath;
+ mUpdatesDir.append("/fix");
+ mCurrentFile = xmlUpdateFile;
+ mStoreInMemory = false;
+ mDownloadStatus = UPDATE_LIST2;
+ download();
+ }
+ break;
+
+ case UPDATE_LIST:
+ if (mDownloadComplete)
+ {
+ if (mCurrentFile == xmlUpdateFile)
+ {
+ mUpdateFiles = loadXMLFile(std::string(mUpdatesDir).append(
+ "/").append(xmlUpdateFile));
+
+ if (mUpdateFiles.empty())
+ {
+ 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(std::string(mUpdatesDir).append(
+ "/").append(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((std::string(mUpdatesDir).append(
+ "/").append(mCurrentFile)).c_str());
+
+ if (!temp.is_open() || !validateFile(std::string(
+ mUpdatesDir).append("/").append(mCurrentFile),
+ mCurrentChecksum))
+ {
+ temp.close();
+ download();
+ }
+ else
+ {
+ temp.close();
+ logger->log("%s already here", mCurrentFile.c_str());
+ }
+ mUpdateIndex++;
+ }
+ else
+ {
+ // Download of updates completed
+ mCurrentFile = "latest.txt";
+ mStoreInMemory = true;
+ mDownloadStatus = UPDATE_PATCH;
+ download(); // download() changes
+ // mDownloadComplete to false
+ }
+ }
+ break;
+ case UPDATE_LIST2:
+ if (mDownloadComplete)
+ {
+ if (mCurrentFile == xmlUpdateFile)
+ {
+ mTempUpdateFiles = loadXMLFile(std::string(
+ mUpdatesDir).append("/").append(xmlUpdateFile));
+ }
+ mUpdateIndexOffset = mUpdateIndex;
+ mUpdateIndex = 0;
+ mStoreInMemory = false;
+ mDownloadStatus = UPDATE_RESOURCES2;
+ download();
+ }
+ break;
+ case UPDATE_RESOURCES2:
+ if (mDownloadComplete)
+ {
+ if (mUpdateIndex < mTempUpdateFiles.size())
+ {
+ const UpdateFile thisFile = mTempUpdateFiles[mUpdateIndex];
+ mCurrentFile = thisFile.name;
+ std::string checksum;
+ checksum = thisFile.hash;
+ std::stringstream ss(checksum);
+ ss >> std::hex >> mCurrentChecksum;
+
+ std::ifstream temp((std::string(mUpdatesDir).append(
+ "/").append(mCurrentFile)).c_str());
+
+ if (!temp.is_open() || !validateFile(std::string(
+ mUpdatesDir).append("/").append(mCurrentFile),
+ mCurrentChecksum))
+ {
+ temp.close();
+ download();
+ }
+ else
+ {
+ temp.close();
+ logger->log("%s already here", mCurrentFile.c_str());
+ }
+ mUpdateIndex++;
+ }
+ else
+ {
+ mUpdatesDir = mUpdatesDirReal;
+ mDownloadStatus = UPDATE_COMPLETE;
+ }
+ }
+ break;
+ case UPDATE_COMPLETE:
+ mUpdatesDir = mUpdatesDirReal;
+ enable();
+ // TRANSLATORS: updater window label
+ setLabel(_("Completed"));
+ break;
+ case UPDATE_IDLE:
+ break;
+ default:
+ logger->log("UpdaterWindow::logic unknown status: "
+ + toString(static_cast<unsigned>(mDownloadStatus)));
+ break;
+ }
+ BLOCK_END("UpdaterWindow::logic")
+}
+
+bool UpdaterWindow::validateFile(const std::string &filePath,
+ const unsigned long hash)
+{
+ FILE *const file = fopen(filePath.c_str(), "rb");
+ if (!file)
+ return false;
+
+ const unsigned long adler = Net::Download::fadler32(file);
+ fclose(file);
+ return adler == hash;
+}
+
+unsigned long UpdaterWindow::getFileHash(const std::string &filePath)
+{
+ int size = 0;
+ char *const buf = static_cast<char*>(ResourceManager::loadFile(
+ filePath, size));
+ if (!buf)
+ return 0;
+ return Net::Download::adlerBuffer(buf, size);
+}
+
+void UpdaterWindow::handleLink(const std::string &link,
+ gcn::MouseEvent *event A_UNUSED)
+{
+ if (strStartWith(link, "http://") || strStartWith(link, "https://"))
+ openBrowser(link);
+}
diff --git a/src/gui/windows/updaterwindow.h b/src/gui/windows/updaterwindow.h
new file mode 100644
index 000000000..e317e3f95
--- /dev/null
+++ b/src/gui/windows/updaterwindow.h
@@ -0,0 +1,258 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_UPDATERWINDOW_H
+#define GUI_UPDATERWINDOW_H
+
+#include "gui/widgets/linkhandler.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 Label;
+class ProgressBar;
+class ResourceManager;
+class ScrollArea;
+
+struct UpdateFile final
+{
+ public:
+ UpdateFile() :
+ name(),
+ hash(),
+ type(),
+ desc(),
+ required(false)
+ {
+ }
+ std::string name;
+ std::string hash;
+ std::string type;
+ std::string desc;
+ bool required;
+};
+
+/**
+ * Update progress window GUI
+ *
+ * \ingroup GUI
+ */
+class UpdaterWindow final : public Window,
+ public gcn::ActionListener,
+ public LinkHandler,
+ 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,
+ const bool applyUpdates, const int updateType);
+
+ A_DELETE_COPY(UpdaterWindow)
+
+ /**
+ * Destructor
+ */
+ ~UpdaterWindow();
+
+ /**
+ * Set's progress bar status
+ */
+ void setProgress(const 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) override;
+
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ void logic() override;
+
+ void handleLink(const std::string &link,
+ gcn::MouseEvent *event A_UNUSED) override;
+
+ static void loadLocalUpdates(const std::string &dir);
+
+ static void unloadUpdates(const std::string &dir);
+
+ static void addUpdateFile(const ResourceManager *const resman,
+ const std::string &path,
+ const std::string &fixPath,
+ const std::string &file,
+ const bool append);
+
+ static void removeUpdateFile(const ResourceManager *const resman,
+ const std::string &path,
+ const std::string &fixPath,
+ const std::string &file);
+
+ static void loadManaPlusUpdates(const std::string &dir,
+ const ResourceManager *const resman);
+
+ static void unloadManaPlusUpdates(const std::string &dir,
+ const ResourceManager *const resman);
+
+ static unsigned long getFileHash(const std::string &filePath);
+
+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);
+
+ static bool validateFile(const std::string &filePath,
+ const unsigned long hash) A_WARN_UNUSED;
+
+ enum UpdateDownloadStatus
+ {
+ UPDATE_ERROR = 0,
+ UPDATE_IDLE,
+ UPDATE_LIST,
+ UPDATE_COMPLETE,
+ UPDATE_NEWS,
+ UPDATE_RESOURCES,
+ UPDATE_PATCH,
+ UPDATE_LIST2,
+ UPDATE_RESOURCES2
+ };
+
+ /** 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;
+
+ std::string mUpdatesDirReal;
+
+ /** 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;
+
+ /** List of temp files to download. */
+ std::vector<UpdateFile> mTempUpdateFiles;
+
+ /** Index of the file to be downloaded. */
+ unsigned int mUpdateIndex;
+
+ /** Index offset for disaplay downloaded file. */
+ unsigned int mUpdateIndexOffset;
+
+ /** Tells ~UpdaterWindow() if it should load updates */
+ bool mLoadUpdates;
+
+ int mUpdateType;
+
+ 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. */
+ std::string mUpdateServerPath;
+};
+
+#endif // GUI_UPDATERWINDOW_H
diff --git a/src/gui/windows/whoisonline.cpp b/src/gui/windows/whoisonline.cpp
new file mode 100644
index 000000000..dafbb0419
--- /dev/null
+++ b/src/gui/windows/whoisonline.cpp
@@ -0,0 +1,866 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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/windows/whoisonline.h"
+
+#include "guild.h"
+#include "party.h"
+
+#include "gui/viewport.h"
+
+#include "gui/windows/chatwindow.h"
+#include "gui/windows/socialwindow.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "actorspritemanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "main.h"
+
+#include "being/localplayer.h"
+#include "being/playerrelations.h"
+
+#include "net/download.h"
+#include "net/net.h"
+#include "net/playerhandler.h"
+
+#include "utils/gettext.h"
+#include "utils/sdlhelper.h"
+
+#include <SDL_thread.h>
+#include <vector>
+#include <algorithm>
+
+// Curl should be included after Guichan to avoid Windows redefinitions
+#include <curl/curl.h>
+
+#include "debug.h"
+
+#ifdef free
+#undef free
+#endif
+
+#ifdef malloc
+#undef malloc
+#endif
+
+class NameFunctuator final
+{
+ public:
+ bool operator()(const OnlinePlayer *left,
+ const OnlinePlayer *right) const
+ {
+ return (compareStrI(left->getNick(), right->getNick()) < 0);
+ }
+} nameCompare;
+
+WhoIsOnline::WhoIsOnline() :
+ // TRANSLATORS: who is online window name
+ Window(_("Who Is Online - Updating"), false, nullptr, "whoisonline.xml"),
+ mThread(nullptr),
+ mDownloadStatus(UPDATE_LIST),
+ mDownloadComplete(true),
+ mDownloadedBytes(0),
+ mMemoryBuffer(nullptr),
+ mCurlError(new char[CURL_ERROR_SIZE]),
+ mBrowserBox(new BrowserBox(this)),
+ mScrollArea(new ScrollArea(mBrowserBox, false)),
+ mUpdateTimer(0),
+ mOnlinePlayers(),
+ mOnlineNicks(),
+ // TRANSLATORS: who is online. button.
+ mUpdateButton(new Button(this, _("Update"), "update", this)),
+ mAllowUpdate(true),
+ mShowLevel(false),
+ mUpdateOnlineList(config.getBoolValue("updateOnlineList")),
+
+ mGroupFriends(true)
+{
+ mCurlError[0] = 0;
+ setWindowName("WhoIsOnline");
+
+ const int h = 350;
+ const int w = 200;
+ setDefaultSize(w, h, ImageRect::CENTER);
+ setVisible(false);
+ setCloseButton(true);
+ setResizable(true);
+ setStickyButtonLock(true);
+ setSaveVisible(true);
+
+ mUpdateButton->setEnabled(false);
+ mUpdateButton->setDimension(gcn::Rectangle(5, 5, w - 10, 20 + 5));
+
+ 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);
+
+ setLocationRelativeTo(getParent());
+
+ loadWindowState();
+ enableVisibleSound(true);
+
+ download();
+
+ widgetResized(gcn::Event(nullptr));
+ config.addListener("updateOnlineList", this);
+ config.addListener("groupFriends", this);
+ mGroupFriends = config.getBoolValue("groupFriends");
+}
+
+WhoIsOnline::~WhoIsOnline()
+{
+ config.removeListeners(this);
+
+ if (mThread && SDL_GetThreadID(mThread))
+ SDL_WaitThread(mThread, nullptr);
+
+ free(mMemoryBuffer);
+ mMemoryBuffer = nullptr;
+
+ // Remove possibly leftover temporary download
+ delete []mCurlError;
+
+ FOR_EACH (std::set<OnlinePlayer*>::iterator, itd, mOnlinePlayers)
+ delete *itd;
+ mOnlinePlayers.clear();
+ mOnlineNicks.clear();
+}
+
+void WhoIsOnline::handleLink(const std::string& link, gcn::MouseEvent *event)
+{
+ if (!event || event->getButton() == gcn::MouseEvent::LEFT)
+ {
+ if (chatWindow)
+ {
+ const std::string text = decodeLinkText(link);
+ if (config.getBoolValue("whispertab"))
+ {
+ chatWindow->localChatInput("/q " + text);
+ }
+ else
+ {
+ chatWindow->addInputText(std::string("/w \"").append(
+ text).append("\" "));
+ }
+ }
+ }
+ else if (event->getButton() == gcn::MouseEvent::RIGHT)
+ {
+ if (player_node && link == player_node->getName())
+ return;
+
+ if (viewport)
+ {
+ if (actorSpriteManager)
+ {
+ const std::string text = decodeLinkText(link);
+ Being *const being = actorSpriteManager->findBeingByName(
+ text, Being::PLAYER);
+
+ if (being && viewport)
+ {
+ viewport->showPopup(being);
+ return;
+ }
+ }
+ viewport->showPlayerPopup(link);
+ }
+ }
+}
+
+void WhoIsOnline::updateWindow(std::vector<OnlinePlayer*> &friends,
+ std::vector<OnlinePlayer*> &neutral,
+ std::vector<OnlinePlayer*> &disregard,
+ std::vector<OnlinePlayer*> enemy,
+ size_t numOnline)
+{
+ // Set window caption
+ // TRANSLATORS: who is online window name
+ setCaption(_("Who Is Online - ") + toString(numOnline));
+
+ // List the online people
+ std::sort(friends.begin(), friends.end(), nameCompare);
+ std::sort(neutral.begin(), neutral.end(), nameCompare);
+ std::sort(disregard.begin(), disregard.end(), nameCompare);
+ bool addedFromSection(false);
+ FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, friends)
+ {
+ mBrowserBox->addRow((*it)->getText());
+ addedFromSection = true;
+ }
+ if (addedFromSection == true)
+ {
+ mBrowserBox->addRow("---");
+ addedFromSection = false;
+ }
+ FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, enemy)
+ {
+ mBrowserBox->addRow((*it)->getText());
+ addedFromSection = true;
+ }
+ if (addedFromSection == true)
+ {
+ mBrowserBox->addRow("---");
+ addedFromSection = false;
+ }
+ FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, neutral)
+ {
+ mBrowserBox->addRow((*it)->getText());
+ addedFromSection = true;
+ }
+ if (addedFromSection == true && !disregard.empty())
+ mBrowserBox->addRow("---");
+
+ FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, disregard)
+ mBrowserBox->addRow((*it)->getText());
+
+ if (mScrollArea->getVerticalMaxScroll() <
+ mScrollArea->getVerticalScrollAmount())
+ {
+ mScrollArea->setVerticalScrollAmount(
+ mScrollArea->getVerticalMaxScroll());
+ }
+}
+
+void WhoIsOnline::loadList(std::vector<OnlinePlayer*> &list)
+{
+ mBrowserBox->clearRows();
+ const size_t numOnline = list.size();
+ std::vector<OnlinePlayer*> friends;
+ std::vector<OnlinePlayer*> neutral;
+ std::vector<OnlinePlayer*> disregard;
+ std::vector<OnlinePlayer*> enemy;
+
+ FOR_EACH (std::set<OnlinePlayer*>::iterator, itd, mOnlinePlayers)
+ delete *itd;
+ mOnlinePlayers.clear();
+ mOnlineNicks.clear();
+
+ mShowLevel = config.getBoolValue("showlevel");
+
+ FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, list)
+ {
+ OnlinePlayer *player = *it;
+ const std::string nick = player->getNick();
+ mOnlinePlayers.insert(player);
+ mOnlineNicks.insert(nick);
+
+ if (!mShowLevel)
+ player->setLevel(0);
+
+ switch (player_relations.getRelation(nick))
+ {
+ case PlayerRelation::NEUTRAL:
+ default:
+ setNeutralColor(player);
+ neutral.push_back(player);
+ break;
+
+ case PlayerRelation::FRIEND:
+ player->setText("2");
+ if (mGroupFriends)
+ friends.push_back(player);
+ else
+ neutral.push_back(player);
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ case PlayerRelation::BLACKLISTED:
+ player->setText("8");
+ disregard.push_back(player);
+ break;
+
+ case PlayerRelation::ENEMY2:
+ player->setText("1");
+ enemy.push_back(player);
+ break;
+
+ case PlayerRelation::IGNORED:
+ case PlayerRelation::ERASED:
+ // Ignore the ignored.
+ break;
+ }
+ }
+
+ updateWindow(friends, neutral, disregard, enemy, numOnline);
+ if (!mOnlineNicks.empty())
+ {
+ if (chatWindow)
+ chatWindow->updateOnline(mOnlineNicks);
+ if (socialWindow)
+ socialWindow->updateActiveList();
+ }
+ updateSize();
+}
+
+void WhoIsOnline::loadWebList()
+{
+ 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<OnlinePlayer*> friends;
+ std::vector<OnlinePlayer*> neutral;
+ std::vector<OnlinePlayer*> disregard;
+ std::vector<OnlinePlayer*> enemy;
+
+ // Tokenize and add each line separately
+ char *line = strtok(mMemoryBuffer, "\n");
+ const std::string gmText("(GM)");
+
+ FOR_EACH (std::set<OnlinePlayer*>::iterator, itd, mOnlinePlayers)
+ delete *itd;
+
+ mOnlinePlayers.clear();
+ mOnlineNicks.clear();
+
+ mShowLevel = config.getBoolValue("showlevel");
+
+ while (line)
+ {
+ std::string nick;
+ lineStr = line;
+ trim(lineStr);
+ if (listStarted == true)
+ {
+ if (lineStr.find(" users are online.") == std::string::npos)
+ {
+ if (lineStr.length() > 24)
+ {
+ nick = lineStr.substr(0, 24);
+ lineStr = lineStr.substr(25);
+ }
+ else
+ {
+ nick = lineStr;
+ lineStr.clear();
+ }
+ trim(nick);
+
+ bool isGM(false);
+ size_t pos = lineStr.find(gmText, 0);
+ if (pos != std::string::npos)
+ {
+ lineStr = lineStr.substr(pos + gmText.length());
+ isGM = true;
+ }
+
+ trim(lineStr);
+ pos = lineStr.find("/", 0);
+
+ if (pos != std::string::npos)
+ lineStr = lineStr.substr(0, pos);
+
+ int level = 0;
+ if (!lineStr.empty())
+ level = atoi(lineStr.c_str());
+
+ if (actorSpriteManager)
+ {
+ Being *const being = actorSpriteManager->findBeingByName(
+ nick, Being::PLAYER);
+ if (being)
+ {
+ if (level > 0)
+ {
+ being->setLevel(level);
+ being->updateName();
+ }
+ else
+ {
+ if (being->getLevel() > 1)
+ level = being->getLevel();
+ }
+ }
+ }
+
+ if (!mShowLevel)
+ level = 0;
+
+ OnlinePlayer *const player = new OnlinePlayer(nick,
+ static_cast<signed char>(255), level,
+ GENDER_UNSPECIFIED, -1);
+ mOnlinePlayers.insert(player);
+ mOnlineNicks.insert(nick);
+
+ if (isGM)
+ player->setIsGM(true);
+
+ numOnline++;
+ switch (player_relations.getRelation(nick))
+ {
+ case PlayerRelation::NEUTRAL:
+ default:
+ setNeutralColor(player);
+ neutral.push_back(player);
+ break;
+
+ case PlayerRelation::FRIEND:
+ player->setText("2");
+ if (mGroupFriends)
+ friends.push_back(player);
+ else
+ neutral.push_back(player);
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ case PlayerRelation::BLACKLISTED:
+ player->setText("8");
+ disregard.push_back(player);
+ break;
+
+ case PlayerRelation::ENEMY2:
+ player->setText("1");
+ enemy.push_back(player);
+ break;
+
+ case PlayerRelation::IGNORED:
+ case PlayerRelation::ERASED:
+ // Ignore the ignored.
+ break;
+ }
+ }
+ }
+ else if (lineStr.find("------------------------------")
+ != std::string::npos)
+ {
+ listStarted = true;
+ }
+ line = strtok(nullptr, "\n");
+ }
+
+ updateWindow(friends, neutral, disregard, enemy, numOnline);
+
+ // Free the memory buffer now that we don't need it anymore
+ free(mMemoryBuffer);
+ mMemoryBuffer = nullptr;
+}
+
+size_t WhoIsOnline::memoryWrite(void *ptr, size_t size,
+ size_t nmemb, FILE *stream)
+{
+ if (!stream)
+ return 0;
+
+ WhoIsOnline *const wio = reinterpret_cast<WhoIsOnline *>(stream);
+ const 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 *const wio = reinterpret_cast<WhoIsOnline *>(ptr);
+ CURLcode res;
+ const std::string url(client->getOnlineUrl() + "/online.txt");
+
+ while (attempts < 1 && !wio->mDownloadComplete)
+ {
+ CURL *curl = curl_easy_init();
+ if (curl)
+ {
+ if (!wio->mAllowUpdate)
+ {
+ curl_easy_cleanup(curl);
+ curl = nullptr;
+ 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.getStringValue("appName").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, 30);
+ Net::Download::addProxy(curl);
+ Net::Download::secureCurl(curl);
+
+ // Make sure the resources2.txt and news.txt aren't cached,
+ // in order to always get the latest version.
+ struct curl_slist *pHeaders = nullptr;
+ 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++;
+ curl_easy_cleanup(curl);
+ curl_slist_free_all(pHeaders);
+ curl = nullptr;
+ 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;
+ return 0;
+}
+
+void WhoIsOnline::download()
+{
+ if (serverVersion < 3)
+ {
+ mDownloadComplete = true;
+ if (mThread && SDL_GetThreadID(mThread))
+ SDL_WaitThread(mThread, nullptr);
+
+ mDownloadComplete = false;
+ mThread = SDL::createThread(WhoIsOnline::downloadThread,
+ "whoisonline", this);
+ if (mThread == nullptr)
+ mDownloadStatus = UPDATE_ERROR;
+ }
+ else
+ {
+ if (client->limitPackets(PACKET_ONLINELIST))
+ Net::getPlayerHandler()->requestOnlineList();
+ }
+}
+
+void WhoIsOnline::logic()
+{
+ BLOCK_START("WhoIsOnline::logic")
+ mScrollArea->logic();
+ BLOCK_END("WhoIsOnline::logic")
+}
+
+void WhoIsOnline::slowLogic()
+{
+ if (!mAllowUpdate)
+ return;
+
+ BLOCK_START("WhoIsOnline::slowLogic")
+ if (mUpdateTimer == 0)
+ mUpdateTimer = cur_time;
+
+ const double timeDiff = difftime(cur_time, mUpdateTimer);
+ const int timeLimit = isWindowVisible() ? 20 : 120;
+
+ if (mUpdateOnlineList && timeDiff >= timeLimit
+ && mDownloadStatus != UPDATE_LIST)
+ {
+ if (mDownloadComplete == true)
+ {
+ // TRANSLATORS: who is online window name
+ 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;
+ // TRANSLATORS: who is online window name
+ setCaption(_("Who Is Online - error"));
+ mUpdateButton->setEnabled(true);
+ mUpdateTimer = cur_time + 240;
+ mDownloadComplete = true;
+ updateSize();
+ break;
+ case UPDATE_LIST:
+ if (mDownloadComplete == true)
+ {
+ loadWebList();
+ mDownloadStatus = UPDATE_COMPLETE;
+ mUpdateButton->setEnabled(true);
+ mUpdateTimer = 0;
+ updateSize();
+ if (!mOnlineNicks.empty())
+ {
+ if (chatWindow)
+ chatWindow->updateOnline(mOnlineNicks);
+ if (socialWindow)
+ socialWindow->updateActiveList();
+ }
+ }
+ break;
+ case UPDATE_COMPLETE:
+ default:
+ break;
+ }
+ BLOCK_END("WhoIsOnline::slowLogic")
+}
+
+void WhoIsOnline::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "update")
+ {
+ if (serverVersion < 3)
+ {
+ if (mDownloadStatus == UPDATE_COMPLETE)
+ {
+ mUpdateTimer = cur_time - 20;
+ if (mUpdateButton)
+ mUpdateButton->setEnabled(false);
+ // TRANSLATORS: who is online window name
+ setCaption(_("Who Is Online - Update"));
+ if (mThread && SDL_GetThreadID(mThread))
+ {
+ SDL_WaitThread(mThread, nullptr);
+ mThread = nullptr;
+ }
+ mDownloadComplete = true;
+ }
+ }
+ else
+ {
+ if (client->limitPackets(PACKET_ONLINELIST))
+ {
+ mUpdateTimer = cur_time;
+ Net::getPlayerHandler()->requestOnlineList();
+ }
+ }
+ }
+}
+
+void WhoIsOnline::widgetResized(const gcn::Event &event)
+{
+ Window::widgetResized(event);
+ updateSize();
+}
+
+void WhoIsOnline::updateSize()
+{
+ const gcn::Rectangle area = getChildrenArea();
+ if (mUpdateButton)
+ mUpdateButton->setWidth(area.width - 10);
+
+ if (mScrollArea)
+ mScrollArea->setSize(area.width - 10, area.height - 10 - 30);
+ if (mBrowserBox)
+ mBrowserBox->setWidth(area.width - 10);
+}
+
+const std::string WhoIsOnline::prepareNick(const std::string &nick,
+ const int level,
+ const std::string &color) const
+{
+ const std::string text = encodeLinkText(nick);
+ if (mShowLevel && level > 1)
+ {
+ return strprintf("@@%s|##%s%s (%d)@@", text.c_str(),
+ color.c_str(), text.c_str(), level);
+ }
+ else
+ {
+ return strprintf("@@%s|##%s%s@@", text.c_str(),
+ color.c_str(), text.c_str());
+ }
+}
+
+void WhoIsOnline::optionChanged(const std::string &name)
+{
+ if (name == "updateOnlineList")
+ mUpdateOnlineList = config.getBoolValue("updateOnlineList");
+ else if (name == "groupFriends")
+ mGroupFriends = config.getBoolValue("groupFriends");
+}
+
+void WhoIsOnline::setNeutralColor(OnlinePlayer *const player)
+{
+ if (!player)
+ return;
+
+ if (actorSpriteManager && player_node)
+ {
+ const std::string &nick = player->getNick();
+ if (nick == player_node->getName())
+ {
+ player->setText("s");
+ return;
+ }
+ if (player_node->isInParty())
+ {
+ const Party *const party = player_node->getParty();
+ if (party)
+ {
+ if (party->getMember(nick))
+ {
+ player->setText("P");
+ return;
+ }
+ }
+ }
+
+ const Being *const being = actorSpriteManager->findBeingByName(nick);
+ if (being)
+ {
+ const Guild *const guild2 = player_node->getGuild();
+ if (guild2)
+ {
+ const Guild *const guild1 = being->getGuild();
+ if (guild1)
+ {
+ if (guild1->getId() == guild2->getId()
+ || guild2->getMember(nick))
+ {
+ player->setText("U");
+ return;
+ }
+ }
+ else if (guild2->isMember(nick))
+ {
+ player->setText("U");
+ return;
+ }
+ }
+ }
+ const Guild *const guild3 = Guild::getGuild(1);
+ if (guild3 && guild3->isMember(nick))
+ {
+ player->setText("U");
+ return;
+ }
+ }
+ player->setText("0");
+}
+
+void WhoIsOnline::getPlayerNames(StringVect &names)
+{
+ names.clear();
+ FOR_EACH (std::set<std::string>::const_iterator, it, mOnlineNicks)
+ names.push_back(*it);
+}
+
+void OnlinePlayer::setText(std::string color)
+{
+ mText.clear();
+
+ if (mStatus != 255 && actorSpriteManager)
+ {
+ Being *const being = actorSpriteManager->findBeingByName(
+ mNick, Being::PLAYER);
+ if (being)
+ {
+ being->setState(mStatus);
+ // for now highlight versions > 3
+ if (mVersion > 3)
+ being->setAdvanced(true);
+ }
+ }
+
+ if ((mStatus != 255 && mStatus & Being::FLAG_GM) || mIsGM)
+ mText.append("(GM) ");
+
+ if (mLevel > 0)
+ mText.append(strprintf("%d", mLevel));
+
+ if (mGender == GENDER_FEMALE)
+ mText.append("\u2640");
+ else if (mGender == GENDER_MALE)
+ mText.append("\u2642");
+
+ if (mStatus > 0 && mStatus != 255)
+ {
+ if (mStatus & Being::FLAG_SHOP)
+ mText.append("$");
+ if (mStatus & Being::FLAG_AWAY)
+ {
+ // TRANSLATORS: this away status writed in player nick
+ mText.append(_("A"));
+ }
+ if (mStatus & Being::FLAG_INACTIVE)
+ {
+ // TRANSLATORS: this inactive status writed in player nick
+ mText.append(_("I"));
+ }
+
+ if (mStatus & Being::FLAG_GM && color == "0")
+ color = "2";
+ }
+ else if (mIsGM && color == "0")
+ {
+ color = "2";
+ }
+
+ if (mVersion > 0)
+ mText.append(strprintf(" - %d", mVersion));
+
+ const std::string text = encodeLinkText(mNick);
+ mText = strprintf("@@%s|##%s%s %s@@", text.c_str(), color.c_str(),
+ text.c_str(), mText.c_str());
+}
diff --git a/src/gui/windows/whoisonline.h b/src/gui/windows/whoisonline.h
new file mode 100644
index 000000000..860eeacda
--- /dev/null
+++ b/src/gui/windows/whoisonline.h
@@ -0,0 +1,223 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 Andrei Karas
+ * Copyright (C) 2011-2013 The ManaPlus developers
+ *
+ * This file is part of The ManaPlus 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_WHOISONLINE_H
+#define GUI_WHOISONLINE_H
+
+#include "configlistener.h"
+
+#include "gui/widgets/linkhandler.h"
+#include "gui/widgets/window.h"
+
+#include <set>
+
+#include <guichan/actionlistener.hpp>
+
+class BrowserBox;
+class Button;
+class ScrollArea;
+
+struct SDL_Thread;
+
+class OnlinePlayer final
+{
+ public:
+ OnlinePlayer(const std::string &nick, const unsigned char status,
+ const char level, const unsigned char gender,
+ const char version) :
+ mNick(nick),
+ mText(""),
+ mStatus(status),
+ mLevel(level),
+ mVersion(version),
+ mGender(gender),
+ mIsGM(false)
+ {
+ }
+
+ A_DELETE_COPY(OnlinePlayer)
+
+ const std::string getNick() const A_WARN_UNUSED
+ { return mNick; }
+
+ unsigned char getStaus() const A_WARN_UNUSED
+ { return mStatus; }
+
+ void setIsGM(const bool b)
+ { mIsGM = b; }
+
+ char getVersion() const A_WARN_UNUSED
+ { return mVersion; }
+
+ char getLevel() const A_WARN_UNUSED
+ { return mLevel; }
+
+ const std::string getText() const A_WARN_UNUSED
+ { return mText; }
+
+ void setText(std::string str);
+
+ void setLevel(const char level)
+ { mLevel = level; }
+
+ private:
+ std::string mNick;
+
+ std::string mText;
+
+ unsigned char mStatus;
+
+ char mLevel;
+
+ char mVersion;
+
+ unsigned char mGender;
+
+ bool mIsGM;
+};
+
+/**
+ * Update progress window GUI
+ *
+ * \ingroup GUI
+ */
+class WhoIsOnline final : public Window,
+ public LinkHandler,
+ public gcn::ActionListener,
+ public ConfigListener
+{
+public:
+ /**
+ * Constructor.
+ */
+ WhoIsOnline();
+
+ A_DELETE_COPY(WhoIsOnline)
+
+ /**
+ * Destructor
+ */
+ ~WhoIsOnline();
+
+ /**
+ * Loads and display online list from the memory buffer.
+ */
+ void loadWebList();
+
+ void loadList(std::vector<OnlinePlayer*> &list);
+
+ void handleLink(const std::string& link, gcn::MouseEvent *event) override;
+
+ void logic() override;
+
+ void slowLogic();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void widgetResized(const gcn::Event &event) override;
+
+ const std::set<OnlinePlayer*> &getOnlinePlayers() const A_WARN_UNUSED
+ { return mOnlinePlayers; }
+
+ const std::set<std::string> &getOnlineNicks() const A_WARN_UNUSED
+ { return mOnlineNicks; }
+
+ void setAllowUpdate(const bool n)
+ { mAllowUpdate = n; }
+
+ void optionChanged(const std::string &name) override;
+
+ void updateList(StringVect &list);
+
+ void readFromWeb();
+
+ void setNeutralColor(OnlinePlayer *const player);
+
+ void getPlayerNames(StringVect &names);
+
+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(const std::string &nick, const int level,
+ const std::string &color)
+ const A_WARN_UNUSED;
+
+ void updateWindow(std::vector<OnlinePlayer*> &friends,
+ std::vector<OnlinePlayer*> &neutral,
+ std::vector<OnlinePlayer*> &disregard,
+ std::vector<OnlinePlayer*> enemy,
+ size_t numOnline);
+
+ 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<OnlinePlayer*> mOnlinePlayers;
+ std::set<std::string> mOnlineNicks;
+
+ Button *mUpdateButton;
+ bool mAllowUpdate;
+ bool mShowLevel;
+ bool mUpdateOnlineList;
+ bool mGroupFriends;
+};
+
+extern WhoIsOnline *whoIsOnline;
+
+#endif // GUI_WHOISONLINE_H
diff --git a/src/gui/windows/worldselectdialog.cpp b/src/gui/windows/worldselectdialog.cpp
new file mode 100644
index 000000000..bd46df9a1
--- /dev/null
+++ b/src/gui/windows/worldselectdialog.cpp
@@ -0,0 +1,166 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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/windows/worldselectdialog.h"
+
+#include "client.h"
+
+#include "input/keydata.h"
+#include "input/keyevent.h"
+
+#include "gui/widgets/button.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "net/loginhandler.h"
+#include "net/net.h"
+
+#include "utils/gettext.h"
+
+#include "debug.h"
+
+extern WorldInfo **server_info;
+
+/**
+ * The list model for the server list.
+ */
+class WorldListModel final : public gcn::ListModel
+{
+ public:
+ explicit WorldListModel(Worlds worlds) :
+ mWorlds(worlds)
+ {
+ }
+
+ A_DELETE_COPY(WorldListModel)
+
+ ~WorldListModel()
+ { }
+
+ int getNumberOfElements() override
+ {
+ return static_cast<int>(mWorlds.size());
+ }
+
+ std::string getElementAt(int i) override
+ {
+ const WorldInfo *const si = mWorlds[i];
+ if (si)
+ {
+ return std::string(si->name).append(" (").append(
+ toString(si->online_users)).append(")");
+ }
+ else
+ {
+ return "???";
+ }
+ }
+ private:
+ Worlds mWorlds;
+};
+
+WorldSelectDialog::WorldSelectDialog(Worlds worlds):
+ // TRANSLATORS: world select dialog name
+ Window(_("Select World"), false, nullptr, "world.xml"),
+ gcn::ActionListener(),
+ gcn::KeyListener(),
+ mWorldListModel(new WorldListModel(worlds)),
+ mWorldList(new ListBox(this, mWorldListModel, "")),
+ // TRANSLATORS: world dialog button
+ mChangeLoginButton(new Button(this, _("Change Login"), "login", this)),
+ // TRANSLATORS: world dialog button
+ mChooseWorld(new Button(this, _("Choose World"), "world", this))
+{
+ ScrollArea *const worldsScroll = new ScrollArea(mWorldList,
+ getOptionBool("showbackground"), "world_background.xml");
+
+ 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.empty())
+ {
+ // 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 = nullptr;
+}
+
+void WorldSelectDialog::action(const gcn::ActionEvent &event)
+{
+ const std::string &eventId = event.getId();
+ if (eventId == "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 (eventId == "login")
+ {
+ client->setState(STATE_PRE_LOGIN);
+ }
+}
+
+void WorldSelectDialog::keyPressed(gcn::KeyEvent &keyEvent)
+{
+ const int actionId = static_cast<KeyEvent*>(
+ &keyEvent)->getActionId();
+
+ if (actionId == static_cast<int>(Input::KEY_GUI_CANCEL))
+ {
+ action(gcn::ActionEvent(nullptr,
+ mChangeLoginButton->getActionEventId()));
+ }
+ else if (actionId == static_cast<int>(Input::KEY_GUI_SELECT)
+ || actionId == static_cast<int>(Input::KEY_GUI_SELECT2))
+ {
+ action(gcn::ActionEvent(nullptr, mChooseWorld->getActionEventId()));
+ }
+}
diff --git a/src/gui/windows/worldselectdialog.h b/src/gui/windows/worldselectdialog.h
new file mode 100644
index 000000000..ace7fd63c
--- /dev/null
+++ b/src/gui/windows/worldselectdialog.h
@@ -0,0 +1,74 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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_WORLDSELECTDIALOG_H
+#define GUI_WORLDSELECTDIALOG_H
+
+#include "gui/widgets/window.h"
+
+#include "net/worldinfo.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+
+class Button;
+class ListBox;
+class WorldListModel;
+
+/**
+ * The server select dialog.
+ *
+ * \ingroup Interface
+ */
+class WorldSelectDialog final : public Window, public gcn::ActionListener,
+ public gcn::KeyListener
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @see Window::Window
+ */
+ explicit WorldSelectDialog(Worlds worlds);
+
+ A_DELETE_COPY(WorldSelectDialog)
+
+ /**
+ * Destructor.
+ */
+ ~WorldSelectDialog();
+
+ /**
+ * Called when receiving actions from the widgets.
+ */
+ void action(const gcn::ActionEvent &event) override;
+
+ void keyPressed(gcn::KeyEvent &keyEvent) override;
+
+ private:
+ WorldListModel *mWorldListModel;
+ ListBox *mWorldList;
+ Button *mChangeLoginButton;
+ Button *mChooseWorld;
+};
+
+#endif // GUI_WORLDSELECTDIALOG_H