diff options
Diffstat (limited to 'src/gui/windows')
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 ¤tServer, + 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 ¤tServer = 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 |