diff options
Diffstat (limited to 'src/gui')
233 files changed, 48498 insertions, 0 deletions
diff --git a/src/gui/beingpopup.cpp b/src/gui/beingpopup.cpp new file mode 100644 index 000000000..6b3a25ee4 --- /dev/null +++ b/src/gui/beingpopup.cpp @@ -0,0 +1,142 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/beingpopup.h" + +#include "being.h" +#include "graphics.h" +#include "units.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "gui/widgets/label.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> + + +BeingPopup::BeingPopup(): + Popup("BeingPopup") +{ + // Being Name + mBeingName = new Label("A"); + mBeingName->setFont(boldFont); + mBeingName->setPosition(getPadding(), getPadding()); + + const int fontHeight = mBeingName->getHeight() + getPadding(); + + // Being's party + mBeingParty = new Label("A"); + mBeingParty->setPosition(getPadding(), fontHeight); + + // Being's party + mBeingGuild = new Label("A"); + mBeingGuild->setPosition(getPadding(), 2 * fontHeight); + + mBeingRank = new Label("A"); + mBeingRank->setPosition(getPadding(), 3 * fontHeight); + + add(mBeingName); + add(mBeingParty); + add(mBeingGuild); + add(mBeingRank); +} + +BeingPopup::~BeingPopup() +{ +} + +void BeingPopup::show(int x, int y, Being *b) +{ + if (!b) + { + setVisible(false); + return; + } + + Label *label1 = mBeingParty; + Label *label2 = mBeingGuild; + Label *label3 = mBeingRank; + + mBeingName->setCaption(b->getName()); + mBeingName->adjustSize(); + label1->setCaption(""); + label2->setCaption(""); + label3->setCaption(""); + + if (!(b->getPartyName().empty())) + { + label1->setCaption(strprintf(_("Party: %s"), + b->getPartyName().c_str())); + label1->adjustSize(); + } + else + { + label3 = label2; + label2 = label1; + label1 = 0; + } + + if (!(b->getGuildName().empty())) + { + label2->setCaption(strprintf(_("Guild: %s"), + b->getGuildName().c_str())); + label2->adjustSize(); + } + else + { + label3 = label2; + label2 = 0; + } + + if (b->getPvpRank() > 0) + { + label3->setCaption(strprintf(_("Pvp rank: %d"), b->getPvpRank())); + label3->adjustSize(); + } + else + { + label3 = 0; + } + + int minWidth = mBeingName->getWidth(); + if (label1 && label1->getWidth() > minWidth) + minWidth = label1->getWidth(); + if (label2 && label2->getWidth() > minWidth) + minWidth = label2->getWidth(); + if (label3 && label3->getWidth() > minWidth) + minWidth = label3->getWidth(); + + int height = getFont()->getHeight(); + if (label1) + height += getFont()->getHeight(); + if (label2) + height += getFont()->getHeight(); + if (label3) + height += getFont()->getHeight(); + + setContentSize(minWidth + 10, height + 10); + + position(x, y); + return; +} diff --git a/src/gui/beingpopup.h b/src/gui/beingpopup.h new file mode 100644 index 000000000..d43f8e105 --- /dev/null +++ b/src/gui/beingpopup.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BEINGPOPUP_H +#define BEINGPOPUP_H + +#include "gui/widgets/popup.h" + +class Being; +class Label; + +/** + * A popup that displays information about a being. + */ +class BeingPopup : public Popup +{ + public: + /** + * Constructor. Initializes the being popup. + */ + BeingPopup(); + + /** + * Destructor. Cleans up the being popup on deletion. + */ + ~BeingPopup(); + + /** + * Sets the info to be displayed given a particular player. + */ + void show(int x, int y, Being *b); + + // TODO: Add a version for monsters, NPCs, etc? + + private: + Label *mBeingName; + Label *mBeingParty; + Label *mBeingGuild; + Label *mBeingRank; +}; + +#endif // BEINGPOPUP_H diff --git a/src/gui/botcheckerwindow.cpp b/src/gui/botcheckerwindow.cpp new file mode 100644 index 000000000..f7bd7d060 --- /dev/null +++ b/src/gui/botcheckerwindow.cpp @@ -0,0 +1,413 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "botcheckerwindow.h" + +#include <SDL.h> +#include <SDL_thread.h> +#include <vector> +#include <algorithm> + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/table.h" + +#include "actorspritemanager.h" +#include "chat.h" +#include "configuration.h" +#include "localplayer.h" +#include "main.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + + +#define COLUMNS_NR 5 // name plus listbox +#define NAME_COLUMN 0 +#define TIME_COLUMN 1 + +#define ROW_HEIGHT 12 +// The following column widths really shouldn't be hardcoded but should scale with the size of the widget... excep +// that, right now, the widget doesn't exactly scale either. +#define NAME_COLUMN_WIDTH 185 +#define TIME_COLUMN_WIDTH 70 + +#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column) + +class UsersTableModel : public TableModel +{ +public: + UsersTableModel() : + mPlayers(0) + { + playersUpdated(); + } + + virtual ~UsersTableModel() + { + freeWidgets(); + } + + virtual int getRows() const + { + return static_cast<int>(mPlayers.size()); + } + + virtual int getColumns() const + { + return COLUMNS_NR; + } + + virtual int getRowHeight() const + { + return ROW_HEIGHT; + } + + virtual int getColumnWidth(int index) const + { + if (index == NAME_COLUMN) + return NAME_COLUMN_WIDTH; + else + return TIME_COLUMN_WIDTH; + } + + virtual void playersUpdated() + { + signalBeforeUpdate(); + + freeWidgets(); + mPlayers.clear(); + if (actorSpriteManager && botCheckerWindow + && botCheckerWindow->mEnabled) + { + std::set<ActorSprite*> beings = actorSpriteManager->getAll(); + ActorSprites::iterator i = beings.begin(); + for (ActorSprites::const_iterator i = beings.begin(); + i != beings.end(); i++) + { + Being *being = dynamic_cast<Being*>(*i); + + if (being && being->getType() == Being::PLAYER + && being != player_node && being->getName() != "") + { + mPlayers.push_back(being); + } + } + } + + unsigned int curTime = cur_time; + // set up widgets + for (unsigned int r = 0; r < mPlayers.size(); ++r) + { + if (!mPlayers.at(r)) + continue; + + std::string name = mPlayers.at(r)->getName(); + gcn::Widget *widget = new Label(name); + + mWidgets.push_back(widget); + + if (mPlayers.at(r)->getAttackTime() != 0) + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getAttackTime())); + } + else + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTestTime()) + "?"); + } + mWidgets.push_back(widget); + + if (mPlayers.at(r)->getTalkTime() != 0) + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTalkTime())); + } + else + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTestTime()) + "?"); + } + mWidgets.push_back(widget); + + if (mPlayers.at(r)->getMoveTime() != 0) + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getMoveTime())); + } + else + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTestTime()) + "?"); + } + mWidgets.push_back(widget); + + std::string str; + bool talkBot = false; + bool moveBot = false; + bool attackBot = false; + bool otherBot = false; + + if (curTime - mPlayers.at(r)->getTestTime() > 2 * 60) + { + int attack = curTime - (mPlayers.at(r)->getAttackTime() + ? mPlayers.at(r)->getAttackTime() + : mPlayers.at(r)->getTestTime()); + int talk = curTime - (mPlayers.at(r)->getTalkTime() + ? mPlayers.at(r)->getTalkTime() + : mPlayers.at(r)->getTestTime()) - attack; + int move = curTime - (mPlayers.at(r)->getMoveTime() + ? mPlayers.at(r)->getMoveTime() + : mPlayers.at(r)->getTestTime()) - attack; + int other = curTime - (mPlayers.at(r)->getOtherTime() + ? mPlayers.at(r)->getMoveTime() + : mPlayers.at(r)->getOtherTime()) - attack; + + if (attack < 2 * 60) + attackBot = true; + + // attacking but not talking more than 2 minutes + if (talk > 2 * 60 && talk > 2 * 60) + { + talkBot = true; + str += toString((talk) / 60) + " "; + } + + // attacking but not moving more than 2 minutes + if (move > 2 * 60 && move > 2 * 60) + { + moveBot = true; + str += toString((move) / 60); + } + + // attacking but not other activity more than 2 minutes + if (move > 2 * 60 && other > 2 * 60) + otherBot = true; + } + + if (str.length() > 0) + { + if (attackBot && talkBot && moveBot && otherBot) + str = "bot!! " + str; + else if (attackBot && talkBot && moveBot) + str = "bot! " + str; + else if (talkBot && moveBot) + str = "bot " + str; + else if (talkBot || moveBot) + str = "bot? " + str; + } + else + { + str = "ok"; + } + + widget = new Label(str); + mWidgets.push_back(widget); + + } + + signalAfterUpdate(); + } + + virtual void updateModelInRow(int row _UNUSED_) + { + } + + + virtual gcn::Widget *getElementAt(int row, int column) const + { + return mWidgets[WIDGET_AT(row, column)]; + } + + virtual void freeWidgets() + { + for (std::vector<gcn::Widget *>::const_iterator it = mWidgets.begin(); + it != mWidgets.end(); it++) + { + delete *it; + } + + mWidgets.clear(); + } + +protected: + std::vector<Being*> mPlayers; + std::vector<gcn::Widget*> mWidgets; +}; + + +BotCheckerWindow::BotCheckerWindow(): + Window(_("Bot Checker")), + mEnabled(false) +{ + int w = 500; + int h = 250; + + mLastUpdateTime = 0; + mNeedUpdate = false; + + mTableModel = new UsersTableModel(); + mTable = new GuiTable(mTableModel); + mTable->setOpaque(false); + mTable->setLinewiseSelection(true); + mTable->setWrappingEnabled(true); + mTable->setActionEventId("skill"); + mTable->addActionListener(this); + + mPlayerTableTitleModel = new StaticTableModel(1, COLUMNS_NR); + mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH); + + for (int f = 0; f < 4; f++) + { + mPlayerTableTitleModel->fixColumnWidth(TIME_COLUMN + f, + TIME_COLUMN_WIDTH); + } + + mPlayerTitleTable = new GuiTable(mPlayerTableTitleModel); + //mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf)); + mPlayerTitleTable->setHeight(1); + + mPlayerTableTitleModel->set(0, 0, new Label(_("Name"))); + mPlayerTableTitleModel->set(0, 1, new Label(_("Attack"))); + mPlayerTableTitleModel->set(0, 2, new Label(_("Talk"))); + mPlayerTableTitleModel->set(0, 3, new Label(_("Move"))); + mPlayerTableTitleModel->set(0, 4, new Label(_("Result"))); + + mPlayerTitleTable->setLinewiseSelection(true); + + setWindowName("BotCheckerWindow"); + setCloseButton(true); + setDefaultSize(w, h, ImageRect::CENTER); + + playersScrollArea = new ScrollArea(mTable); + + mIncButton = new Button(_("Reset"), "reset", this); + playersScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mPlayerTitleTable->setPosition(getPadding(), getPadding()); + mPlayerTitleTable->setWidth(w - 10); + mPlayerTitleTable->setHeight(20); + + playersScrollArea->setPosition(getPadding(), 20 + 2*getPadding()); + playersScrollArea->setWidth(w - 15); + playersScrollArea->setHeight(h - 80); + + mIncButton->setPosition(getPadding(), 190 + 3*getPadding()); + mIncButton->setWidth(80); + mIncButton->setHeight(20); + + add(mPlayerTitleTable); + add(playersScrollArea); + add(mIncButton); + + center(); + + setWidth(w); + setHeight(h); + loadWindowState(); + + config.addListener("enableBotCheker", this); + mEnabled = config.getBoolValue("enableBotCheker"); +} + +BotCheckerWindow::~BotCheckerWindow() +{ + config.removeListener("enableBotCheker", this); +} + +void BotCheckerWindow::logic() +{ + if (mEnabled && mTableModel) + { + unsigned int nowTime = cur_time; + if (nowTime - mLastUpdateTime > 5 && mNeedUpdate) + { + mTableModel->playersUpdated(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + else if (nowTime - mLastUpdateTime > 15) + { + mTableModel->playersUpdated(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + } + + Window::logic(); +} + +void BotCheckerWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "reset") + { + reset(); + mNeedUpdate = true; + } + +} + +void BotCheckerWindow::update() +{ +} + +void BotCheckerWindow::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); +} + +void BotCheckerWindow::updateList() +{ + if (mTableModel) + mNeedUpdate = true; +} + +void BotCheckerWindow::reset() +{ + if (actorSpriteManager) + { + std::set<ActorSprite*> beings = actorSpriteManager->getAll(); + ActorSprites::iterator i = beings.begin(); + for (ActorSprites::const_iterator i = beings.begin(); + i != beings.end(); i++) + { + Being *being = dynamic_cast<Being*>(*i); + + if (being && being->getType() == Being::PLAYER + && being != player_node && being->getName() != "") + { + being->resetCounters(); + } + } + } +} + +void BotCheckerWindow::optionChanged(const std::string &name) +{ + if (name == "enableBotCheker") + mEnabled = config.getBoolValue("enableBotCheker"); +} diff --git a/src/gui/botcheckerwindow.h b/src/gui/botcheckerwindow.h new file mode 100644 index 000000000..cb225d3e7 --- /dev/null +++ b/src/gui/botcheckerwindow.h @@ -0,0 +1,95 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef BOTCHECKER_H +#define BOTCHECKER_H + +#include "configlistener.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +#include <vector> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +struct BOTCHK +{ + short id; /**< Index into "botchecker_db" array */ + short lv, sp; +}; + +class GuiTable; +class ScrollArea; +class UsersTableModel; +class StaticTableModel; + +class BotCheckerWindow : public Window, public gcn::ActionListener, + public ConfigListener +{ + public: + friend class UsersTableModel; + + /** + * Constructor. + */ + BotCheckerWindow(); + + /** + * Destructor. + */ + ~BotCheckerWindow(); + + void action(const gcn::ActionEvent &event); + + void update(); + + void logic(); + + void widgetResized(const gcn::Event &event); + + void updateList(); + + void reset(); + + void optionChanged(const std::string &name); + + private: + GuiTable *mTable; + ScrollArea *playersScrollArea; + UsersTableModel *mTableModel; + StaticTableModel *mPlayerTableTitleModel; + GuiTable *mPlayerTitleTable; + gcn::Button *mIncButton; + int mLastUpdateTime; + bool mNeedUpdate; + bool mEnabled; +}; + +extern BotCheckerWindow *botCheckerWindow; + +#endif diff --git a/src/gui/buy.cpp b/src/gui/buy.cpp new file mode 100644 index 000000000..2be6c65a4 --- /dev/null +++ b/src/gui/buy.cpp @@ -0,0 +1,323 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/buy.h" + +#include "shopitem.h" +#include "units.h" + +#include "gui/setup.h" +#include "gui/trade.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shopitems.h" +#include "gui/widgets/shoplistbox.h" +#include "gui/widgets/slider.h" + +#include "shopitem.h" +#include "units.h" + +#include "net/buysellhandler.h" +#include "net/net.h" +#include "net/npchandler.h" + +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +BuyDialog::DialogList BuyDialog::instances; + +BuyDialog::BuyDialog(int npcId): + Window(_("Buy")), + mNpcId(npcId), mMoney(0), mAmountItems(0), mMaxItems(0), mNick("") +{ + init(); +} + +BuyDialog::BuyDialog(std::string nick): + Window(_("Buy")), + mNpcId(-1), mMoney(0), mAmountItems(0), mMaxItems(0), mNick(nick) +{ + init(); + logger->log("BuyDialog::BuyDialog nick:" + mNick); +} + +void BuyDialog::init() +{ + setWindowName("Buy"); + setResizable(true); + setCloseButton(true); + setMinWidth(260); + setMinHeight(230); + setDefaultSize(260, 230, ImageRect::CENTER); + + mShopItems = new ShopItems; + + mShopItemList = new ShopListBox(mShopItems, mShopItems); + mScrollArea = new ScrollArea(mShopItemList); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mSlider = new Slider(1.0); + mQuantityLabel = new Label(strprintf("%d / %d", mAmountItems, mMaxItems)); + mQuantityLabel->setAlignment(gcn::Graphics::CENTER); + mMoneyLabel = new Label(strprintf(_("Price: %s / Total: %s"), + "", "")); + + // TRANSLATORS: This is a narrow symbol used to denote 'increasing'. + // You may change this symbol if your language uses another. + mIncreaseButton = new Button(_("+"), "inc", this); + // TRANSLATORS: This is a narrow symbol used to denote 'decreasing'. + // You may change this symbol if your language uses another. + mDecreaseButton = new Button(_("-"), "dec", this); + mBuyButton = new Button(_("Buy"), "buy", this); + mQuitButton = new Button(_("Quit"), "quit", this); + mAddMaxButton = new Button(_("Max"), "max", this); + + mDecreaseButton->adjustSize(); + mDecreaseButton->setWidth(mIncreaseButton->getWidth()); + + mIncreaseButton->setEnabled(false); + mDecreaseButton->setEnabled(false); + mBuyButton->setEnabled(false); + mSlider->setEnabled(false); + + mSlider->setActionEventId("slider"); + mSlider->addActionListener(this); + mShopItemList->addSelectionListener(this); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mScrollArea, 8, 5).setPadding(3); + place(0, 5, mDecreaseButton); + place(1, 5, mSlider, 3); + place(4, 5, mIncreaseButton); + place(5, 5, mQuantityLabel, 2); + place(7, 5, mAddMaxButton); + place(0, 6, mMoneyLabel, 8); + place(6, 7, mBuyButton); + place(7, 7, mQuitButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); +} + +BuyDialog::~BuyDialog() +{ + delete mShopItems; + mShopItems = 0; + + instances.remove(this); +} + +void BuyDialog::setMoney(int amount) +{ + mMoney = amount; + mShopItemList->setPlayersMoney(amount); + + updateButtonsAndLabels(); +} + +void BuyDialog::reset() +{ + mShopItems->clear(); + mShopItemList->adjustSize(); + + // Reset previous selected items to prevent failing asserts + mShopItemList->setSelected(-1); + mSlider->setValue(0); + + setMoney(0); +} + +void BuyDialog::addItem(int id, int amount, int price) +{ + mShopItems->addItem(id, amount, price); + mShopItemList->adjustSize(); +} + +void BuyDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "quit") + { + close(); + return; + } + + int selectedItem = mShopItemList->getSelected(); + + // The following actions require a valid selection + if (selectedItem < 0 || + selectedItem >= static_cast<int>(mShopItems->getNumberOfElements())) + { + return; + } + + if (event.getId() == "slider") + { + mAmountItems = static_cast<int>(mSlider->getValue()); + updateButtonsAndLabels(); + } + else if (event.getId() == "inc" && mAmountItems < mMaxItems) + { + mAmountItems++; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "dec" && mAmountItems > 1) + { + mAmountItems--; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "max") + { + mAmountItems = mMaxItems; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + // TODO: Actually we'd have a bug elsewhere if this check for the number + // of items to be bought ever fails, Bertram removed the assertions, is + // there a better way to ensure this fails in an _obvious_ way in C++? + else if (event.getId() == "buy" && mAmountItems > 0 && + mAmountItems <= mMaxItems) + { + if (mNpcId != -1) + { + Net::getNpcHandler()->buyItem(mNpcId, + mShopItems->at(selectedItem)->getId(), mAmountItems); + + // Update money and adjust the max number of items that can be bought + mMaxItems -= mAmountItems; + setMoney(mMoney - + mAmountItems * mShopItems->at(selectedItem)->getPrice()); + + // Reset selection + mAmountItems = 1; + mSlider->setValue(1); + mSlider->gcn::Slider::setScale(1, mMaxItems); + } + else if (tradeWindow) + { + ShopItem *item = mShopItems->at(selectedItem); + if (item) + { + Net::getBuySellHandler()->sendBuyRequest(mNick, + item, mAmountItems); +// logger->log("buy button mNick:" + mNick); + if (tradeWindow) + { + tradeWindow->addAutoMoney(mNick, + item->getPrice() * mAmountItems); + } + } + } + } +} + +void BuyDialog::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + // Reset amount of items and update labels + mAmountItems = 1; + mSlider->setValue(1); + + updateButtonsAndLabels(); + mSlider->gcn::Slider::setScale(1, mMaxItems); +} + +void BuyDialog::updateButtonsAndLabels() +{ + const int selectedItem = mShopItemList->getSelected(); + int price = 0; + + if (selectedItem > -1) + { + ShopItem * item = mShopItems->at(selectedItem); + if (item) + { + int itemPrice = item->getPrice(); + + // Calculate how many the player can afford + if (itemPrice) + mMaxItems = mMoney / itemPrice; + else + mMaxItems = 1; + + if (item->getQuantity() > 0 && mMaxItems > item->getQuantity()) + mMaxItems = item->getQuantity(); + + if (mAmountItems > mMaxItems) + mAmountItems = mMaxItems; + + // Calculate price of pending purchase + price = mAmountItems * itemPrice; + } + } + else + { + mMaxItems = 0; + mAmountItems = 0; + } + + // Enable or disable buttons and slider + mIncreaseButton->setEnabled(mAmountItems < mMaxItems); + mDecreaseButton->setEnabled(mAmountItems > 1); + mBuyButton->setEnabled(mAmountItems > 0); + mSlider->setEnabled(mMaxItems > 1); + + // Update quantity and money labels + mQuantityLabel->setCaption(strprintf("%d / %d", mAmountItems, mMaxItems)); + mMoneyLabel->setCaption(strprintf(_("Price: %s / Total: %s"), + Units::formatCurrency(price).c_str(), + Units::formatCurrency(mMoney - price).c_str())); +} + +void BuyDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (visible && mShopItemList) + mShopItemList->requestFocus(); + else + scheduleDelete(); +} + +void BuyDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if (*it) + (*it)->close(); + } +} diff --git a/src/gui/buy.h b/src/gui/buy.h new file mode 100644 index 000000000..5133414c1 --- /dev/null +++ b/src/gui/buy.h @@ -0,0 +1,151 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BUY_H +#define BUY_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/selectionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ShopItems; +class ShopListBox; +class ListBox; + +/** + * The buy dialog. + * + * \ingroup Interface + */ +class BuyDialog : public Window, public gcn::ActionListener, + public gcn::SelectionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + BuyDialog(int npcId); + + /** + * Constructor. + */ + BuyDialog(std::string nick); + + /** + * Destructor + */ + ~BuyDialog(); + + void init(); + + /** + * Resets the dialog, clearing shop inventory. + */ + void reset(); + + /** + * Sets the amount of available money. + */ + void setMoney(int amount); + + /** + * Adds an item to the shop inventory. + */ + void addItem(int id, int amount, int price); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Returns the number of items in the shop inventory. + */ + int getNumberOfElements(); + + /** + * Updates the labels according to the selected item. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Returns the name of item number i in the shop inventory. + */ + std::string getElementAt(int i); + + /** + * Updates the state of buttons and labels. + */ + void updateButtonsAndLabels(); + + /** + * Sets the visibility of this window. + */ + void setVisible(bool visible); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list<BuyDialog*> DialogList; + static DialogList instances; + + int mNpcId; + + gcn::Button *mBuyButton; + gcn::Button *mQuitButton; + gcn::Button *mAddMaxButton; + gcn::Button *mIncreaseButton; + gcn::Button *mDecreaseButton; + ShopListBox *mShopItemList; + gcn::ScrollArea *mScrollArea; + gcn::Label *mMoneyLabel; + gcn::Label *mQuantityLabel; + gcn::Slider *mSlider; + + ShopItems *mShopItems; + + int mMoney; + int mAmountItems; + int mMaxItems; + std::string mNick; +}; + +#endif diff --git a/src/gui/buysell.cpp b/src/gui/buysell.cpp new file mode 100644 index 000000000..f6a1fc193 --- /dev/null +++ b/src/gui/buysell.cpp @@ -0,0 +1,137 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/buysell.h" + +#include "gui/setup.h" + +#include "gui/widgets/button.h" + +#include "net/buysellhandler.h" +#include "net/net.h" +#include "net/npchandler.h" + +#include "utils/gettext.h" + +BuySellDialog::DialogList BuySellDialog::instances; + +BuySellDialog::BuySellDialog(int npcId): + Window(_("Shop")), + mNpcId(npcId), + mNick(""), + mBuyButton(0) +{ + init(); +} + +BuySellDialog::BuySellDialog(std::string nick): + Window(_("Shop")), + mNpcId(-1), + mNick(nick), + mBuyButton(0) +{ + init(); +} + +void BuySellDialog::init() +{ + setWindowName("BuySell"); + //setupWindow->registerWindowForReset(this); + setCloseButton(true); + + static const char *buttonNames[] = + { + N_("Buy"), N_("Sell"), N_("Cancel"), 0 + }; + int x = 10, y = 10; + + for (const char **curBtn = buttonNames; *curBtn; curBtn++) + { + Button *btn = new Button(gettext(*curBtn), *curBtn, this); + if (!mBuyButton) + mBuyButton = btn; // For focus request + btn->setPosition(x, y); + add(btn); + x += btn->getWidth() + 10; + } + mBuyButton->requestFocus(); + + setContentSize(x, 2 * y + mBuyButton->getHeight()); + + center(); + setDefaultSize(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); +} + +BuySellDialog::~BuySellDialog() +{ + instances.remove(this); +} + +void BuySellDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (visible) + { + if (mBuyButton) + mBuyButton->requestFocus(); + } + else + { + scheduleDelete(); + } +} + +void BuySellDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "Buy") + { + if (mNpcId != -1) + Net::getNpcHandler()->buy(mNpcId); + else + Net::getBuySellHandler()->requestSellList(mNick); + } + else if (event.getId() == "Sell") + { + if (mNpcId != -1) + Net::getNpcHandler()->sell(mNpcId); + else + Net::getBuySellHandler()->requestBuyList(mNick); + } + + close(); +} + +void BuySellDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if (*it) + (*it)->close(); + } +} diff --git a/src/gui/buysell.h b/src/gui/buysell.h new file mode 100644 index 000000000..f533252ee --- /dev/null +++ b/src/gui/buysell.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BUYSELL_H +#define BUYSELL_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +/** + * A dialog to choose between buying or selling at a shop. + * + * \ingroup Interface + */ +class BuySellDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. The action listener passed will receive "sell", "buy" + * or "cancel" events when the respective buttons are pressed. + * + * @see Window::Window + */ + BuySellDialog(int npcId); + + BuySellDialog(std::string nick); + + virtual ~BuySellDialog(); + + void init(); + + void setVisible(bool visible); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list<BuySellDialog*> DialogList; + static DialogList instances; + + int mNpcId; + std::string mNick; + gcn::Button *mBuyButton; +}; + +#endif diff --git a/src/gui/changeemaildialog.cpp b/src/gui/changeemaildialog.cpp new file mode 100644 index 000000000..4485e0199 --- /dev/null +++ b/src/gui/changeemaildialog.cpp @@ -0,0 +1,167 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/changeemaildialog.h" + +#include "client.h" +#include "log.h" + +#include "gui/register.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <string> +#include <sstream> + +ChangeEmailDialog::ChangeEmailDialog(LoginData *loginData): + Window(_("Change Email Address"), true), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + gcn::Label *accountLabel = new Label(strprintf(_("Account: %s"), + mLoginData->username.c_str())); + gcn::Label *newEmailLabel = new Label(_("Type new email address twice:")); + mFirstEmailField = new TextField; + mSecondEmailField = new TextField; + mChangeEmailButton = new Button(_("Change Email Address"), + "change_email", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + const int width = 200; + const int height = 130; + setContentSize(width, height); + + accountLabel->setPosition(5, 5); + accountLabel->setWidth(130); + + newEmailLabel->setPosition( + 5, accountLabel->getY() + accountLabel->getHeight() + 7); + newEmailLabel->setWidth(width - 5); + + mFirstEmailField->setPosition( + 5, newEmailLabel->getY() + newEmailLabel->getHeight() + 7); + mFirstEmailField->setWidth(130); + + mSecondEmailField->setPosition( + 5, mFirstEmailField->getY() + mFirstEmailField->getHeight() + 7); + mSecondEmailField->setWidth(130); + + mCancelButton->setPosition( + width - 5 - mCancelButton->getWidth(), + height - 5 - mCancelButton->getHeight()); + mChangeEmailButton->setPosition( + mCancelButton->getX() - 5 - mChangeEmailButton->getWidth(), + mCancelButton->getY()); + + add(accountLabel); + add(newEmailLabel); + add(mFirstEmailField); + add(mSecondEmailField); + add(mChangeEmailButton); + add(mCancelButton); + + center(); + setVisible(true); + mFirstEmailField->requestFocus(); + + mFirstEmailField->setActionEventId("change_email"); + mSecondEmailField->setActionEventId("change_email"); +} + +ChangeEmailDialog::~ChangeEmailDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void ChangeEmailDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_CHAR_SELECT); + } + else if (event.getId() == "change_email") + { + + const std::string username = mLoginData->username.c_str(); + const std::string newFirstEmail = mFirstEmailField->getText(); + const std::string newSecondEmail = mSecondEmailField->getText(); + logger->log("ChangeEmailDialog::Email change, Username is %s", + username.c_str()); + + std::stringstream errorMessage; + int error = 0; + + unsigned int min = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int max = Net::getLoginHandler()->getMaxPasswordLength(); + + if (newFirstEmail.length() < min) + { + // First email address too short + errorMessage << strprintf(_("The new email address needs to be at " + "least %d characters long."), min); + error = 1; + } + else if (newFirstEmail.length() > max - 1 ) + { + // First email address too long + errorMessage << strprintf(_("The new email address needs to be " + "less than %d characters long."), max); + error = 1; + } + else if (newFirstEmail != newSecondEmail) + { + // Second Pass mismatch + errorMessage << _("The email address entries mismatch."); + error = 2; + } + + if (error > 0) + { + if (error == 1) + mWrongDataNoticeListener->setTarget(this->mFirstEmailField); + else if (error == 2) + mWrongDataNoticeListener->setTarget(this->mSecondEmailField); + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str()); + if (dlg) + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, change account password. + mChangeEmailButton->setEnabled(false); + // Set the new email address + mLoginData->email = newFirstEmail; + Client::setState(STATE_CHANGEEMAIL_ATTEMPT); + } + } +} diff --git a/src/gui/changeemaildialog.h b/src/gui/changeemaildialog.h new file mode 100644 index 000000000..7e5f04fa4 --- /dev/null +++ b/src/gui/changeemaildialog.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_CHANGEEMAIL_H +#define GUI_CHANGEEMAIL_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +class LoginData; +class OkDialog; +class WrongDataNoticeListener; + +/** + * The Change email dialog. + * + * \ingroup Interface + */ +class ChangeEmailDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + ChangeEmailDialog(LoginData *loginData); + + /** + * Destructor. + */ + ~ChangeEmailDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * This is used to pass the pointer to where the new email should be + * put when the dialog finishes. + */ + static void setEmail(std::string *email); + + private: + gcn::TextField *mFirstEmailField; + gcn::TextField *mSecondEmailField; + + gcn::Button *mChangeEmailButton; + gcn::Button *mCancelButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif // GUI_CHANGEEMAIL_H diff --git a/src/gui/changepassworddialog.cpp b/src/gui/changepassworddialog.cpp new file mode 100644 index 000000000..bc29c9264 --- /dev/null +++ b/src/gui/changepassworddialog.cpp @@ -0,0 +1,157 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "changepassworddialog.h" + +#include "client.h" +#include "log.h" + +#include "gui/register.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <string> +#include <sstream> + +ChangePasswordDialog::ChangePasswordDialog(LoginData *loginData): + Window(_("Change Password"), true), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + gcn::Label *accountLabel = new Label( + strprintf(_("Account: %s"), mLoginData->username.c_str())); + mOldPassField = new PasswordField; + mFirstPassField = new PasswordField; + mSecondPassField = new PasswordField; + mChangePassButton = new Button(_("Change Password"), "change_password", + this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + place(0, 0, accountLabel, 3); + place(0, 1, new Label(_("Password:")), 3); + place(0, 2, mOldPassField, 3).setPadding(1); + place(0, 3, new Label(_("Type new password twice:")), 3); + place(0, 4, mFirstPassField, 3).setPadding(1); + place(0, 5, mSecondPassField, 3).setPadding(1); + place(1, 6, mCancelButton); + place(2, 6, mChangePassButton); + reflowLayout(200); + + center(); + setVisible(true); + mOldPassField->requestFocus(); + + mOldPassField->setActionEventId("change_password"); + mFirstPassField->setActionEventId("change_password"); + mSecondPassField->setActionEventId("change_password"); +} + +ChangePasswordDialog::~ChangePasswordDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void ChangePasswordDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_CHAR_SELECT); + } + else if (event.getId() == "change_password") + { + + const std::string username = mLoginData->username.c_str(); + const std::string oldPassword = mOldPassField->getText(); + const std::string newFirstPass = mFirstPassField->getText(); + const std::string newSecondPass = mSecondPassField->getText(); + logger->log("ChangePasswordDialog::Password change, Username is %s", + username.c_str()); + + std::stringstream errorMessage; + int error = 0; + + unsigned int min = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int max = Net::getLoginHandler()->getMaxPasswordLength(); + + // Check old Password + if (oldPassword.empty()) + { + // No old password + errorMessage << _("Enter the old password first."); + error = 1; + } + else if (newFirstPass.length() < min) + { + // First password too short + errorMessage << strprintf(_("The new password needs to be at least" + " %d characters long."), min); + error = 2; + } + else if (newFirstPass.length() > max - 1 ) + { + // First password too long + errorMessage << strprintf(_("The new password needs to be less " + "than %d characters long."), max); + error = 2; + } + else if (newFirstPass != newSecondPass) + { + // Second Pass mismatch + errorMessage << _("The new password entries mismatch."); + error = 3; + } + + if (error > 0) + { + if (error == 1) + mWrongDataNoticeListener->setTarget(this->mOldPassField); + else if (error == 2) + mWrongDataNoticeListener->setTarget(this->mFirstPassField); + else if (error == 3) + mWrongDataNoticeListener->setTarget(this->mSecondPassField); + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str()); + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, change account password. + mChangePassButton->setEnabled(false); + // Set the new password + mLoginData->password = oldPassword; + mLoginData->newPassword = newFirstPass; + Client::setState(STATE_CHANGEPASSWORD_ATTEMPT); + } + } +} diff --git a/src/gui/changepassworddialog.h b/src/gui/changepassworddialog.h new file mode 100644 index 000000000..361debe45 --- /dev/null +++ b/src/gui/changepassworddialog.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CHANGEPASSWORDDIALOG_H +#define CHANGEPASSWORDDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +class LoginData; +class OkDialog; +class WrongDataNoticeListener; + +/** + * The Change password dialog. + * + * \ingroup Interface + */ +class ChangePasswordDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + ChangePasswordDialog(LoginData *loginData); + + /** + * Destructor + */ + ~ChangePasswordDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + gcn::TextField *mOldPassField; + gcn::TextField *mFirstPassField; + gcn::TextField *mSecondPassField; + + gcn::Button *mChangePassButton; + gcn::Button *mCancelButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif diff --git a/src/gui/charcreatedialog.cpp b/src/gui/charcreatedialog.cpp new file mode 100644 index 000000000..4dc6251b9 --- /dev/null +++ b/src/gui/charcreatedialog.cpp @@ -0,0 +1,372 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/charcreatedialog.h" + +#include "game.h" +#include "localplayer.h" +#include "main.h" +#include "units.h" + +#include "gui/charselectdialog.h" +#include "gui/confirmdialog.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/playerbox.h" +#include "gui/widgets/radiobutton.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/textfield.h" + +#include "net/charhandler.h" +#include "net/messageout.h" +#include "net/net.h" + +#include "resources/colordb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> + +CharCreateDialog::CharCreateDialog(CharSelectDialog *parent, int slot): + Window(_("Create Character"), true, parent), + mCharSelectDialog(parent), + mSlot(slot) +{ + mPlayer = new Being(0, ActorSprite::PLAYER, 0, NULL); + mPlayer->setGender(GENDER_MALE); + + int numberOfHairColors = ColorDB::size(); + + mHairStyle = rand() % mPlayer->getNumOfHairstyles(); + mHairColor = rand() % numberOfHairColors; + updateHair(); + + mNameField = new TextField(""); + mNameLabel = new Label(_("Name:")); + // TRANSLATORS: This is a narrow symbol used to denote 'next'. + // You may change this symbol if your language uses another. + mNextHairColorButton = new Button(_(">"), "nextcolor", this); + // TRANSLATORS: This is a narrow symbol used to denote 'previous'. + // You may change this symbol if your language uses another. + mPrevHairColorButton = new Button(_("<"), "prevcolor", this); + mHairColorLabel = new Label(_("Hair color:")); + mNextHairStyleButton = new Button(_(">"), "nextstyle", this); + mPrevHairStyleButton = new Button(_("<"), "prevstyle", this); + mHairStyleLabel = new Label(_("Hair style:")); + mCreateButton = new Button(_("Create"), "create", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + mMale = new RadioButton(_("Male"), "gender"); + mFemale = new RadioButton(_("Female"), "gender"); + + // Default to a Male character + mMale->setSelected(true); + + mMale->setActionEventId("gender"); + mFemale->setActionEventId("gender"); + + mMale->addActionListener(this); + mFemale->addActionListener(this); + + mPlayerBox = new PlayerBox(mPlayer); + mPlayerBox->setWidth(74); + + mNameField->setActionEventId("create"); + mNameField->addActionListener(this); + + mAttributesLeft = new Label( + strprintf(_("Please distribute %d points"), 99)); + + int w = 200; + int h = 330; + setContentSize(w, h); + mPlayerBox->setDimension(gcn::Rectangle(80, 30, 110, 85)); + mNameLabel->setPosition(5, 5); + mNameField->setDimension( + gcn::Rectangle(45, 5, w - 45 - 7, mNameField->getHeight())); + mPrevHairColorButton->setPosition(90, 35); + mNextHairColorButton->setPosition(165, 35); + mHairColorLabel->setPosition(5, 40); + mPrevHairStyleButton->setPosition(90, 64); + mNextHairStyleButton->setPosition(165, 64); + mHairStyleLabel->setPosition(5, 70); + mAttributesLeft->setPosition(15, 280); + updateSliders(); + mCancelButton->setPosition( + w - 5 - mCancelButton->getWidth(), + h - 5 - mCancelButton->getHeight()); + mCreateButton->setPosition( + mCancelButton->getX() - 5 - mCreateButton->getWidth(), + h - 5 - mCancelButton->getHeight()); + + mMale->setPosition(30, 120); + mFemale->setPosition(100, 120); + + add(mPlayerBox); + add(mNameField); + add(mNameLabel); + add(mNextHairColorButton); + add(mPrevHairColorButton); + add(mHairColorLabel); + add(mNextHairStyleButton); + add(mPrevHairStyleButton); + add(mHairStyleLabel); + add(mAttributesLeft); + add(mCreateButton); + add(mCancelButton); + + add(mMale); + add(mFemale); + + center(); + setVisible(true); + mNameField->requestFocus(); +} + +CharCreateDialog::~CharCreateDialog() +{ + delete mPlayer; + mPlayer = 0; + + // Make sure the char server handler knows that we're gone + Net::getCharHandler()->setCharCreateDialog(0); +} + +void CharCreateDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "create") + { + if (Net::getNetworkType() == ServerInfo::MANASERV + || getName().length() >= 4) + { + // Attempt to create the character + mCreateButton->setEnabled(false); + + std::vector<int> atts; + for (unsigned i = 0; i < mAttributeSlider.size(); i++) + { + atts.push_back(static_cast<int>( + mAttributeSlider[i]->getValue())); + } + + int characterSlot = mSlot; + // On Manaserv, the slots start at 1, so we offset them. + if (Net::getNetworkType() == ServerInfo::MANASERV) + ++characterSlot; + + Net::getCharHandler()->newCharacter(getName(), characterSlot, + mFemale->isSelected(), + mHairStyle, + mHairColor, atts); + } + else + { + new OkDialog(_("Error"), + _("Your name needs to be at least 4 characters."), + true, this); + } + } + else if (event.getId() == "cancel") + { + scheduleDelete(); + } + else if (event.getId() == "nextcolor") + { + mHairColor++; + updateHair(); + } + else if (event.getId() == "prevcolor") + { + mHairColor--; + updateHair(); + } + else if (event.getId() == "nextstyle") + { + mHairStyle++; + updateHair(); + } + else if (event.getId() == "prevstyle") + { + mHairStyle--; + updateHair(); + } + else if (event.getId() == "statslider") + { + updateSliders(); + } + else if (event.getId() == "gender") + { + if (mMale->isSelected()) + mPlayer->setGender(GENDER_MALE); + else + mPlayer->setGender(GENDER_FEMALE); + } +} + +std::string CharCreateDialog::getName() const +{ + std::string name = mNameField->getText(); + trim(name); + return name; +} + +void CharCreateDialog::updateSliders() +{ + for (unsigned i = 0; i < mAttributeSlider.size(); i++) + { + // Update captions + mAttributeValue[i]->setCaption( + toString(static_cast<int>(mAttributeSlider[i]->getValue()))); + mAttributeValue[i]->adjustSize(); + } + + // Update distributed points + int pointsLeft = mMaxPoints - getDistributedPoints(); + if (pointsLeft == 0) + { + mAttributesLeft->setCaption(_("Character stats OK")); + mCreateButton->setEnabled(true); + } + else + { + mCreateButton->setEnabled(false); + if (pointsLeft > 0) + { + mAttributesLeft->setCaption( + strprintf(_("Please distribute %d points"), pointsLeft)); + } + else + { + mAttributesLeft->setCaption( + strprintf(_("Please remove %d points"), -pointsLeft)); + } + } + + mAttributesLeft->adjustSize(); +} + +void CharCreateDialog::unlock() +{ + mCreateButton->setEnabled(true); +} + +int CharCreateDialog::getDistributedPoints() const +{ + int points = 0; + + for (unsigned i = 0; i < mAttributeSlider.size(); i++) + points += static_cast<int>(mAttributeSlider[i]->getValue()); + return points; +} + +void CharCreateDialog::setAttributes(const std::vector<std::string> &labels, + int available, int min, int max) +{ + mMaxPoints = available; + + for (unsigned i = 0; i < mAttributeLabel.size(); i++) + { + remove(mAttributeLabel[i]); + delete mAttributeLabel[i]; + mAttributeLabel[i] = 0; + remove(mAttributeSlider[i]); + delete mAttributeSlider[i]; + mAttributeSlider[i] = 0; + remove(mAttributeValue[i]); + delete mAttributeValue[i]; + mAttributeValue[i] = 0; + } + + mAttributeLabel.resize(labels.size()); + mAttributeSlider.resize(labels.size()); + mAttributeValue.resize(labels.size()); + + int w = 200; + int h = 330; + + for (unsigned i = 0; i < labels.size(); i++) + { + mAttributeLabel[i] = new Label(labels[i]); + mAttributeLabel[i]->setWidth(70); + mAttributeLabel[i]->setPosition(5, 140 + i*20); + add(mAttributeLabel[i]); + + mAttributeSlider[i] = new Slider(min, max); + mAttributeSlider[i]->setDimension(gcn::Rectangle(75, 140 + i * 20, + 100, 10)); + mAttributeSlider[i]->setActionEventId("statslider"); + mAttributeSlider[i]->addActionListener(this); + add(mAttributeSlider[i]); + + mAttributeValue[i] = new Label(toString(min)); + mAttributeValue[i]->setPosition(180, 140 + i*20); + add(mAttributeValue[i]); + } + + mAttributesLeft->setPosition(15, 280); + updateSliders(); + + mCancelButton->setPosition( + w - 5 - mCancelButton->getWidth(), + h - 5 - mCancelButton->getHeight()); + mCreateButton->setPosition( + mCancelButton->getX() - 5 - mCreateButton->getWidth(), + h - 5 - mCancelButton->getHeight()); +} + +void CharCreateDialog::setFixedGender(bool fixed, Gender gender) +{ + if (gender == GENDER_FEMALE) + { + mFemale->setSelected(true); + mMale->setSelected(false); + } + else + { + mMale->setSelected(true); + mFemale->setSelected(false); + } + + mPlayer->setGender(gender); + + if (fixed) + { + mMale->setEnabled(false); + mFemale->setEnabled(false); + } +} + +void CharCreateDialog::updateHair() +{ + mHairStyle %= Being::getNumOfHairstyles(); + if (mHairStyle < 0) + mHairStyle += Being::getNumOfHairstyles(); + + mHairColor %= ColorDB::size(); + if (mHairColor < 0) + mHairColor += ColorDB::size(); + + mPlayer->setSprite(Net::getCharHandler()->hairSprite(), + mHairStyle * -1, ColorDB::get(mHairColor)); +} diff --git a/src/gui/charcreatedialog.h b/src/gui/charcreatedialog.h new file mode 100644 index 000000000..018de3f59 --- /dev/null +++ b/src/gui/charcreatedialog.h @@ -0,0 +1,122 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CHAR_CREATE_DIALOG_H +#define CHAR_CREATE_DIALOG_H + +#include "being.h" +#include "guichanfwd.h" + +#include "gui/charselectdialog.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +#include <string> +#include <vector> + +class LocalPlayer; +class PlayerBox; + +/** + * Character creation dialog. + * + * \ingroup Interface + */ +class CharCreateDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + CharCreateDialog(CharSelectDialog *parent, int slot); + + /** + * Destructor. + */ + ~CharCreateDialog(); + + void action(const gcn::ActionEvent &event); + + /** + * Unlocks the dialog, enabling the create character button again. + */ + void unlock(); + + void setAttributes(const std::vector<std::string> &labels, + int available, + int min, int max); + + void setFixedGender(bool fixed, Gender gender = GENDER_FEMALE); + + private: + int getDistributedPoints() const; + + void updateSliders(); + + /** + * Returns the name of the character to create. + */ + std::string getName() const; + + /** + * Communicate character creation to the server. + */ + void attemptCharCreate(); + + void updateHair(); + + CharSelectDialog *mCharSelectDialog; + + gcn::TextField *mNameField; + gcn::Label *mNameLabel; + gcn::Button *mNextHairColorButton; + gcn::Button *mPrevHairColorButton; + gcn::Label *mHairColorLabel; + gcn::Button *mNextHairStyleButton; + gcn::Button *mPrevHairStyleButton; + gcn::Label *mHairStyleLabel; + + gcn::RadioButton *mMale; + gcn::RadioButton *mFemale; + + std::vector<gcn::Slider*> mAttributeSlider; + std::vector<gcn::Label*> mAttributeLabel; + std::vector<gcn::Label*> mAttributeValue; + gcn::Label *mAttributesLeft; + + int mMaxPoints; + int mUsedPoints; + + gcn::Button *mCreateButton; + gcn::Button *mCancelButton; + + Being *mPlayer; + PlayerBox *mPlayerBox; + + int mHairStyle; + int mHairColor; + + int mSlot; +}; + +#endif // CHAR_CREATE_DIALOG_H diff --git a/src/gui/charselectdialog.cpp b/src/gui/charselectdialog.cpp new file mode 100644 index 000000000..d95930108 --- /dev/null +++ b/src/gui/charselectdialog.cpp @@ -0,0 +1,456 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/charselectdialog.h" + +#include "client.h" +#include "game.h" +#include "localplayer.h" +#include "units.h" +#include "log.h" + +#include "gui/changeemaildialog.h" +#include "gui/changepassworddialog.h" +#include "gui/charcreatedialog.h" +#include "gui/confirmdialog.h" +#include "gui/okdialog.h" +#include "gui/sdlinput.h" +#include "gui/unregisterdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/container.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/playerbox.h" +#include "gui/widgets/textfield.h" + +#include "net/charhandler.h" +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/messageout.h" +#include "net/net.h" + +#include "resources/colordb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> + +#include <string> +#include <cassert> + +// Character slots per row in the dialog +static const int SLOTS_PER_ROW = 5; + +/** + * Listener for confirming character deletion. + */ +class CharDeleteConfirm : public ConfirmDialog +{ + public: + CharDeleteConfirm(CharSelectDialog *m, int index): + ConfirmDialog(_("Confirm Character Delete"), + _("Are you sure you want to delete this character?"), + false, false, m), + mMaster(m), + mIndex(index) + { + } + + void action(const gcn::ActionEvent &event) + { + if (event.getId() == "yes" && mMaster) + mMaster->attemptCharacterDelete(mIndex); + + ConfirmDialog::action(event); + } + + private: + CharSelectDialog *mMaster; + int mIndex; +}; + +class CharacterDisplay : public Container +{ + public: + CharacterDisplay(CharSelectDialog *charSelectDialog); + + void setCharacter(Net::Character *character); + + Net::Character *getCharacter() const + { return mCharacter; } + + void requestFocus(); + + void setActive(bool active); + + private: + void update(); + + Net::Character *mCharacter; + + PlayerBox *mPlayerBox; + Label *mName; + Label *mLevel; + Label *mMoney; + Button *mButton; + Button *mDelete; +}; + +CharSelectDialog::CharSelectDialog(LoginData *loginData): + Window(_("Account and Character Management")), + mLocked(false), + mUnregisterButton(0), + mChangeEmailButton(0), + mCharacterEntries(0), + mLoginData(loginData), + mCharHandler(Net::getCharHandler()) +{ + setCloseButton(false); + + mAccountNameLabel = new Label(loginData->username); + mSwitchLoginButton = new Button(_("Switch Login"), "switch", this); + mChangePasswordButton = new Button(_("Change Password"), "change_password", + this); + + int optionalActions = Net::getLoginHandler()->supportedOptionalActions(); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mAccountNameLabel, 2); + place(0, 1, mSwitchLoginButton); + + if (optionalActions & Net::LoginHandler::Unregister) + { + mUnregisterButton = new Button(_("Unregister"), + "unregister", this); + place(3, 1, mUnregisterButton); + } + + place(0, 2, mChangePasswordButton); + + if (optionalActions & Net::LoginHandler::ChangeEmail) + { + mChangeEmailButton = new Button(_("Change Email"), + "change_email", this); + place(3, 2, mChangeEmailButton); + } + + place = getPlacer(0, 1); + + for (int i = 0; i < (int)mLoginData->characterSlots; i++) + { + mCharacterEntries.push_back(new CharacterDisplay(this)); + place(i % SLOTS_PER_ROW, (int)i / SLOTS_PER_ROW, mCharacterEntries[i]); + } + + reflowLayout(); + + addKeyListener(this); + + center(); + setVisible(true); + + Net::getCharHandler()->setCharSelectDialog(this); + if (mCharacterEntries[0]) + mCharacterEntries[0]->requestFocus(); +} + +CharSelectDialog::~CharSelectDialog() +{ +} + +void CharSelectDialog::action(const gcn::ActionEvent &event) +{ + // Check if a button of a character was pressed + const gcn::Widget *sourceParent = event.getSource()->getParent(); + int selected = -1; + for (int i = 0; i < (int)mCharacterEntries.size(); ++i) + { + if (mCharacterEntries[i] == sourceParent) + { + selected = i; + break; + } + } + + const std::string &eventId = event.getId(); + + if (selected != -1) + { + if (eventId == "use") + { + attemptCharacterSelect(selected); + } + else if (eventId == "new" && + !mCharacterEntries[selected]->getCharacter()) + { + // Start new character dialog + CharCreateDialog *charCreateDialog = + new CharCreateDialog(this, selected); + mCharHandler->setCharCreateDialog(charCreateDialog); + } + else if (eventId == "delete" + && mCharacterEntries[selected]->getCharacter()) + { + new CharDeleteConfirm(this, selected); + } + } + else if (eventId == "switch") + { + Client::setState(STATE_SWITCH_LOGIN); + } + else if (eventId == "change_password") + { + Client::setState(STATE_CHANGEPASSWORD); + } + else if (eventId == "change_email") + { + Client::setState(STATE_CHANGEEMAIL); + } + else if (eventId == "unregister") + { + Client::setState(STATE_UNREGISTER); + } +} + +void CharSelectDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + { + action(gcn::ActionEvent(mSwitchLoginButton, + mSwitchLoginButton->getActionEventId())); + } +} + +/** + * Communicate character deletion to the server. + */ +void CharSelectDialog::attemptCharacterDelete(int index) +{ + if (mLocked) + return; + + mCharHandler->deleteCharacter(mCharacterEntries[index]->getCharacter()); + lock(); +} + +/** + * Communicate character selection to the server. + */ +void CharSelectDialog::attemptCharacterSelect(int index) +{ + if (mLocked || !mCharacterEntries[index]) + return; + + setVisible(false); + if (mCharHandler && mCharacterEntries[index]) + { + mCharHandler->chooseCharacter( + mCharacterEntries[index]->getCharacter()); + } + lock(); +} + +void CharSelectDialog::setCharacters(const Net::Characters &characters) +{ + // Reset previous characters + std::vector<CharacterDisplay*>::iterator iter, iter_end; + for (iter = mCharacterEntries.begin(), iter_end = mCharacterEntries.end(); + iter != iter_end; ++iter) + (*iter)->setCharacter(0); + + Net::Characters::const_iterator i, i_end = characters.end(); + for (i = characters.begin(); i != i_end; ++i) + { + if (!*i) + continue; + + Net::Character *character = *i; + + // Slots Number start at 1 for Manaserv, so we offset them by one. + int characterSlot = character->slot; + if (Net::getNetworkType() == ServerInfo::MANASERV && characterSlot > 0) + --characterSlot; + + if (characterSlot >= (int)mCharacterEntries.size()) + { + logger->log("Warning: slot out of range: %d", character->slot); + continue; + } + + mCharacterEntries[characterSlot]->setCharacter(character); + } +} + +void CharSelectDialog::lock() +{ + assert(!mLocked); + setLocked(true); +} + +void CharSelectDialog::unlock() +{ + setLocked(false); +} + +void CharSelectDialog::setLocked(bool locked) +{ + mLocked = locked; + + if (mSwitchLoginButton) + mSwitchLoginButton->setEnabled(!locked); + if (mChangePasswordButton) + mChangePasswordButton->setEnabled(!locked); + if (mUnregisterButton) + mUnregisterButton->setEnabled(!locked); + if (mChangeEmailButton) + mChangeEmailButton->setEnabled(!locked); + + for (int i = 0; i < (int)mCharacterEntries.size(); ++i) + { + if (mCharacterEntries[i]) + mCharacterEntries[i]->setActive(!mLocked); + } +} + +bool CharSelectDialog::selectByName(const std::string &name, + SelectAction action) +{ + if (mLocked) + return false; + + for (int i = 0; i < (int)mCharacterEntries.size(); ++i) + { + Net::Character *character = mCharacterEntries[i]->getCharacter(); + if (mCharacterEntries[i] && character) + { + if (character->dummy->getName() == name) + { + if (mCharacterEntries[i]) + mCharacterEntries[i]->requestFocus(); + if (action == Choose) + attemptCharacterSelect(i); + return true; + } + } + } + + return false; +} + + +CharacterDisplay::CharacterDisplay(CharSelectDialog *charSelectDialog): + mCharacter(0), + mPlayerBox(new PlayerBox) +{ + mButton = new Button("wwwwwwwww", "go", charSelectDialog); + mName = new Label("wwwwwwwwwwwwwwwwwwwwwwww"); + mLevel = new Label("(888)"); + mMoney = new Label("wwwwwwwww"); + + mDelete = new Button(_("Delete"), "delete", charSelectDialog); + + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mPlayerBox, 3, 5); + place(0, 5, mName, 3); + place(0, 6, mLevel, 3); + place(0, 7, mMoney, 3); + place(0, 8, mButton, 3); + place(0, 9, mDelete, 3); + + update(); + + + // Setting the width so that the largest label fits. + mName->adjustSize(); + mMoney->adjustSize(); +/* + int width = 74; + if (width - 20 < mName->getWidth()) + width = 20 + mName->getWidth(); + if (width - 20 < mMoney->getWidth()) + width = 20 + mMoney->getWidth(); + h.reflowLayout(width, 112 + mName->getHeight() + mLevel->getHeight() + + mMoney->getHeight() + mButton->getHeight() + mDelete->getHeight()); +*/ + + setWidth(100); + setHeight(200); +} + +void CharacterDisplay::setCharacter(Net::Character *character) +{ + if (mCharacter == character) + return; + + mCharacter = character; + mPlayerBox->setPlayer(character ? character->dummy : 0); + update(); +} + +void CharacterDisplay::requestFocus() +{ + mButton->requestFocus(); +} + +void CharacterDisplay::setActive(bool active) +{ + mButton->setEnabled(active); + mDelete->setEnabled(active); +} + +void CharacterDisplay::update() +{ + if (mCharacter) + { + const LocalPlayer *character = mCharacter->dummy; + mButton->setCaption(_("Choose")); + mButton->setActionEventId("use"); + mName->setCaption(strprintf("%s", character->getName().c_str())); + mLevel->setCaption(strprintf("Level %d", + mCharacter->data.mAttributes[LEVEL])); + mMoney->setCaption(Units::formatCurrency( + mCharacter->data.mAttributes[MONEY])); + + mDelete->setVisible(true); + } + else + { + mButton->setCaption(_("Create")); + mButton->setActionEventId("new"); + mName->setCaption(_("(empty)")); + mLevel->setCaption(_("(empty)")); + mMoney->setCaption(Units::formatCurrency(0)); + + mDelete->setVisible(false); + } + + // Recompute layout + distributeResizedEvent(); +} diff --git a/src/gui/charselectdialog.h b/src/gui/charselectdialog.h new file mode 100644 index 000000000..7596e755b --- /dev/null +++ b/src/gui/charselectdialog.h @@ -0,0 +1,113 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CHAR_SELECT_H +#define CHAR_SELECT_H + +#include "being.h" +#include "guichanfwd.h" +#include "main.h" + +#include "gui/widgets/window.h" + +#include "net/charhandler.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> + +class CharacterDisplay; +class LocalPlayer; +class LoginData; +class PlayerBox; + +namespace Net +{ + class CharHandler; +} + +/** + * Character selection dialog. + * + * \ingroup Interface + */ +class CharSelectDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + friend class CharDeleteConfirm; + friend class Net::CharHandler; + + /** + * Constructor. + */ + CharSelectDialog(LoginData *loginData); + + ~CharSelectDialog(); + + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + enum SelectAction + { + Focus = 0, + Choose + }; + + /** + * Attempt to select the character with the given name. Returns whether + * a character with the given name was found. + * + * \param action determines what to do when a character with the given + * name was found (just focus or also try to choose this + * character). + */ + bool selectByName(const std::string &name, + SelectAction action = Focus); + + private: + void attemptCharacterDelete(int index); + void attemptCharacterSelect(int index); + + void setCharacters(const Net::Characters &characters); + + void lock(); + void unlock(); + void setLocked(bool locked); + + bool mLocked; + + gcn::Label *mAccountNameLabel; + + gcn::Button *mSwitchLoginButton; + gcn::Button *mChangePasswordButton; + gcn::Button *mUnregisterButton; + gcn::Button *mChangeEmailButton; + + /** The player boxes */ + std::vector<CharacterDisplay*> mCharacterEntries; + + LoginData *mLoginData; + + Net::CharHandler *mCharHandler; +}; + +#endif diff --git a/src/gui/chat.cpp b/src/gui/chat.cpp new file mode 100644 index 000000000..3ffa018c3 --- /dev/null +++ b/src/gui/chat.cpp @@ -0,0 +1,1350 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "chat.h" + +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "guild.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "party.h" +#include "playerinfo.h" +#include "playerrelations.h" +#include "spellshortcut.h" +#include "sound.h" + +#include "gui/setup.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/battletab.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/itemlinkhandler.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tabbedarea.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/tradetab.h" +#include "gui/widgets/whispertab.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/focushandler.hpp> +#include <guichan/focuslistener.hpp> + +#include <sstream> + +#include <sys/stat.h> + +/** + * The chat input hides when it loses focus. It is also invisible by default. + */ +class ChatInput : public TextField, public gcn::FocusListener +{ + public: + ChatInput(): + TextField("", false) + { + setVisible(false); + addFocusListener(this); + } + + /** + * Called if the chat input loses focus. It will set itself to + * invisible as result. + */ + void focusLost(const gcn::Event &event _UNUSED_) + { + setVisible(false); + } +}; + +const char *COLOR_NAME[14] = +{ + N_("default"), + N_("black"), + N_("red"), + N_("green"), + N_("blue"), + N_("gold"), + N_("yellow"), + N_("pink"), + N_("purple"), + N_("grey"), + N_("brown"), + N_("rainbow 1"), + N_("rainbow 2"), + N_("rainbow 3"), +}; + + +class ColorListModel : public gcn::ListModel +{ +public: + virtual ~ColorListModel() { } + + virtual int getNumberOfElements() + { + return 14; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return COLOR_NAME[i]; + } +}; + +#define ACTION_COLOR_PICKER "color picker" + + +ChatWindow::ChatWindow(): + Window(_("Chat")), + mTmpVisible(false), + mChatHistoryIndex(0) +{ + listen(CHANNEL_NOTICES); + listen(CHANNEL_ATTRIBUTES); + + setWindowName("Chat"); + + if (setupWindow) + setupWindow->registerWindowForReset(this); + + // no title presented, title bar is padding so window can be moved. + gcn::Window::setTitleBarHeight(gcn::Window::getPadding() + 4); + setShowTitle(false); + setResizable(true); + setDefaultVisible(true); + setSaveVisible(true); + setDefaultSize(600, 123, ImageRect::LOWER_LEFT); + setMinWidth(150); + setMinHeight(90); + + mItemLinkHandler = new ItemLinkHandler; + + mChatInput = new ChatInput; + mChatInput->setActionEventId("chatinput"); + mChatInput->addActionListener(this); + + mChatTabs = new TabbedArea; + + mChatColor = config.getIntValue("chatColor"); + mColorListModel = new ColorListModel; + mColorPicker = new DropDown(mColorListModel); + + mColorPicker->setActionEventId(ACTION_COLOR_PICKER); + mColorPicker->addActionListener(this); + mColorPicker->setSelected(mChatColor); + + add(mChatTabs); + add(mChatInput); + add(mColorPicker); + + loadWindowState(); + + mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth() + - 2*getPadding() - 8, getPadding()); + + // Add key listener to chat input to be able to respond to up/down + mChatInput->addKeyListener(this); + mCurHist = mHistory.end(); + + mReturnToggles = config.getBoolValue("ReturnToggles"); + + mRainbowColor = 0; + + mColorPicker->setVisible(config.getBoolValue("showChatColorsList")); + + fillCommands(); + initTradeFilter(); +} + +ChatWindow::~ChatWindow() +{ + config.setValue("ReturnToggles", mReturnToggles); + removeAllWhispers(); + delete mItemLinkHandler; + mItemLinkHandler = 0; + delete mColorPicker; + mColorPicker = 0; + delete mColorListModel; + mColorListModel = 0; +} + +void ChatWindow::fillCommands() +{ + mCommands.push_back("/all"); + mCommands.push_back("/away "); + mCommands.push_back("/closeall"); + mCommands.push_back("/clear"); + mCommands.push_back("/create "); + mCommands.push_back("/close"); + mCommands.push_back("/cacheinfo"); + mCommands.push_back("/erase "); + mCommands.push_back("/follow "); + mCommands.push_back("/heal "); + mCommands.push_back("/ignoreall"); + mCommands.push_back("/help"); + mCommands.push_back("/announce "); + mCommands.push_back("/where"); + mCommands.push_back("/who"); + mCommands.push_back("/msg "); + mCommands.push_back("/mail "); + mCommands.push_back("/whisper "); + mCommands.push_back("/w "); + mCommands.push_back("/query "); + mCommands.push_back("/ignore "); + mCommands.push_back("/unignore "); + mCommands.push_back("/join "); + mCommands.push_back("/list"); + mCommands.push_back("/party"); + mCommands.push_back("/createparty "); + mCommands.push_back("/createguild "); + mCommands.push_back("/me "); + mCommands.push_back("/toggle"); + mCommands.push_back("/present"); + mCommands.push_back("/quit"); + mCommands.push_back("/move "); + mCommands.push_back("/target "); + mCommands.push_back("/invite "); + mCommands.push_back("/leave"); + mCommands.push_back("/kick "); + mCommands.push_back("/item"); + mCommands.push_back("/imitation"); + mCommands.push_back("/exp"); + mCommands.push_back("/ping"); + mCommands.push_back("/outfit "); + mCommands.push_back("/emote "); + mCommands.push_back("/navigate "); + mCommands.push_back("/priceload"); + mCommands.push_back("/pricesave"); + mCommands.push_back("/trade "); + mCommands.push_back("/friend "); + mCommands.push_back("/befriend "); + mCommands.push_back("/disregard "); + mCommands.push_back("/neutral "); + mCommands.push_back("/raw "); + mCommands.push_back("/disconnect"); + mCommands.push_back("/undress "); + mCommands.push_back("/attack"); + mCommands.push_back("/dirs"); + mCommands.push_back("/info"); + mCommands.push_back("/wait"); +} + +void ChatWindow::resetToDefaultSize() +{ + Window::resetToDefaultSize(); +} + +void ChatWindow::adjustTabSize() +{ + const gcn::Rectangle area = getChildrenArea(); + + mChatInput->setPosition(mChatInput->getFrameSize(), + area.height - mChatInput->getHeight() - + mChatInput->getFrameSize()); + mChatInput->setWidth(area.width - 2 * mChatInput->getFrameSize()); + + mChatTabs->setWidth(area.width - 2 * mChatTabs->getFrameSize()); + mChatTabs->setHeight(area.height - 2 * mChatTabs->getFrameSize() - + (mChatInput->getHeight() + mChatInput->getFrameSize() * 2)); + + ChatTab *tab = getFocused(); + if (tab) + { + gcn::Widget *content = tab->mScrollArea; + if (content) + { + content->setSize(mChatTabs->getWidth() + - 2 * content->getFrameSize(), + mChatTabs->getContainerHeight() + - 2 * content->getFrameSize()); + content->logic(); + } + } + + mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth() + - 2*getPadding() - 8, getPadding()); + +} + +void ChatWindow::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); + + adjustTabSize(); +} + +/* +void ChatWindow::logic() +{ + Window::logic(); + + Tab *tab = getFocused(); + if (tab != mCurrentTab) + mCurrentTab = tab; +} +*/ + +ChatTab *ChatWindow::getFocused() const +{ + return static_cast<ChatTab*>(mChatTabs->getSelectedTab()); +} + +void ChatWindow::clearTab(ChatTab *tab) +{ + if (tab) + tab->clearText(); +} + +void ChatWindow::clearTab() +{ + clearTab(getFocused()); +} + +void ChatWindow::prevTab() +{ + if (!mChatTabs) + return; + + int tab = mChatTabs->getSelectedTabIndex(); + + if (tab == 0) + tab = mChatTabs->getNumberOfTabs(); + tab--; + + mChatTabs->setSelectedTab(tab); +} + +void ChatWindow::nextTab() +{ + if (!mChatTabs) + return; + + int tab = mChatTabs->getSelectedTabIndex(); + + tab++; + if (tab == mChatTabs->getNumberOfTabs()) + tab = 0; + + mChatTabs->setSelectedTab(tab); +} + +void ChatWindow::defaultTab() +{ + if (mChatTabs) + mChatTabs->setSelectedTab((unsigned)0); +} + +void ChatWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "chatinput") + { + std::string message = mChatInput->getText(); + + if (!message.empty()) + { + // If message different from previous, put it in the history + if (mHistory.empty() || message != mHistory.back()) + mHistory.push_back(message); + + // Reset history iterator + mCurHist = mHistory.end(); + + // Send the message to the server + chatInput(addColors(message)); + + // Clear the text from the chat input + mChatInput->setText(""); + } + + if (message.empty() || !mReturnToggles) + { + // Remove focus and hide input + if (mFocusHandler) + mFocusHandler->focusNone(); + + // If the chatWindow is shown up because you want to send a message + // It should hide now + if (mTmpVisible) + setVisible(false); + } + } + else if (event.getId() == ACTION_COLOR_PICKER) + { + mChatColor = mColorPicker->getSelected(); + config.setValue("chatColor", mChatColor); + } + + if (mColorPicker && mColorPicker->isVisible() + != config.getBoolValue("showChatColorsList")) + { + mColorPicker->setVisible(config.getBoolValue( + "showChatColorsList")); + } +} + +bool ChatWindow::requestChatFocus() +{ + // Make sure chatWindow is visible + if (!isVisible()) + { + setVisible(true); + + /* + * This is used to hide chatWindow after sending the message. There is + * a trick here, because setVisible will set mTmpVisible to false, you + * have to put this sentence *after* setVisible, not before it + */ + mTmpVisible = true; + } + + // Don't do anything else if the input is already visible and has focus + if (mChatInput->isVisible() && mChatInput->isFocused()) + return false; + + // Give focus to the chat input + mChatInput->setVisible(true); + mChatInput->requestFocus(); + return true; +} + +bool ChatWindow::isInputFocused() const +{ + return mChatInput->isFocused(); +} + +void ChatWindow::removeTab(ChatTab *tab) +{ + mChatTabs->removeTab(tab); +} + +void ChatWindow::addTab(ChatTab *tab) +{ + mChatTabs->addTab(tab, tab->mScrollArea); + + // Update UI + logic(); +} + +void ChatWindow::removeWhisper(const std::string &nick) +{ + std::string tempNick = nick; + toLower(tempNick); + mWhispers.erase(tempNick); +} + +void ChatWindow::removeAllWhispers() +{ + TabMap::iterator iter; + std::list<ChatTab*> tabs; + + for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter) + tabs.push_back(iter->second); + + for (std::list<ChatTab*>::iterator it = tabs.begin(); + it != tabs.end(); ++it) + { + delete *it; + } + + mWhispers.clear(); +} + +void ChatWindow::ignoreAllWhispers() +{ + TabMap::iterator iter; + for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter) + { + WhisperTab *tab = dynamic_cast<WhisperTab*>(iter->second); + if (tab && player_relations.getRelation(tab->getNick()) + != PlayerRelation::IGNORED) + { + player_relations.setRelation(tab->getNick(), + PlayerRelation::IGNORED); + } + + delete(iter->second); + iter->second = 0; + } +} + +void ChatWindow::chatInput(const std::string &message) +{ + ChatTab *tab = NULL; + std::string msg = message; + trim(msg); + + if (config.getBoolValue("allowCommandsInChatTabs") + && msg.length() > 1 + && ((msg.at(0) == '#' && msg.at(1) != '#') || msg.at(0) == '@') + && localChatTab) + { + tab = localChatTab; + } + else + { + tab = getFocused(); + } + if (tab) + tab->chatInput(msg); + Game::instance()->setValidSpeed(); +} + +void ChatWindow::localChatInput(const std::string &msg) +{ + if (localChatTab) + localChatTab->chatInput(msg); + else + chatInput(msg); +} + +void ChatWindow::doPresent() +{ + if (!actorSpriteManager) + return; + + const ActorSprites &actors = actorSpriteManager->getAll(); + std::string response = ""; + int playercount = 0; + + for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end(); + it != it_end; it++) + { + if ((*it)->getType() == ActorSprite::PLAYER) + { + if (!response.empty()) + response += ", "; + response += static_cast<Being*>(*it)->getName(); + ++playercount; + } + } + + std::string log = strprintf(_("Present: %s; %d players are present."), + response.c_str(), playercount); + + if (getFocused()) + getFocused()->chatLog(log, BY_SERVER); +} + +void ChatWindow::scroll(int amount) +{ + if (!isVisible()) + return; + + ChatTab *tab = getFocused(); + if (tab) + tab->scroll(amount); +} + +void ChatWindow::mousePressed(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + if (viewport) + { + gcn::Tab *tab = mChatTabs->getSelectedTab(); + if (tab) + { + ChatTab *cTab = dynamic_cast<ChatTab*>(tab); + if (cTab) + viewport->showChatPopup(cTab); + } + } + } + + Window::mousePressed(event); + + if (event.isConsumed()) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + ChatTab *tab = getFocused(); + if (tab) + mMoved = !isResizeAllowed(event); + } + + mDragOffsetX = event.getX(); + mDragOffsetY = event.getY(); +} + +void ChatWindow::mouseDragged(gcn::MouseEvent &event) +{ + Window::mouseDragged(event); + + if (event.isConsumed()) + return; + + if (isMovable() && mMoved) + { + int newX = std::max(0, getX() + event.getX() - mDragOffsetX); + int newY = std::max(0, getY() + event.getY() - mDragOffsetY); + newX = std::min(graphics->getWidth() - getWidth(), newX); + newY = std::min(graphics->getHeight() - getHeight(), newY); + setPosition(newX, newY); + } +} + +/* +void ChatWindow::mouseReleased(gcn::MouseEvent &event _UNUSED_) +{ + +} +*/ + +void ChatWindow::keyPressed(gcn::KeyEvent &event) +{ + if (event.getKey().getValue() == Key::DOWN) + { + if (mCurHist != mHistory.end()) + { + // Move forward through the history + HistoryIterator prevHist = mCurHist++; + + if (mCurHist != mHistory.end()) + { + mChatInput->setText(*mCurHist); + mChatInput->setCaretPosition(static_cast<unsigned>( + mChatInput->getText().length())); + } + else + { + mChatInput->setText(""); + mCurHist = prevHist; + } + } + else if (mChatInput->getText() != "") + { + mChatInput->setText(""); + } + } + else if (event.getKey().getValue() == Key::UP && + mCurHist != mHistory.begin() && !mHistory.empty()) + { + // Move backward through the history + mCurHist--; + mChatInput->setText(*mCurHist); + mChatInput->setCaretPosition(static_cast<unsigned>( + mChatInput->getText().length())); + } + else if (event.getKey().getValue() == Key::INSERT && + mChatInput->getText() != "") + { + // Add the current message to the history and clear the text + if (mHistory.empty() || mChatInput->getText() != mHistory.back()) + mHistory.push_back(mChatInput->getText()); + mCurHist = mHistory.end(); + mChatInput->setText(""); + } + else if (keyboard.isKeyActive(keyboard.KEY_AUTOCOMPLETE_CHAT) && + mChatInput->getText() != "") + { + autoComplete(); + return; + } + else if (keyboard.isKeyActive(keyboard.KEY_DEACTIVATE_CHAT) && + mChatInput->isVisible()) + { + mChatInput->setVisible(false); + } + else if (keyboard.isKeyActive(keyboard.KEY_CHAT_PREV_HISTORY) && + mChatInput->isVisible()) + { + ChatTab *tab = getFocused(); + if (tab && tab->getRows().size() > 0) + { + if (!mChatHistoryIndex) + { + mChatHistoryIndex = static_cast<unsigned>( + tab->getRows().size()); + + mChatInput->setText(""); + mChatInput->setCaretPosition(0); + return; + } + else + { + mChatHistoryIndex --; + } + + std::list<std::string>::iterator it; + unsigned int f = 0; + for (it = tab->getRows().begin(); + it != tab->getRows().end(); ++it, f++) + { + if (f == mChatHistoryIndex) + mChatInput->setText(*it); + } + mChatInput->setCaretPosition(static_cast<unsigned>( + mChatInput->getText().length())); + } + } + else if (keyboard.isKeyActive(keyboard.KEY_CHAT_NEXT_HISTORY) && + mChatInput->isVisible()) + { + ChatTab *tab = getFocused(); + if (tab && !tab->getRows().empty()) + { + if (mChatHistoryIndex + 1 < tab->getRows().size()) + { + mChatHistoryIndex ++; + } + else if (mChatHistoryIndex < tab->getRows().size()) + { + mChatHistoryIndex ++; + mChatInput->setText(""); + mChatInput->setCaretPosition(0); + return; + } + else + { + mChatHistoryIndex = 0; + } + + std::list<std::string>::iterator it; + unsigned int f = 0; + for (it = tab->getRows().begin(); + it != tab->getRows().end(); ++it, f++) + { + if (f == mChatHistoryIndex) + mChatInput->setText(*it); + } + mChatInput->setCaretPosition(static_cast<unsigned>( + mChatInput->getText().length())); + } + } + + std::string Temp; + switch (event.getKey().getValue()) + { + case Key::F2: Temp = "\u2318"; break; + case Key::F3: Temp = "\u263A"; break; + case Key::F4: Temp = "\u2665"; break; + case Key::F5: Temp = "\u266A"; break; + case Key::F6: Temp = "\u266B"; break; + case Key::F7: Temp = "\u26A0"; break; + case Key::F8: Temp = "\u2622"; break; + case Key::F9: Temp = "\u262E"; break; + case Key::F10: Temp = "\u2605"; break; + case Key::F11: Temp = "\u2618"; break; + case Key::F12: Temp = "\u2592"; break; + default: break; + } + + if (Temp != "") + addInputText(Temp, false); +} + +void ChatWindow::event(Channels channel, const Mana::Event &event) +{ + if (channel == CHANNEL_NOTICES) + { + if (event.getName() == EVENT_SERVERNOTICE && localChatTab) + localChatTab->chatLog(event.getString("message"), BY_SERVER); + } + else if (channel == CHANNEL_ATTRIBUTES) + { + if (!config.getBoolValue("showBattleEvents")) + return; + + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + switch (event.getInt("id")) + { + case EXP: + { + if (event.getInt("oldValue") > event.getInt("newValue")) + break; + + int change = event.getInt("newValue") + - event.getInt("oldValue"); + + if (change != 0) + battleChatLog("+" + toString(change) + " xp"); + break; + } + case LEVEL: + battleChatLog("Level: " + toString( + event.getInt("newValue"))); + break; + default: + break; + }; + } + } +} + +void ChatWindow::addInputText(const std::string &text, bool space) +{ + int caretPos = mChatInput->getCaretPosition(); + const std::string inputText = mChatInput->getText(); + + std::ostringstream ss; + ss << inputText.substr(0, caretPos) << text; + if (space) + ss << " "; + + ss << inputText.substr(caretPos); + + mChatInput->setText(ss.str()); + mChatInput->setCaretPosition(caretPos + static_cast<int>( + text.length()) + static_cast<int>(space)); + requestChatFocus(); +} + +void ChatWindow::addItemText(const std::string &item) +{ + std::ostringstream text; + text << "[" << item << "]"; + addInputText(text.str()); +} + +void ChatWindow::setVisible(bool isVisible) +{ + Window::setVisible(isVisible); + + /* + * For whatever reason, if setVisible is called, the mTmpVisible effect + * should be disabled. + */ + mTmpVisible = false; +} + +void ChatWindow::whisper(const std::string &nick, + const std::string &mes, Own own) +{ + if (mes.empty() || !player_node) + return; + + std::string playerName = player_node->getName(); + std::string tempNick = nick; + + toLower(playerName); + toLower(tempNick); + + if (tempNick.compare(playerName) == 0) + return; + + ChatTab *tab = 0; + TabMap::const_iterator i = mWhispers.find(tempNick); + + if (i != mWhispers.end()) + tab = i->second; + else if (config.getBoolValue("whispertab")) + tab = addWhisperTab(nick); + + if (tab) + { + if (own == BY_PLAYER) + { + tab->chatInput(mes); + } + else if (own == BY_SERVER) + { + tab->chatLog(mes); + } + else + { + tab->chatLog(nick, mes); + player_node->afkRespond(tab, nick); + } + } + else if (localChatTab) + { + if (own == BY_PLAYER) + { + Net::getChatHandler()->privateMessage(nick, mes); + + localChatTab->chatLog(strprintf(_("Whispering to %s: %s"), + nick.c_str(), mes.c_str()), BY_PLAYER); + } + else + { + localChatTab->chatLog(nick + " : " + mes, ACT_WHISPER, false); + if (player_node) + player_node->afkRespond(0, nick); + } + } +} + +ChatTab *ChatWindow::addWhisperTab(const std::string &nick, bool switchTo) +{ + if (!player_node) + return NULL; + + std::string playerName = player_node->getName(); + std::string tempNick = nick; + + toLower(playerName); + toLower(tempNick); + + TabMap::const_iterator i = mWhispers.find(tempNick); + ChatTab *ret; + + if (tempNick.compare(playerName) == 0) + return NULL; + + if (i != mWhispers.end()) + { + ret = i->second; + } + else + { + ret = new WhisperTab(nick); + mWhispers[tempNick] = ret; + if (config.getBoolValue("showChatHistory")) + ret->loadFromLogFile(nick); + } + + if (switchTo) + mChatTabs->setSelectedTab(ret); + + return ret; +} + +ChatTab *ChatWindow::getWhisperTab(const std::string &nick) const +{ + if (!player_node) + return NULL; + + std::string playerName = player_node->getName(); + std::string tempNick = nick; + + toLower(playerName); + toLower(tempNick); + + TabMap::const_iterator i = mWhispers.find(tempNick); + ChatTab *ret = 0; + + if (tempNick.compare(playerName) == 0) + return NULL; + + if (i != mWhispers.end()) + ret = i->second; + + return ret; +} + +std::string ChatWindow::addColors(std::string &msg) +{ + // default color or chat command + if (mChatColor == 0 || msg.length() == 0 || msg.at(0) == '#' + || msg.at(0) == '/' || msg.at(0) == '@' || msg.at(0) == '!') + { + return msg; + } + + std::string newMsg = ""; + int cMap[] = {1, 4, 5, 2, 3, 6, 7, 9, 0, 8}; + + // rainbow + switch(mChatColor) + { + case 11: + msg = removeColors(msg); + for (unsigned int f = 0; f < msg.length(); f ++) + { + newMsg += "##" + toString(mRainbowColor++) + msg.at(f); + if (mRainbowColor > 9) + mRainbowColor = 0; + } + return newMsg; + case 12: + msg = removeColors(msg); + for (unsigned int f = 0; f < msg.length(); f ++) + { + newMsg += "##" + toString(cMap[mRainbowColor++]) + msg.at(f); + if (mRainbowColor > 9) + mRainbowColor = 0; + } + return newMsg; + case 13: + msg = removeColors(msg); + for (unsigned int f = 0; f < msg.length(); f ++) + { + newMsg += "##" + toString(cMap[9-mRainbowColor++]) + msg.at(f); + if (mRainbowColor > 9) + mRainbowColor = 0; + } + return newMsg; + default: + break; + } + + // simple colors + return "##" + toString(mChatColor - 1) + msg; +} + +void ChatWindow::autoComplete() +{ + int caretPos = mChatInput->getCaretPosition(); + int startName = 0; + const std::string inputText = mChatInput->getText(); + std::string name = inputText.substr(0, caretPos); + std::string newName(""); + + for (int f = caretPos - 1; f > -1; f --) + { + if (isWordSeparator(inputText[f])) + { + startName = f + 1; + name = inputText.substr(f + 1, caretPos - f); + break; + } + } + + if (caretPos - 1 + 1 == startName) + return; + + ChatTab *cTab = static_cast<ChatTab*>(mChatTabs->getSelectedTab()); + std::vector<std::string> nameList; + + cTab->getAutoCompleteList(nameList); + newName = autoComplete(nameList, name); + + if (newName == "" && actorSpriteManager) + { + actorSpriteManager->getPlayerNames(nameList, true); + newName = autoComplete(nameList, name); + } + if (newName == "") + newName = autoCompleteHistory(name); + if (newName == "" && spellManager) + newName = spellManager->autoComplete(name); + if (newName == "") + newName = autoCompleteCommands(name); + if (newName == "" && actorSpriteManager) + { + actorSpriteManager->getMobNames(nameList); + newName = autoComplete(nameList, name); + } + + if (newName != "") + { + mChatInput->setText(inputText.substr(0, startName) + newName + + inputText.substr(caretPos, inputText.length() - caretPos)); + + int len = caretPos - static_cast<int>(name.length()) + + static_cast<int>(newName.length()); + + if (startName > 0) + mChatInput->setCaretPosition(len + 1); + else + mChatInput->setCaretPosition(len); + } +} + +std::string ChatWindow::autoComplete(std::vector<std::string> &names, + std::string partName) const +{ + std::vector<std::string>::iterator i = names.begin(); + toLower(partName); + std::string newName(""); + + while (i != names.end()) + { + if (!i->empty()) + { + std::string name = *i; + toLower(name); + + std::string::size_type pos = name.find(partName, 0); + if (pos == 0) + { + if (newName != "") + { + toLower(newName); + newName = findSameSubstring(name, newName); + } + else + { + newName = *i; + } + } + } + ++i; + } + + return newName; +} + +std::string ChatWindow::autoCompleteCommands(std::string partName) +{ + Commands::iterator i = mCommands.begin(); + std::vector<std::string> nameList; + + while (i != mCommands.end()) + { + std::string line = *i; + std::string::size_type pos = line.find(partName, 0); + if (pos == 0) + nameList.push_back(line); + ++i; + } + return autoComplete(nameList, partName); +} + +/* +void ChatWindow::moveTabLeft(ChatTab *tab) +{ + mChatTabs->moveLeft(tab); +} + +void ChatWindow::moveTabRight(ChatTab *tab) +{ + mChatTabs->moveRight(tab); +} +*/ + +std::string ChatWindow::autoCompleteHistory(std::string partName) +{ + History::iterator i = mHistory.begin(); + std::vector<std::string> nameList; + + while (i != mHistory.end()) + { + std::string line = *i; + unsigned int f = 0; + while (f < line.length() && !isWordSeparator(line.at(f))) + f++; + + line = line.substr(0, f); + if (line != "") + nameList.push_back(line); + + ++i; + } + return autoComplete(nameList, partName); +} + +void ChatWindow::resortChatLog(std::string line, Own own, + bool ignoreRecord, bool tryRemoveColors) +{ + if (own == -1) + own = BY_SERVER; + + if (tradeChatTab) + { + if (findI(line, mTradeFilter) != std::string::npos) + { +// logger->log("trade: " + line); + tradeChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + return; + } + + unsigned long idx = line.find(": \302\202"); + if (idx != std::string::npos) + { + line = line.erase(idx + 2, 2); + tradeChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + return; + } + + unsigned long idx1 = line.find("@@"); + if (idx1 != std::string::npos) + { + unsigned long idx2 = line.find("|", idx1); + if (idx2 != std::string::npos) + { + unsigned long idx3 = line.find("@@", idx2); + if (idx3 != std::string::npos) + { + tradeChatTab->chatLog(line, own, ignoreRecord, + tryRemoveColors); + return; + } + } + } + } + + if (localChatTab) + localChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); +} + +void ChatWindow::battleChatLog(std::string line, Own own, + bool ignoreRecord, bool tryRemoveColors) +{ + if (own == -1) + own = BY_SERVER; + if (battleChatTab) + battleChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + else if (debugChatTab) + debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); +} + +void ChatWindow::initTradeFilter() +{ + std::string tradeListName = Client::getServerConfigDirectory() + + "/tradefilter.txt"; + + std::ifstream tradeFile; + struct stat statbuf; + + if (!stat(tradeListName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) + { + tradeFile.open(tradeListName.c_str(), std::ios::in); + char line[100]; + while (tradeFile.getline(line, 100)) + { + std::string str = line; + if (!str.empty()) + mTradeFilter.push_back(str); + } + tradeFile.close(); + } +} + +void ChatWindow::updateOnline(std::set<std::string> &onlinePlayers) +{ + TabMap::iterator iter; + const Party *party = 0; + const Guild *guild = 0; + if (player_node) + { + party = player_node->getParty(); + guild = player_node->getGuild(); + } + for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter) + { + if (!iter->second) + return; + + WhisperTab *tab = static_cast<WhisperTab*>(iter->second); + + if (!tab) + continue; + + if (onlinePlayers.find(tab->getNick()) != onlinePlayers.end()) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + } + else + { + const std::string nick = tab->getNick(); + if (actorSpriteManager) + { + const Being *being = actorSpriteManager->findBeingByName( + nick, ActorSprite::PLAYER); + if (being) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + continue; + } + } + if (party) + { + const PartyMember *pm = party->getMember(nick); + if (pm && pm->getOnline()) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + continue; + } + } + if (guild) + { + const GuildMember *gm = guild->getMember(nick); + if (gm && gm->getOnline()) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + continue; + } + } + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER_OFFLINE)); + } + } +} + +void ChatWindow::loadState() +{ + int num = 0; + while (num < 50) + { + std::string nick = serverConfig.getValue( + "chatWhisper" + toString(num), ""); + + if (nick.empty()) + break; + addWhisperTab(nick); + serverConfig.deleteKey("chatWhisper" + toString(num)); + num ++; + } + + while (num < 50) + { + serverConfig.deleteKey("chatWhisper" + toString(num)); + num ++; + } +} + +void ChatWindow::saveState() +{ + int num = 0; + TabMap::iterator iter; + for (iter = mWhispers.begin(); iter != mWhispers.end() && num < 50; ++iter) + { + if (!iter->second) + return; + + WhisperTab *tab = static_cast<WhisperTab*>(iter->second); + + if (!tab) + continue; + + serverConfig.setValue("chatWhisper" + toString(num), + tab->getNick()); + + num ++; + } + + while (num < 50) + { + serverConfig.deleteKey("chatWhisper" + toString(num)); + num ++; + } +} + +std::string ChatWindow::doReplace(const std::string &msg) +{ + if (Client::getServerName() == "server.themanaworld.org" + || Client::getServerName() == "themanaworld.org" + || Client::getServerName() == "81.161.192.4") + { + return msg; + } + + std::string str = msg; + replaceSpecialChars(str); + return str; +} diff --git a/src/gui/chat.h b/src/gui/chat.h new file mode 100644 index 000000000..444057b57 --- /dev/null +++ b/src/gui/chat.h @@ -0,0 +1,318 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CHAT_H +#define CHAT_H + +#include "listener.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> +#include <guichan/widget.hpp> +#include <guichan/widgetlistener.hpp> + +#include <list> +#include <string> +#include <map> +#include <vector> +#include <set> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class BrowserBox; +class ChatTab; +class Channel; +class ChatInput; +class ColorListModel; +class ScrollArea; +class TabbedArea; +class ItemLinkHandler; +class Tab; +class WhisperTab; + +#define DEFAULT_CHAT_WINDOW_SCROLL 7 // 1 means `1/8th of the window size'. + +enum Own +{ + BY_GM = 0, + BY_PLAYER, + BY_OTHER, + BY_SERVER, + BY_CHANNEL, + ACT_WHISPER, // getting whispered at + ACT_IS, // equivalent to "/me" on IRC + BY_LOGGER, + BY_UNKNOWN = -1 +}; + +/** One item in the chat log */ +struct CHATLOG +{ + std::string nick; + std::string text; + Own own; +}; + +/** + * The chat window. + * + * \ingroup Interface + */ +class ChatWindow : public Window, + public gcn::ActionListener, + public gcn::KeyListener, + public Mana::Listener +{ + public: + /** + * Constructor. + */ + ChatWindow(); + + /** + * Destructor: used to write back values to the config file + */ + ~ChatWindow(); + + /** + * Reset the chat window to default positions. + */ + void resetToDefaultSize(); + + /** + * Gets the focused tab. + */ + ChatTab *getFocused() const; + + /** + * Clear the given tab. + */ + void clearTab(ChatTab *tab); + + /** + * Clear the current tab. + */ + void clearTab(); + + /** + * Switch to the previous tab in order + */ + void prevTab(); + + /** + * Switch to the next tab in order + */ + void nextTab(); + + /** + * Switch to the default tab + */ + void defaultTab(); + + /** + * Performs action. + */ + void action(const gcn::ActionEvent &event); + + /** + * Request focus for typing chat message. + * + * \returns true if the input was shown + * false otherwise + */ + bool requestChatFocus(); + + /** + * Checks whether ChatWindow is Focused or not. + */ + bool isInputFocused() const; + + /** + * Passes the text to the current tab as input + * + * @param msg The message text which is to be sent. + */ + void chatInput(const std::string &msg); + + /** + * Passes the text to the local chat tab as input + * + * @param msg The message text which is to be sent. + */ + void localChatInput(const std::string &msg); + + /** Called when key is pressed */ + void keyPressed(gcn::KeyEvent &event); + + /** Set the chat input as the given text. */ + void setInputText(const std::string &text); + + /** Add the given text to the chat input. */ + void addInputText(const std::string &text, bool space = true); + + /** Called to add item to chat */ + void addItemText(const std::string &item); + + /** Override to reset mTmpVisible */ + void setVisible(bool visible); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + void event(Channels channel, const Mana::Event &event); + + /** + * Scrolls the chat window + * + * @param amount direction and amount to scroll. Negative numbers scroll + * up, positive numbers scroll down. The absolute amount indicates the + * amount of 1/8ths of chat window real estate that should be scrolled. + */ + void scroll(int amount); + + /** + * Sets the file being recorded to + * + * @param msg The file to write out to. If null, then stop recording. + */ + void setRecordingFile(const std::string &msg); + + bool getReturnTogglesChat() const + { return mReturnToggles; } + + void setReturnTogglesChat(bool toggles) + { mReturnToggles = toggles; } + + void doPresent(); + + void whisper(const std::string &nick, const std::string &mes, + Own own = BY_OTHER); + + ChatTab *addWhisperTab(const std::string &nick, bool switchTo = false); + + ChatTab *getWhisperTab(const std::string &nick) const; + + void removeAllWhispers(); + + void ignoreAllWhispers(); + + void resortChatLog(std::string line, Own own = BY_UNKNOWN, + bool ignoreRecord = false, + bool tryRemoveColors = true); + + void battleChatLog(std::string line, Own own = BY_UNKNOWN, + bool ignoreRecord = false, + bool tryRemoveColors = true); + + void updateOnline(std::set<std::string> &onlinePlayers); + + void loadState(); + + void saveState(); + + std::string doReplace(const std::string &msg); + + protected: + friend class ChatTab; + friend class WhisperTab; + friend class PopupMenu; + + /** Remove the given tab from the window */ + void removeTab(ChatTab *tab); + + /** Add the tab to the window */ + void addTab(ChatTab *tab); + + void removeWhisper(const std::string &nick); + + void adjustTabSize(); + + void autoComplete(); + + std::string addColors(std::string &msg); + + std::string autoCompleteHistory(std::string partName); + + std::string autoCompleteCommands(std::string partName); + + std::string autoComplete(std::vector<std::string> &names, + std::string partName) const; + + /** Used for showing item popup on clicking links **/ + ItemLinkHandler *mItemLinkHandler; + + /** Input box for typing chat messages. */ + ChatInput *mChatInput; + + void widgetResized(const gcn::Event &event); + + void initTradeFilter(); + + int mRainbowColor; + + private: + void fillCommands(); + + bool mTmpVisible; + + /** Tabbed area for holding each channel. */ + TabbedArea *mChatTabs; + + typedef std::map<const std::string, ChatTab*> TabMap; + /** Manage whisper tabs */ + TabMap mWhispers; + + typedef std::list<std::string> History; + typedef History::iterator HistoryIterator; + History mHistory; /**< Command history. */ + HistoryIterator mCurHist; /**< History iterator. */ + + typedef std::list<std::string> Commands; + typedef Commands::iterator CommandsIterator; + History mCommands; /**< Command list. */ + + bool mReturnToggles; /**< Marks whether <Return> toggles the chat log + or not */ + + std::list<std::string> mTradeFilter; + + gcn::DropDown *mColorPicker; + ColorListModel *mColorListModel; + int mChatColor; + unsigned int mChatHistoryIndex; +}; + +extern ChatWindow *chatWindow; + +#endif diff --git a/src/gui/confirmdialog.cpp b/src/gui/confirmdialog.cpp new file mode 100644 index 000000000..1d24a25bd --- /dev/null +++ b/src/gui/confirmdialog.cpp @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/confirmdialog.h" + +#include "sound.h" + +#include "gui/gui.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/textbox.h" + +#include "utils/gettext.h" + +#include <guichan/font.hpp> + +ConfirmDialog::ConfirmDialog(const std::string &title, const std::string &msg, + bool ignore, bool modal, Window *parent): + Window(title, modal, parent) +{ + mTextBox = new TextBox; + mTextBox->setEditable(false); + mTextBox->setOpaque(false); + mTextBox->setTextWrapped(msg, 260); + + gcn::Button *yesButton = new Button(_("Yes"), "yes", this); + gcn::Button *noButton = new Button(_("No"), "no", this); + gcn::Button *ignoreButton = NULL; + + if (ignore) + ignoreButton = new Button(_("Ignore"), "ignore", this); + + const int numRows = mTextBox->getNumberOfRows(); + int inWidth = yesButton->getWidth() + noButton->getWidth() + + (2 * getPadding()); + + if (ignoreButton) + inWidth += ignoreButton->getWidth(); + + const int fontHeight = getFont()->getHeight(); + const int height = numRows * fontHeight; + int width = getFont()->getWidth(title); + + if (width < mTextBox->getMinWidth()) + width = mTextBox->getMinWidth(); + if (width < inWidth) + width = inWidth; + + setContentSize(mTextBox->getMinWidth() + fontHeight, height + fontHeight + + noButton->getHeight()); + mTextBox->setPosition(getPadding(), getPadding()); + + // 8 is the padding that GUIChan adds to button widgets + // (top and bottom combined) + yesButton->setPosition((width - inWidth) / 2, height + 8); + noButton->setPosition(yesButton->getX() + yesButton->getWidth() + + (2 * getPadding()), + height + 8); + if (ignoreButton) + { + ignoreButton->setPosition(noButton->getX() + noButton->getWidth() + + (2 * getPadding()), height + 8); + } + + add(mTextBox); + add(yesButton); + add(noButton); + + if (ignore && ignoreButton) + add(ignoreButton); + + if (getParent()) + { + center(); + getParent()->moveToTop(this); + } + setVisible(true); + yesButton->requestFocus(); + sound.playGuiSfx("system/newmessage.ogg"); +} + +void ConfirmDialog::action(const gcn::ActionEvent &event) +{ + setActionEventId(event.getId()); + distributeActionEvent(); + + // Can we receive anything else anyway? + if (event.getId() == "yes" || event.getId() == "no" + || event.getId() == "ignore") + { + scheduleDelete(); + } +} + diff --git a/src/gui/confirmdialog.h b/src/gui/confirmdialog.h new file mode 100644 index 000000000..0ba9c869b --- /dev/null +++ b/src/gui/confirmdialog.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OPTION_DIALOG_H +#define OPTION_DIALOG_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +class TextBox; + +/** + * An option dialog. + * + * \ingroup GUI + */ +class ConfirmDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + ConfirmDialog(const std::string &title, const std::string &msg, + bool ignore = false, bool modal = false, + Window *parent = NULL); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + TextBox *mTextBox; +}; + +#endif diff --git a/src/gui/connectiondialog.cpp b/src/gui/connectiondialog.cpp new file mode 100644 index 000000000..8f960d335 --- /dev/null +++ b/src/gui/connectiondialog.cpp @@ -0,0 +1,65 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "connectiondialog.h" + +#include "log.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/progressindicator.h" + +#include "utils/gettext.h" + +ConnectionDialog::ConnectionDialog(const std::string &text, + State cancelState): + Window(""), + mCancelState(cancelState) +{ + setTitleBarHeight(0); + setMovable(false); + setMinWidth(0); + + ProgressIndicator *progressIndicator = new ProgressIndicator; + gcn::Label *label = new Label(text); + Button *cancelButton = new Button(_("Cancel"), "cancelButton", this); + + place(0, 0, progressIndicator); + place(0, 1, label); + place(0, 2, cancelButton).setHAlign(LayoutCell::CENTER); + reflowLayout(); + + center(); + setVisible(true); +} + +void ConnectionDialog::action(const gcn::ActionEvent &) +{ + logger->log1("Cancel pressed"); + Client::setState(mCancelState); +} + +void ConnectionDialog::draw(gcn::Graphics *graphics) +{ + // Don't draw the window background, only draw the children + drawChildren(graphics); +} diff --git a/src/gui/connectiondialog.h b/src/gui/connectiondialog.h new file mode 100644 index 000000000..623a66455 --- /dev/null +++ b/src/gui/connectiondialog.h @@ -0,0 +1,62 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "client.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +/** + * The connection dialog. + * + * \ingroup Interface + */ +class ConnectionDialog : public Window, gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @param text The text to display + * @param cancelState The state to enter when Cancel is pressed + * + * @see Window::Window + */ + ConnectionDialog(const std::string &text, State cancelState); + + /** + * Called when the user presses Cancel. Restores the global state to + * the previous one. + */ + void action(const gcn::ActionEvent &); + + void draw(gcn::Graphics *graphics); + + private: + gcn::Label *mLabel; + State mCancelState; +}; + +#endif diff --git a/src/gui/debugwindow.cpp b/src/gui/debugwindow.cpp new file mode 100644 index 000000000..2feb30c80 --- /dev/null +++ b/src/gui/debugwindow.cpp @@ -0,0 +1,248 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/debugwindow.h" + +#include "client.h" +#include "game.h" +#include "localplayer.h" +#include "main.h" +#include "map.h" +#include "particle.h" + +#include "gui/setup.h" +#include "gui/setup_video.h" +#include "gui/viewport.h" + +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/chattab.h" + +#include "resources/image.h" + +#include "net/packetcounters.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +DebugWindow::DebugWindow(): + Window(_("Debug")) +{ + setWindowName("Debug"); + if (setupWindow) + setupWindow->registerWindowForReset(this); + + mUpdateTime = 0; + setResizable(true); + setCloseButton(true); + setSaveVisible(true); + setDefaultSize(500, 150, ImageRect::CENTER); + +#ifdef USE_OPENGL + switch (Image::getLoadAsOpenGL()) + { + case 0: + mFPSText = _("%d FPS (Software)"); + break; + case 1: + default: + mFPSText = _("%d FPS (fast OpenGL)"); + break; + case 2: + mFPSText = _("%d FPS (old OpenGL)"); + break; + }; +#else + mFPSText = _("%d FPS (Software)"); +#endif + + mFPSLabel = new Label(strprintf(_("%d FPS"), 0)); + mMusicFileLabel = new Label(strprintf(_("Music:"))); + mMapLabel = new Label(strprintf(_("Map:"))); + mMinimapLabel = new Label(strprintf(_("Minimap:"))); + mTileMouseLabel = new Label(strprintf(_("Cursor: (%d, %d)"), 0, 0)); + mParticleCountLabel = new Label(strprintf(_("Particle count: %d"), 88888)); + mMapActorCountLabel = new Label(strprintf( + _("Map actors count: %d"), 88888)); + + mPingLabel = new Label(" "); + mInPackets1Label = new Label(" "); + mOutPackets1Label = new Label(" "); + + mXYLabel = new Label(strprintf("%s (?,?)", _("Player Position:"))); + mTargetLabel = new Label(strprintf("%s ?", _("Target:"))); + mTargetIdLabel = new Label(strprintf("%s ? ", _("Target Id:"))); + mTargetLevelLabel = new Label(strprintf("%s ?", _("Target Level:"))); + mTargetPartyLabel = new Label(strprintf("%s ?", _("Target Party:"))); + mTargetGuildLabel = new Label(strprintf("%s ?", _("Target Guild:"))); + + place(0, 0, mFPSLabel, 3); + place(4, 0, mTileMouseLabel, 2); + place(0, 1, mMusicFileLabel, 3); + place(4, 1, mParticleCountLabel, 2); + place(4, 2, mMapActorCountLabel, 2); + place(0, 2, mMapLabel, 4); + place(0, 3, mMinimapLabel, 4); + place(0, 4, mXYLabel, 4); + place(4, 3, mPingLabel, 2); + place(4, 4, mInPackets1Label, 2); + place(4, 5, mOutPackets1Label, 2); + place(0, 5, mTargetLabel, 4); + place(0, 6, mTargetIdLabel, 4); + place(0, 7, mTargetLevelLabel, 4); + place(0, 8, mTargetPartyLabel, 4); + place(0, 9, mTargetGuildLabel, 4); + + loadWindowState(); +} + +void DebugWindow::logic() +{ + if (!isVisible()) + return; + + mFPSLabel->setCaption(strprintf(mFPSText.c_str(), fps)); + + if (player_node) + { + mXYLabel->setCaption(strprintf("%s (%d, %d)", _("Player Position:"), + player_node->getTileX(), player_node->getTileY())); + } + else + { + mXYLabel->setCaption(strprintf("%s (?, ?)", _("Player Position:"))); + } + + if (player_node && player_node->getTarget()) + { + Being *target = player_node->getTarget(); + + mTargetLabel->setCaption(strprintf("%s %s (%d, %d)", _("Target:"), + target->getName().c_str(), target->getTileX(), + target->getTileY())); + + mTargetIdLabel->setCaption(strprintf("%s %d", + _("Target Id:"), target->getId())); + if (target->getLevel()) + { + mTargetLevelLabel->setCaption(strprintf("%s %d", + _("Target Level:"), target->getLevel())); + } + else + { + mTargetLevelLabel->setCaption(strprintf("%s ?", + _("Target Level:"))); + } + + mTargetPartyLabel->setCaption(strprintf("%s %s", _("Target Party:"), + target->getPartyName().c_str())); + + mTargetGuildLabel->setCaption(strprintf("%s %s", _("Target Guild:"), + target->getGuildName().c_str())); + } + else + { + mTargetLabel->setCaption(strprintf("%s ?", _("Target:"))); + mTargetIdLabel->setCaption(strprintf("%s ?", _("Target Id:"))); + mTargetLevelLabel->setCaption(strprintf("%s ?", _("Target Level:"))); + mTargetPartyLabel->setCaption(strprintf("%s ?", _("Target Party:"))); + mTargetGuildLabel->setCaption(strprintf("%s ?", _("Target Guild:"))); + } + + const Map *map = Game::instance()->getCurrentMap(); + if (map && viewport) + { + // Get the current mouse position + int mouseTileX = (viewport->getMouseX() + viewport->getCameraX()) + / map->getTileWidth(); + int mouseTileY = (viewport->getMouseY() + viewport->getCameraY()) + / map->getTileHeight(); + mTileMouseLabel->setCaption(strprintf("%s (%d, %d)", + _("Cursor:"), mouseTileX, mouseTileY)); + + mMusicFileLabel->setCaption(strprintf("%s %s", _("Music:"), + map->getProperty("music").c_str())); + mMinimapLabel->setCaption(strprintf("%s %s", _("Minimap:"), + map->getProperty("minimap").c_str())); + mMapLabel->setCaption(strprintf("%s %s", _("Map:"), + map->getProperty("_filename").c_str())); + + + if (mUpdateTime != cur_time) + { + mUpdateTime = cur_time; + mParticleCountLabel->setCaption(strprintf(_("Particle count: %d"), + Particle::particleCount)); + + mMapActorCountLabel->setCaption( + strprintf("%s %d", _("Map actors count:"), + map->getActorsCount())); + } + } + else + { + mTileMouseLabel->setCaption(strprintf("%s (?, ?)", _("Cursor:"))); + + mMusicFileLabel->setCaption(strprintf("%s ?", _("Music:"))); + mMinimapLabel->setCaption(strprintf("%s ?", _("Minimap:"))); + mMapLabel->setCaption(strprintf("%s ?", _("Map:"))); + + mMapActorCountLabel->setCaption( + strprintf("%s ?", _("Map actors count:"))); + } + + mMapActorCountLabel->adjustSize(); + mParticleCountLabel->adjustSize(); + + if (player_node && player_node->getPingTime() != 0) + { + mPingLabel->setCaption(strprintf(_("Ping: %d ms"), + player_node->getPingTime())); + } + else + { + mPingLabel->setCaption(_("Ping: ? ms")); + } + + mInPackets1Label->setCaption(strprintf(_("In: %d bytes/s"), + PacketCounters::getInBytes())); + mOutPackets1Label->setCaption(strprintf(_("Out: %d bytes/s"), + PacketCounters::getOutBytes())); + + if (player_node) + player_node->tryPingRequest(); +} + +void DebugWindow::draw(gcn::Graphics *g) +{ + Window::draw(g); + + if (player_node) + { + Being *target = player_node->getTarget(); + if (target) + { + Graphics *g2 = static_cast<Graphics*>(g); + target->draw(g2, -target->getPixelX() + 16 + getWidth() / 2, + -target->getPixelY() + 32 + getHeight() / 2); + } + } +} diff --git a/src/gui/debugwindow.h b/src/gui/debugwindow.h new file mode 100644 index 000000000..1c97f8ca9 --- /dev/null +++ b/src/gui/debugwindow.h @@ -0,0 +1,72 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DEBUGWINDOW_H +#define DEBUGWINDOW_H + +#include "gui/widgets/window.h" + +class Label; + +/** + * The debug window. + * + * \ingroup Interface + */ +class DebugWindow : public Window +{ + public: + /** + * Constructor. + */ + DebugWindow(); + + /** + * Logic (updates components' size and infos) + */ + void logic(); + + void draw(gcn::Graphics *g); + + void setPing(int pingTime); + + private: + Label *mMusicFileLabel, *mMapLabel, *mMinimapLabel; + Label *mTileMouseLabel, *mFPSLabel; + Label *mParticleCountLabel; + Label *mMapActorCountLabel; + Label *mXYLabel; + Label *mTargetLabel; + Label *mTargetIdLabel; + Label *mTargetLevelLabel; + Label *mTargetPartyLabel; + Label *mTargetGuildLabel; + Label *mPingLabel; + Label *mInPackets1Label; + Label *mOutPackets1Label; + + std::string mFPSText; + int mUpdateTime; +}; + +extern DebugWindow *debugWindow; + +#endif diff --git a/src/gui/editdialog.cpp b/src/gui/editdialog.cpp new file mode 100644 index 000000000..b8d999ce7 --- /dev/null +++ b/src/gui/editdialog.cpp @@ -0,0 +1,73 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/editdialog.h" + +#include "gui/gui.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/textfield.h" + +#include "utils/gettext.h" + +#include <guichan/font.hpp> + +EditDialog::EditDialog(const std::string &title, const std::string &msg, + std::string eventOk, int width, + Window *parent, bool modal): + Window(title, modal, parent) +{ + mTextField = new TextField; + mTextField->setText(msg); + + mEventOk = eventOk; + + gcn::Button *okButton = new Button(_("OK"), mEventOk, this); + + const int numRows = 1; + const int fontHeight = getFont()->getHeight(); + const int height = numRows * fontHeight; + + setContentSize(width, height + fontHeight + okButton->getHeight()); + mTextField->setPosition(getPadding(), getPadding()); + mTextField->setWidth(width - (2 * getPadding())); + + okButton->setPosition((width - okButton->getWidth()) / 2, height + 8); + + add(mTextField); + add(okButton); + + center(); + setVisible(true); + okButton->requestFocus(); +} + +void EditDialog::action(const gcn::ActionEvent &event) +{ + // Proxy button events to our listeners + ActionListenerIterator i; + for (i = mActionListeners.begin(); i != mActionListeners.end(); ++i) + (*i)->action(event); + + if (event.getId() == mEventOk) + scheduleDelete(); +} diff --git a/src/gui/editdialog.h b/src/gui/editdialog.h new file mode 100644 index 000000000..55947b23d --- /dev/null +++ b/src/gui/editdialog.h @@ -0,0 +1,66 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef EDIT_DIALOG_H +#define EDIT_DIALOG_H + +#include "gui/widgets/window.h" +#include "gui/widgets/textfield.h" + +#include <guichan/actionlistener.hpp> + +#define ACTION_EDIT_OK "edit ok" + +class TextField; + +/** + * An 'Ok' button dialog. + * + * \ingroup GUI + */ +class EditDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + EditDialog(const std::string &title, const std::string &msg, + std::string eventOk = ACTION_EDIT_OK, int width = 300, + Window *parent = NULL, bool modal = true); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + std::string getMsg() + { return mTextField->getText(); } + + private: + std::string mEventOk; + + TextField *mTextField; +}; + +#endif diff --git a/src/gui/emotepopup.cpp b/src/gui/emotepopup.cpp new file mode 100644 index 000000000..2a0d94aca --- /dev/null +++ b/src/gui/emotepopup.cpp @@ -0,0 +1,214 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/emotepopup.h" + +#include "animatedsprite.h" +#include "configuration.h" +#include "emoteshortcut.h" +#include "graphics.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "resources/emotedb.h" +#include "resources/image.h" +#include "resources/iteminfo.h" + +#include "utils/dtor.h" + +#include <guichan/mouseinput.hpp> +#include <guichan/selectionlistener.hpp> + +const int EmotePopup::gridWidth = 34; // emote icon width + 4 +const int EmotePopup::gridHeight = 36; // emote icon height + 4 + +static const int MAX_COLUMNS = 6; + +EmotePopup::EmotePopup(): + mSelectedEmoteIndex(-1), + mHoveredEmoteIndex(-1), + mRowCount(1), + mColumnCount(1) +{ + // Setup emote sprites + for (int i = 0; i <= EmoteDB::getLast(); ++i) + { + const AnimatedSprite *sprite = EmoteDB::getAnimation(i, true); + if (sprite) + mEmotes.push_back(sprite); + } + + mSelectionImage = Theme::getImageFromTheme("selection.png"); + if (!mSelectionImage) + logger->log1("Error: Unable to load selection.png"); + + if (mSelectionImage) + mSelectionImage->setAlpha(Client::getGuiAlpha()); + + addMouseListener(this); + recalculateSize(); + setVisible(true); +} + +EmotePopup::~EmotePopup() +{ + if (mSelectionImage) + mSelectionImage->decRef(); +} + +void EmotePopup::draw(gcn::Graphics *graphics) +{ + Popup::draw(graphics); + + if (!mColumnCount) + return; + + const unsigned int emoteCount = static_cast<unsigned>(mEmotes.size()); + const unsigned int emotesLeft + = static_cast<unsigned>(mEmotes.size() % mColumnCount); + + for (unsigned int i = 0; i < emoteCount ; i++) + { + int row = i / mColumnCount; + int column = i % mColumnCount; + + unsigned int emoteX = 4 + column * gridWidth; + unsigned int emoteY = 4 + row * gridHeight; + + // Center the last row when there are less emotes than columns + if (emotesLeft > 0 && row == mRowCount - 1) + emoteX += (mColumnCount - emotesLeft) * gridWidth / 2; + + // Draw selection image below hovered item + if (mSelectionImage && static_cast<int>(i) == mHoveredEmoteIndex) + { + static_cast<Graphics*>(graphics)->drawImage( + mSelectionImage, emoteX, emoteY + 4); + } + + // Draw emote icon + if (mEmotes[i]) + mEmotes[i]->draw(static_cast<Graphics*>(graphics), emoteX, emoteY); + } +} + +void EmotePopup::mousePressed(gcn::MouseEvent &event) +{ + if (event.getButton() != gcn::MouseEvent::LEFT) + return; + + const int index = getIndexAt(event.getX(), event.getY()); + if (index != -1) + { + setSelectedEmoteIndex(index); + if (emoteShortcut) + { + emoteShortcut->setEmoteSelected( + static_cast<unsigned char>(index + 1)); + } + } +} + +void EmotePopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + mHoveredEmoteIndex = getIndexAt(event.getX(), event.getY()); +} + +int EmotePopup::getSelectedEmote() const +{ + return 1 + mSelectedEmoteIndex; +} + +void EmotePopup::setSelectedEmoteIndex(int index) +{ + if (index == mSelectedEmoteIndex) + return; + + mSelectedEmoteIndex = index; + distributeValueChangedEvent(); +} + +int EmotePopup::getIndexAt(int x, int y) const +{ + if (!gridWidth || !gridHeight) + return -1; + + const unsigned int emotesLeft + = static_cast<unsigned>(mEmotes.size() % mColumnCount); + const unsigned int row = y / gridHeight; + unsigned int column; + + // Take into account that the last row is centered + if (emotesLeft > 0 && static_cast<signed>(row) == mRowCount - 1) + { + int unsigned emotesMissing = mColumnCount - emotesLeft; + column = std::min((x - emotesMissing * gridWidth / 2) / gridWidth, + emotesLeft - 1); + } + else + { + column = std::min(x / gridWidth, mColumnCount - 1); + } + + int unsigned index = column + (row * mColumnCount); + + if (index < mEmotes.size()) + return index; + + return -1; +} + +void EmotePopup::recalculateSize() +{ + const unsigned emoteCount = static_cast<unsigned>(mEmotes.size()); + + mRowCount = emoteCount / MAX_COLUMNS; + if (emoteCount % MAX_COLUMNS > 0) + ++mRowCount; + + if (mRowCount) + mColumnCount = emoteCount / mRowCount; + else + mColumnCount = 1; + + if (emoteCount % mRowCount > 0) + ++mColumnCount; + + setContentSize(mColumnCount * gridWidth, mRowCount * gridHeight); +} + +void EmotePopup::distributeValueChangedEvent() +{ + gcn::SelectionEvent event(this); + Listeners::const_iterator i_end = mListeners.end(); + Listeners::const_iterator i; + + for (i = mListeners.begin(); i != i_end; ++i) + { + if (*i) + (*i)->valueChanged(event); + } +} diff --git a/src/gui/emotepopup.h b/src/gui/emotepopup.h new file mode 100644 index 000000000..c1026d0a5 --- /dev/null +++ b/src/gui/emotepopup.h @@ -0,0 +1,121 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef EMOTEPOPUP_H +#define EMOTEPOPUP_H + +#include "gui/widgets/popup.h" + +#include <guichan/mouselistener.hpp> + +#include <list> +#include <vector> + +class AnimatedSprite; +class Image; + +namespace gcn +{ + class SelectionListener; +} + +/** + * An emote popup. Used to activate emotes and assign them to shortcuts. + * + * \ingroup GUI + */ +class EmotePopup : public Popup +{ + public: + /** + * Constructor. Initializes the graphic. + */ + EmotePopup(); + + virtual ~EmotePopup(); + + /** + * Draws the emotes. + */ + void draw(gcn::Graphics *graphics); + + void mousePressed(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + /** + * Returns the selected emote. + */ + int getSelectedEmote() const; + + /** + * Adds a listener to the list that's notified each time a change to + * the selection occurs. + */ + void addSelectionListener(gcn::SelectionListener *listener) + { mListeners.push_back(listener); } + + /** + * Removes a listener from the list that's notified each time a change + * to the selection occurs. + */ + void removeSelectionListener(gcn::SelectionListener *listener) + { mListeners.remove(listener); } + + private: + /** + * Sets the index of the currently selected emote. + */ + void setSelectedEmoteIndex(int index); + + /** + * Returns the index at the specified coordinates. Returns -1 when + * there is no valid index. + */ + int getIndexAt(int x, int y) const; + + /** + * Determine and set the size of the container. + */ + void recalculateSize(); + + /** + * Sends out selection events to the list of selection listeners. + */ + void distributeValueChangedEvent(); + + std::vector<const AnimatedSprite*> mEmotes; + Image *mSelectionImage; + int mSelectedEmoteIndex; + int mHoveredEmoteIndex; + + int mRowCount; + int mColumnCount; + + typedef std::list<gcn::SelectionListener*> Listeners; + + Listeners mListeners; + + static const int gridWidth; + static const int gridHeight; +}; + +#endif diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp new file mode 100644 index 000000000..dc5dc8c04 --- /dev/null +++ b/src/gui/equipmentwindow.cpp @@ -0,0 +1,260 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/button.h" + +#include "equipment.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" + +#include "gui/equipmentwindow.h" +#include "gui/itempopup.h" +#include "gui/theme.h" +#include "gui/setup.h" +#include "gui/viewport.h" + +#include "gui/widgets/playerbox.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> + +static const int BOX_WIDTH = 36; +static const int BOX_HEIGHT = 36; + +// Positions of the boxes, 2nd dimension is X and Y respectively. +static const int boxPosition[][2] = +{ + { 90, 40 }, // EQUIP_TORSO_SLOT + { 8, 78 }, // EQUIP_GLOVES_SLOT + { 70, 0 }, // EQUIP_HEAD_SLOT + { 50, 253 }, // EQUIP_LEGS_SLOT + { 90, 253 }, // EQUIP_FEET_SLOT + { 8, 213 }, // EQUIP_RING1_SLOT + { 129, 213 }, // EQUIP_RING2_SLOT + { 50, 40 }, // EQUIP_NECK_SLOT + { 8, 168 }, // EQUIP_FIGHT1_SLOT + { 129, 168 }, // EQUIP_FIGHT2_SLOT + { 129, 78 }, // EQUIP_PROJECTILE_SLOT + { 8, 123 }, // EQUIP_EVOL_RING1_SLOT + { 129, 123 }, // EQUIP_EVOL_RING2_SLOT +}; + +EquipmentWindow::EquipmentWindow(Equipment *equipment): + Window(_("Equipment")), + mEquipment(equipment), + mSelected(-1) +{ + mItemPopup = new ItemPopup; + if (setupWindow) + setupWindow->registerWindowForReset(this); + + // Control that shows the Player + PlayerBox *playerBox = new PlayerBox; + playerBox->setDimension(gcn::Rectangle(50, 80, 74, 168)); + playerBox->setPlayer(player_node); + + setWindowName("Equipment"); + setCloseButton(true); + setSaveVisible(true); + setDefaultSize(180, 345, ImageRect::CENTER); + loadWindowState(); + + mUnequip = new Button(_("Unequip"), "unequip", this); + const gcn::Rectangle &area = getChildrenArea(); + mUnequip->setPosition(area.width - mUnequip->getWidth() - 5, + area.height - mUnequip->getHeight() - 5); + mUnequip->setEnabled(false); + + add(playerBox); + add(mUnequip); + + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + mEquipBox[i].posX = boxPosition[i][0] + getPadding(); + mEquipBox[i].posY = boxPosition[i][1] + getTitleBarHeight(); + } +} + +EquipmentWindow::~EquipmentWindow() +{ + delete mItemPopup; + mItemPopup = 0; +} + +void EquipmentWindow::draw(gcn::Graphics *graphics) +{ + // Draw window graphics + Window::draw(graphics); + + Graphics *g = static_cast<Graphics*>(graphics); + + Window::drawChildren(graphics); + + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + if (i == mSelected) + { + const gcn::Color color = Theme::getThemeColor(Theme::HIGHLIGHT); + + // Set color to the highlight color + g->setColor(gcn::Color(color.r, color.g, color.b, getGuiAlpha())); + g->fillRectangle(gcn::Rectangle(mEquipBox[i].posX, + mEquipBox[i].posY, BOX_WIDTH, BOX_HEIGHT)); + } + + // Set color black + g->setColor(gcn::Color(0, 0, 0)); + // Draw box border + g->drawRectangle(gcn::Rectangle(mEquipBox[i].posX, mEquipBox[i].posY, + BOX_WIDTH, BOX_HEIGHT)); + + Item *item = mEquipment->getEquipment(i); + if (item) + { + // Draw Item. + Image *image = item->getImage(); + if (image) + { + image->setAlpha(1.0f); // Ensure the image is drawn + // with maximum opacity + g->drawImage(image, + mEquipBox[i].posX + 2, + mEquipBox[i].posY + 2); + if (i == EQUIP_PROJECTILE_SLOT) + { + g->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->drawText(toString(item->getQuantity()), + mEquipBox[i].posX + (BOX_WIDTH / 2), + mEquipBox[i].posY - getFont()->getHeight(), + gcn::Graphics::CENTER); + } + } + } + } +} + +void EquipmentWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "unequip" && mSelected > -1) + { + Item *item = mEquipment->getEquipment(mSelected); + Net::getInventoryHandler()->unequipItem(item); + setSelected(-1); + } +} + +Item *EquipmentWindow::getItem(int x, int y) const +{ + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY, + BOX_WIDTH, BOX_HEIGHT); + + if (tRect.isPointInRect(x, y)) + return mEquipment->getEquipment(i); + } + return NULL; +} + +void EquipmentWindow::mousePressed(gcn::MouseEvent& mouseEvent) +{ + Window::mousePressed(mouseEvent); + + const int x = mouseEvent.getX(); + const int y = mouseEvent.getY(); + + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + // Checks if any of the presses were in the equip boxes. + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + Item *item = mEquipment->getEquipment(i); + gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY, + BOX_WIDTH, BOX_HEIGHT); + + if (tRect.isPointInRect(x, y) && item) + setSelected(i); + } + } + else if (mouseEvent.getButton() == gcn::MouseEvent::RIGHT) + { + if (Item *item = getItem(x, y)) + { + /* Convert relative to the window coordinates to absolute screen + * coordinates. + */ + const int mx = x + getX(); + const int my = y + getY(); + if (viewport) + viewport->showPopup(this, mx, my, item, true); + } + } +} + +// Show ItemTooltip +void EquipmentWindow::mouseMoved(gcn::MouseEvent &event) +{ + if (!mItemPopup) + return; + + const int x = event.getX(); + const int y = event.getY(); + + Item *item = getItem(x, y); + + if (item) + { + int mouseX, mouseY; + SDL_GetMouseState(&mouseX, &mouseY); + + mItemPopup->setItem(item); + mItemPopup->position(x + getX(), y + getY()); + } + else + { + mItemPopup->setVisible(false); + } +} + +// Hide ItemTooltip +void EquipmentWindow::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mItemPopup) + mItemPopup->setVisible(false); +} + +void EquipmentWindow::setSelected(int index) +{ + mSelected = index; + if (mUnequip) + mUnequip->setEnabled(mSelected != -1); +} diff --git a/src/gui/equipmentwindow.h b/src/gui/equipmentwindow.h new file mode 100644 index 000000000..d80535ed6 --- /dev/null +++ b/src/gui/equipmentwindow.h @@ -0,0 +1,98 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef EQUIPMENTWINDOW_H +#define EQUIPMENTWINDOW_H + +#include "equipment.h" +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Inventory; +class Item; +class ItemPopup; + +/** + * Equipment dialog. + * + * \ingroup Interface + */ +class EquipmentWindow : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + EquipmentWindow(Equipment *equipment); + + /** + * Destructor. + */ + ~EquipmentWindow(); + + /** + * Draws the equipment window. + */ + void draw(gcn::Graphics *graphics); + + void action(const gcn::ActionEvent &event); + + void mousePressed(gcn::MouseEvent& mouseEvent); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + Item *getItem(int x, int y) const; + + void setSelected(int index); + + Equipment *mEquipment; + + /** + * Equipment box. + */ + struct EquipBox + { + int posX; + int posY; + }; + + EquipBox mEquipBox[Equipment::EQUIP_VECTOREND]; /**<Equipment Boxes. */ + + ItemPopup *mItemPopup; + gcn::Button *mUnequip; + + int mSelected; /**< Index of selected item. */ +}; + +extern EquipmentWindow *equipmentWindow; + +#endif diff --git a/src/gui/focushandler.cpp b/src/gui/focushandler.cpp new file mode 100644 index 000000000..9a2a244cc --- /dev/null +++ b/src/gui/focushandler.cpp @@ -0,0 +1,99 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "focushandler.h" + +#include "gui/widgets/window.h" + +void FocusHandler::requestModalFocus(gcn::Widget *widget) +{ + /* If there is another widget with modal focus, remove its modal focus + * and put it on the modal widget stack. + */ + if (mModalFocusedWidget && mModalFocusedWidget != widget) + { + mModalStack.push_front(mModalFocusedWidget); + mModalFocusedWidget = NULL; + } + + gcn::FocusHandler::requestModalFocus(widget); +} + +void FocusHandler::releaseModalFocus(gcn::Widget *widget) +{ + mModalStack.remove(widget); + + if (mModalFocusedWidget == widget) + { + gcn::FocusHandler::releaseModalFocus(widget); + + /* Check if there were any previously modal widgets that'd still like + * to regain their modal focus. + */ + if (!mModalStack.empty()) + { + gcn::FocusHandler::requestModalFocus(mModalStack.front()); + mModalStack.pop_front(); + } + } +} + +void FocusHandler::remove(gcn::Widget *widget) +{ + releaseModalFocus(widget); + + gcn::FocusHandler::remove(widget); +} + +void FocusHandler::tabNext() +{ + gcn::FocusHandler::tabNext(); + + checkForWindow(); +} + +void FocusHandler::tabPrevious() +{ + gcn::FocusHandler::tabPrevious(); + + checkForWindow(); +} + +void FocusHandler::checkForWindow() +{ + if (mFocusedWidget) + { + gcn::Widget *widget = mFocusedWidget->getParent(); + + while (widget) + { + Window *window = dynamic_cast<Window*>(widget); + + if (window) + { + window->requestMoveToTop(); + break; + } + + widget = widget->getParent(); + } + } +} diff --git a/src/gui/focushandler.h b/src/gui/focushandler.h new file mode 100644 index 000000000..f933323ae --- /dev/null +++ b/src/gui/focushandler.h @@ -0,0 +1,77 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef FOCUSHANDLER_H +#define FOCUSHANDLER_H + +#include <guichan/focushandler.hpp> + +#include <list> + +/** + * The focus handler. This focus handler does exactly the same as the Guichan + * focus handler, but keeps a stack of modal widgets to be able to handle + * multiple modal focus requests. + */ +class FocusHandler : public gcn::FocusHandler +{ + public: + /** + * Sets modal focus to a widget. When there is already a modal widget + * then that widget loses modal focus and will regain it after this + * widget releases his modal focus. + */ + void requestModalFocus(gcn::Widget *widget); + + /** + * Releases modal focus of a widget. When this widget had modal focus + * and there are other widgets that had also requested modal focus, + * then modal focus will be transfered to the last of those. + */ + void releaseModalFocus(gcn::Widget *widget); + + /** + * Removes a widget from the focus handler. Also makes sure no dangling + * pointers remain in modal focus stack. + */ + void remove(gcn::Widget *widget); + + /** + * Overloaded to allow windows to move to the top when one of their + * widgets is tabbed to when tabbing through focusable elements. + */ + void tabNext(); + void tabPrevious(); + + private: + /** + * Checks to see if the widget tabbed to is in a window, and if it is, + * it requests the window be moved to the top. + */ + void checkForWindow(); + + /** + * Stack of widgets that have requested modal forcus. + */ + std::list<gcn::Widget*> mModalStack; +}; + +#endif diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp new file mode 100644 index 000000000..57a94b3d1 --- /dev/null +++ b/src/gui/gui.cpp @@ -0,0 +1,310 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/gui.h" + +#include "gui/focushandler.h" +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" +#include "gui/truetypefont.h" + +#include "gui/widgets/window.h" +#include "gui/widgets/windowcontainer.h" + +#include "configlistener.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/imageloader.h" +#include "resources/resourcemanager.h" + +#include <guichan/exception.hpp> +#include <guichan/image.hpp> + +// Guichan stuff +Gui *gui = 0; +SDLInput *guiInput = 0; + +// Bolded font +gcn::Font *boldFont = 0; + +class GuiConfigListener : public ConfigListener +{ + public: + GuiConfigListener(Gui *g): + mGui(g) + {} + + void optionChanged(const std::string &name) + { + if (name == "customcursor" && mGui) + { + bool bCustomCursor = config.getBoolValue("customcursor"); + mGui->setUseCustomCursor(bCustomCursor); + } + } + private: + Gui *mGui; +}; + +Gui::Gui(Graphics *graphics): + mCustomCursor(false), + mMouseCursors(NULL), + mMouseCursorAlpha(1.0f), + mMouseInactivityTimer(0), + mCursorType(CURSOR_POINTER) +{ + logger->log1("Initializing GUI..."); + // Set graphics + setGraphics(graphics); + + // Set image loader + static ImageLoader imageLoader; + gcn::Image::setImageLoader(&imageLoader); + + // Set input + guiInput = new SDLInput; + setInput(guiInput); + + // Set focus handler + delete mFocusHandler; + mFocusHandler = new FocusHandler; + + // Initialize top GUI widget + WindowContainer *guiTop = new WindowContainer; + guiTop->setFocusable(true); + guiTop->setDimension(gcn::Rectangle(0, 0, + graphics->getWidth(), graphics->getHeight())); + guiTop->setOpaque(false); + Window::setWindowContainer(guiTop); + setTop(guiTop); + + // Set global font + const int fontSize = config.getIntValue("fontSize"); + std::string fontFile = config.getValue("font", ""); +// may be here need get paths from paths.getValue? +// std::string path = resman->getPath(fontFile); + if (fontFile.empty()) + fontFile = branding.getStringValue("font"); + + try + { + mGuiFont = new TrueTypeFont(fontFile, fontSize); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + // Set particle font + fontFile = config.getValue("particleFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("particleFont"); + + try + { + mInfoParticleFont = new TrueTypeFont( + fontFile, fontSize, TTF_STYLE_BOLD); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + // Set bold font + fontFile = config.getValue("boldFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("boldFont"); + + try + { + boldFont = new TrueTypeFont(fontFile, fontSize); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + // Set help font + fontFile = config.getValue("helpFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("helpFont"); + + try + { + mHelpFont = new TrueTypeFont(fontFile, fontSize); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + gcn::Widget::setGlobalFont(mGuiFont); + + // Initialize mouse cursor and listen for changes to the option + setUseCustomCursor(config.getBoolValue("customcursor")); + mConfigListener = new GuiConfigListener(this); + config.addListener("customcursor", mConfigListener); +} + +Gui::~Gui() +{ + config.removeListener("customcursor", mConfigListener); + delete mConfigListener; + mConfigListener = 0; + + if (mMouseCursors) + { + mMouseCursors->decRef(); + mMouseCursors = 0; + } + + delete mGuiFont; + mGuiFont = 0; + delete boldFont; + boldFont = 0; + delete mHelpFont; + mHelpFont = 0; + delete mInfoParticleFont; + mInfoParticleFont = 0; + delete getTop(); + + delete guiInput; + guiInput = 0; + + Theme::deleteInstance(); +} + +void Gui::logic() +{ + ResourceManager *resman = ResourceManager::getInstance(); + resman->clearScheduled(); + + // Fade out mouse cursor after extended inactivity + if (mMouseInactivityTimer < 100 * 15) + { + ++mMouseInactivityTimer; + mMouseCursorAlpha = std::min(1.0f, mMouseCursorAlpha + 0.05f); + } + else + { + mMouseCursorAlpha = std::max(0.0f, mMouseCursorAlpha - 0.005f); + } + + Palette::advanceGradients(); + + gcn::Gui::logic(); +} + +void Gui::draw() +{ + mGraphics->pushClipArea(getTop()->getDimension()); + getTop()->draw(mGraphics); + + int mouseX, mouseY; + Uint8 button = SDL_GetMouseState(&mouseX, &mouseY); + + if ((SDL_GetAppState() & SDL_APPMOUSEFOCUS || button & SDL_BUTTON(1)) + && mMouseCursors && mCustomCursor && mMouseCursorAlpha > 0.0f) + { + Image *mouseCursor = mMouseCursors->get(mCursorType); + if (mouseCursor) + { + mouseCursor->setAlpha(mMouseCursorAlpha); + + static_cast<Graphics*>(mGraphics)->drawImage( + mouseCursor, + mouseX - 15, + mouseY - 17); + } + } + + mGraphics->popClipArea(); +} + +void Gui::setUseCustomCursor(bool customCursor) +{ + if (customCursor != mCustomCursor) + { + mCustomCursor = customCursor; + + if (mCustomCursor) + { + // Hide the SDL mouse cursor + SDL_ShowCursor(SDL_DISABLE); + + // Load the mouse cursor + mMouseCursors = Theme::getImageSetFromTheme("mouse.png", 40, 40); + + if (!mMouseCursors) + logger->log("Error: Unable to load mouse cursors."); + } + else + { + // Show the SDL mouse cursor + SDL_ShowCursor(SDL_ENABLE); + + // Unload the mouse cursor + if (mMouseCursors) + { + mMouseCursors->decRef(); + mMouseCursors = NULL; + } + } + } +} + +void Gui::handleMouseMoved(const gcn::MouseInput &mouseInput) +{ + gcn::Gui::handleMouseMoved(mouseInput); + mMouseInactivityTimer = 0; +} + +void Gui::updateFonts() +{ + const int fontSize = config.getIntValue("fontSize"); + std::string fontFile = config.getValue("font", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("font"); + + static_cast<TrueTypeFont*>(mGuiFont)->loadFont(fontFile, fontSize); + + fontFile = config.getValue("particleFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("particleFont"); + + static_cast<TrueTypeFont*>(mInfoParticleFont)->loadFont( + fontFile, fontSize, TTF_STYLE_BOLD); + + fontFile = config.getValue("boldFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("boldFont"); + + static_cast<TrueTypeFont*>(boldFont)->loadFont(fontFile, fontSize); +} diff --git a/src/gui/gui.h b/src/gui/gui.h new file mode 100644 index 000000000..b4ddfc299 --- /dev/null +++ b/src/gui/gui.h @@ -0,0 +1,148 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_H +#define GUI_H + +#include "guichanfwd.h" + +#include <guichan/gui.hpp> + +class Graphics; +class GuiConfigListener; +class ImageSet; +class SDLInput; + +/** + * \defgroup GUI Core GUI related classes (widgets) + */ + +/** + * \defgroup Interface User interface related classes (windows, dialogs) + */ + +/** + * Main GUI class. + * + * \ingroup GUI + */ +class Gui : public gcn::Gui +{ + public: + /** + * Constructor. + */ + Gui(Graphics *screen); + + /** + * Destructor. + */ + ~Gui(); + + /** + * Performs logic of the GUI. Overridden to track mouse pointer + * activity. + */ + void logic(); + + /** + * Draws the whole Gui by calling draw functions down in the + * Gui hierarchy. It also draws the mouse pointer. + */ + void draw(); + + gcn::FocusHandler *getFocusHandler() const + { return mFocusHandler; } + + /** + * Return game font. + */ + gcn::Font *getFont() const + { return mGuiFont; } + + /** + * Return help font. + */ + gcn::Font *getHelpFont() const + { return mHelpFont; } + + /** + * Return the Font used for "Info Particles", i.e. ones showing, what + * you picked up, etc. + */ + gcn::Font *getInfoParticleFont() const + { return mInfoParticleFont; } + + /** + * Sets whether a custom cursor should be rendered. + */ + void setUseCustomCursor(bool customCursor); + + /** + * Sets which cursor should be used. + */ + void setCursorType(int index) + { mCursorType = index; } + + void updateFonts(); + + /** + * Cursors are in graphic order from left to right. + * CURSOR_POINTER should be left untouched. + * CURSOR_TOTAL should always be last. + */ + enum + { + CURSOR_POINTER = 0, + CURSOR_RESIZE_ACROSS, + CURSOR_RESIZE_DOWN, + CURSOR_RESIZE_DOWN_LEFT, + CURSOR_RESIZE_DOWN_RIGHT, + CURSOR_FIGHT, + CURSOR_PICKUP, + CURSOR_TALK, + CURSOR_TOTAL + }; + + protected: + void handleMouseMoved(const gcn::MouseInput &mouseInput); + + private: + GuiConfigListener *mConfigListener; + gcn::Font *mGuiFont; /**< The global GUI font */ + gcn::Font *mInfoParticleFont; /**< Font for Info Particles*/ + gcn::Font *mHelpFont; /**< Font for Help Window*/ + bool mCustomCursor; /**< Show custom cursor */ + ImageSet *mMouseCursors; /**< Mouse cursor images */ + float mMouseCursorAlpha; + int mMouseInactivityTimer; + int mCursorType; +}; + +extern Gui *gui; /**< The GUI system */ +extern SDLInput *guiInput; /**< GUI input */ + +/** + * Bolded text font + */ +extern gcn::Font *boldFont; + +#endif // GUI_H diff --git a/src/gui/help.cpp b/src/gui/help.cpp new file mode 100644 index 000000000..aa114b99f --- /dev/null +++ b/src/gui/help.cpp @@ -0,0 +1,106 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/help.h" + +#include "gui/gui.h" +#include "gui/setup.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/browserbox.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" + +#include "resources/resourcemanager.h" +#include "configuration.h" + +#include "utils/gettext.h" + +HelpWindow::HelpWindow(): + Window(_("Help")) +{ + setMinWidth(300); + setMinHeight(250); + setContentSize(455, 350); + setWindowName("Help"); + setResizable(true); + setupWindow->registerWindowForReset(this); + + setDefaultSize(500, 400, ImageRect::CENTER); + + mBrowserBox = new BrowserBox; + mBrowserBox->setOpaque(false); + mScrollArea = new ScrollArea(mBrowserBox); + Button *okButton = new Button(_("Close"), "close", this); + + mScrollArea->setDimension(gcn::Rectangle(5, 5, 445, + 335 - okButton->getHeight())); + okButton->setPosition(450 - okButton->getWidth(), + 345 - okButton->getHeight()); + + mBrowserBox->setLinkHandler(this); + mBrowserBox->setFont(gui->getHelpFont()); + + place(0, 0, mScrollArea, 5, 3).setPadding(3); + place(4, 3, okButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + loadWindowState(); +} + +void HelpWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "close") + setVisible(false); +} + +void HelpWindow::handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_) +{ + std::string helpFile = link; + loadHelp(helpFile); +} + +void HelpWindow::loadHelp(const std::string &helpFile) +{ + mBrowserBox->clearRows(); + + loadFile("header"); + loadFile(helpFile); + + mScrollArea->setVerticalScrollAmount(0); + setVisible(true); +} + +void HelpWindow::loadFile(const std::string &file) +{ + ResourceManager *resman = ResourceManager::getInstance(); + std::string helpPath = branding.getStringValue("helpPath"); + if (helpPath.empty()) + helpPath = paths.getStringValue("help"); + std::vector<std::string> lines = + resman->loadTextFile(helpPath + file + ".txt"); + + for (unsigned int i = 0; i < lines.size(); ++i) + mBrowserBox->addRow(lines[i]); +} diff --git a/src/gui/help.h b/src/gui/help.h new file mode 100644 index 000000000..fe5cb9fe7 --- /dev/null +++ b/src/gui/help.h @@ -0,0 +1,76 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef HELP_H +#define HELP_H + +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class BrowserBox; +class LinkHandler; + +/** + * The help dialog. + */ +class HelpWindow : public Window, public LinkHandler, + public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + HelpWindow(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Handles link action. + */ + void handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_); + + /** + * Loads help in the dialog. + */ + void loadHelp(const std::string &helpFile); + + private: + void loadFile(const std::string &file); + + BrowserBox *mBrowserBox; + gcn::ScrollArea *mScrollArea; +}; + +extern HelpWindow *helpWindow; + +#endif diff --git a/src/gui/inventorywindow.cpp b/src/gui/inventorywindow.cpp new file mode 100644 index 000000000..abb702005 --- /dev/null +++ b/src/gui/inventorywindow.cpp @@ -0,0 +1,503 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/inventorywindow.h" + +#include "inventory.h" +#include "item.h" +#include "units.h" +#include "keyboardconfig.h" +#include "playerinfo.h" + +#include "gui/itemamount.h" +#include "gui/setup.h" +#include "gui/sdlinput.h" +#include "gui/shopwindow.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/itemcontainer.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "resources/iteminfo.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> +#include <guichan/mouseinput.hpp> + +#include <string> + +InventoryWindow::WindowList InventoryWindow::instances; + +InventoryWindow::InventoryWindow(Inventory *inventory): + Window(inventory ? (inventory->isMainInventory() + ? _("Inventory") : _("Storage")) : _("Inventory")), + mInventory(inventory), + mDropButton(0), + mSplit(false) +{ + listen(CHANNEL_ATTRIBUTES); + + setWindowName(isMainInventory() ? "Inventory" : "Storage"); + + if (setupWindow) + setupWindow->registerWindowForReset(this); + + setResizable(true); + setCloseButton(true); + setSaveVisible(true); + + setDefaultSize(387, 307, ImageRect::CENTER); + setMinWidth(316); + setMinHeight(179); + addKeyListener(this); + + mItems = new ItemContainer(mInventory); + mItems->addSelectionListener(this); + + gcn::ScrollArea *invenScroll = new ScrollArea(mItems); + invenScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mSlotsLabel = new Label(_("Slots:")); + mSlotsBar = new ProgressBar(0.0f, 100, 20, Theme::PROG_INVY_SLOTS); + + if (isMainInventory()) + { + std::string equip = _("Equip"); + std::string use = _("Use"); + std::string unequip = _("Unequip"); + + std::string longestUseString = getFont()->getWidth(equip) > + getFont()->getWidth(use) ? equip : use; + + if (getFont()->getWidth(longestUseString) < + getFont()->getWidth(unequip)) + { + longestUseString = unequip; + } + + mUseButton = new Button(longestUseString, "use", this); + mUseButton2 = new Button(longestUseString, "equip", this); + mDropButton = new Button(_("Drop..."), "drop", this); + mSplitButton = new Button(_("Split"), "split", this); + mOutfitButton = new Button(_("Outfits"), "outfit", this); + mShopButton = new Button(_("Shop"), "shop", this); + + mWeightLabel = new Label(_("Weight:")); + mWeightBar = new ProgressBar(0.0f, 100, 20, Theme::PROG_WEIGHT); + + place(0, 0, mWeightLabel).setPadding(3); + place(1, 0, mWeightBar, 3); + place(4, 0, mSlotsLabel).setPadding(3); + place(5, 0, mSlotsBar, 2); + place(0, 1, invenScroll, 7).setPadding(3); + place(0, 2, mUseButton); + place(1, 2, mUseButton2); + place(2, 2, mDropButton); + place(4, 2, mSplitButton); + place(5, 2, mShopButton); + place(6, 2, mOutfitButton); + + updateWeight(); + } + else + { + mStoreButton = new Button(_("Store"), "store", this); + mRetrieveButton = new Button(_("Retrieve"), "retrieve", this); + mCloseButton = new Button(_("Close"), "close", this); + + place(0, 0, mSlotsLabel).setPadding(3); + place(1, 0, mSlotsBar, 3); + place(0, 1, invenScroll, 4, 4); + place(0, 5, mStoreButton); + place(1, 5, mRetrieveButton); + place(3, 5, mCloseButton); + } + + Layout &layout = getLayout(); + layout.setRowHeight(1, Layout::AUTO_SET); + + mInventory->addInventoyListener(this); + + instances.push_back(this); + + if (inventory->isMainInventory()) + { + updateDropButton(); + } + else + { + if (!instances.empty()) + instances.front()->updateDropButton(); + } + + loadWindowState(); + slotsChanged(mInventory); + + if (!isMainInventory()) + setVisible(true); +} + +InventoryWindow::~InventoryWindow() +{ + instances.remove(this); + mInventory->removeInventoyListener(this); + if (!instances.empty()) + instances.front()->updateDropButton(); +} + +void InventoryWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "outfit") + { + extern Window *outfitWindow; + if (outfitWindow) + { + outfitWindow->setVisible(!outfitWindow->isVisible()); + if (outfitWindow->isVisible()) + outfitWindow->requestMoveToTop(); + } + } + + if (event.getId() == "shop") + { + if (shopWindow) + { + shopWindow->setVisible(!shopWindow->isVisible()); + if (shopWindow->isVisible()) + shopWindow->requestMoveToTop(); + } + } + else if (event.getId() == "close") + { + close(); + } + else if (event.getId() == "store") + { + if (!inventoryWindow || !inventoryWindow->isVisible()) + return; + + Item *item = inventoryWindow->getSelectedItem(); + + if (!item) + return; + + ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd, this, item); + } + + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + if (event.getId() == "use") + { + if (item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + else + Net::getInventoryHandler()->equipItem(item); + } + else + Net::getInventoryHandler()->useItem(item); + } + if (event.getId() == "equip") + { + if (!item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + else + Net::getInventoryHandler()->equipItem(item); + } + else + Net::getInventoryHandler()->useItem(item); + } + else if (event.getId() == "drop") + { + if (isStorageActive()) + { + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + item->getInvIndex(), item->getQuantity(), + Inventory::STORAGE); + } + else + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop, + this, item); + } + } + else if (event.getId() == "split") + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item, + (item->getQuantity() - 1)); + } + else if (event.getId() == "retrieve") + { + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, this, + item); + } +} + +Item *InventoryWindow::getSelectedItem() const +{ + return mItems->getSelectedItem(); +} + +void InventoryWindow::mouseClicked(gcn::MouseEvent &event) +{ + Window::mouseClicked(event); + + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + /* Convert relative to the window coordinates to absolute screen + * coordinates. + */ + const int mx = event.getX() + getX(); + const int my = event.getY() + getY(); + + if (viewport) + viewport->showPopup(this, mx, my, item, isMainInventory()); + } + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (isStorageActive() && keyboard.isKeyActive(keyboard.KEY_EMOTE)) + { + Item *item = mItems->getSelectedItem(); + + if (!item || !mInventory) + return; + + if (mInventory->isMainInventory()) + { + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + item->getInvIndex(), item->getQuantity(), + Inventory::STORAGE); + } + else + { + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + item->getInvIndex(), item->getQuantity(), + Inventory::INVENTORY); + } + } + } +} + +void InventoryWindow::keyPressed(gcn::KeyEvent &event) +{ + switch (event.getKey().getValue()) + { + case Key::LEFT_SHIFT: + case Key::RIGHT_SHIFT: + mSplit = true; + break; + default: + break; + } +} + +void InventoryWindow::keyReleased(gcn::KeyEvent &event) +{ + switch (event.getKey().getValue()) + { + case Key::LEFT_SHIFT: + case Key::RIGHT_SHIFT: + mSplit = false; + break; + default: + break; + } +} + +void InventoryWindow::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + if (!mInventory || !mInventory->isMainInventory()) + return; + + Item *item = mItems->getSelectedItem(); + + if (mSplit && item && Net::getInventoryHandler()-> + canSplit(mItems->getSelectedItem())) + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item, + (item->getQuantity() - 1)); + } + + if (!item || item->getQuantity() == 0) + { + if (mUseButton) + mUseButton->setEnabled(true); + if (mUseButton2) + mUseButton2->setEnabled(true); + if (mDropButton) + mDropButton->setEnabled(true); + return; + } + + if (mUseButton) + mUseButton->setEnabled(true); + if (mUseButton2) + mUseButton2->setEnabled(true); + if (mDropButton) + mDropButton->setEnabled(true); + + if (mUseButton && item && item->isEquipment()) + { + if (item->isEquipped()) + mUseButton->setCaption(_("Unequip")); + else + mUseButton->setCaption(_("Equip")); + mUseButton2->setCaption(_("Use")); + } + else if (mUseButton2) + { + mUseButton->setCaption(_("Use")); + if (item->isEquipped()) + mUseButton2->setCaption(_("Unequip")); + else + mUseButton2->setCaption(_("Equip")); + } + + updateDropButton(); + + if (mSplitButton) + { + if (Net::getInventoryHandler()->canSplit(item)) + mSplitButton->setEnabled(true); + else + mSplitButton->setEnabled(false); + } +} + + +void InventoryWindow::setSplitAllowed(bool allowed) +{ + mSplitButton->setVisible(allowed); +} + +void InventoryWindow::close() +{ + if (this == inventoryWindow) + { + setVisible(false); + } + else + { + Net::getInventoryHandler()->closeStorage(Inventory::STORAGE); + scheduleDelete(); + } +} + +void InventoryWindow::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + int id = event.getInt("id"); + if (id == TOTAL_WEIGHT || id == MAX_WEIGHT) + updateWeight(); + } +} + +void InventoryWindow::updateWeight() +{ + if (!isMainInventory()) + return; + + int total = PlayerInfo::getAttribute(TOTAL_WEIGHT); + int max = PlayerInfo::getAttribute(MAX_WEIGHT); + + if (max <= 0) + return; + + // Adjust progress bar + mWeightBar->setProgress(static_cast<float>(total) + / static_cast<float>(max)); + mWeightBar->setText(strprintf("%s/%s", Units::formatWeight(total).c_str(), + Units::formatWeight(max).c_str())); +} + +void InventoryWindow::slotsChanged(Inventory* inventory) +{ + if (inventory == mInventory) + { + const int usedSlots = mInventory->getNumberOfSlotsUsed(); + const int maxSlots = mInventory->getSize(); + + if (maxSlots) + { + mSlotsBar->setProgress(static_cast<float>(usedSlots) + / static_cast<float>(maxSlots)); + } + + mSlotsBar->setText(strprintf("%d/%d", usedSlots, maxSlots)); + } +} + +void InventoryWindow::updateDropButton() +{ + if (!mDropButton) + return; + + if (isStorageActive()) + { + mDropButton->setCaption(_("Store")); + } + else + { + Item *item = 0; + if (mItems) + item = mItems->getSelectedItem(); + + if (item && item->getQuantity() > 1) + mDropButton->setCaption(_("Drop...")); + else + mDropButton->setCaption(_("Drop")); + } +} diff --git a/src/gui/inventorywindow.h b/src/gui/inventorywindow.h new file mode 100644 index 000000000..87f57eb9d --- /dev/null +++ b/src/gui/inventorywindow.h @@ -0,0 +1,155 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef INVENTORYWINDOW_H +#define INVENTORYWINDOW_H + +#include "inventory.h" +#include "listener.h" + +#include "gui/widgets/window.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> +#include <guichan/selectionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Item; +class ItemContainer; +class ProgressBar; +class TextBox; + +/** + * Inventory dialog. + * + * \ingroup Interface + */ +class InventoryWindow : public Window, + public gcn::ActionListener, + public gcn::KeyListener, + public gcn::SelectionListener, + public InventoryListener, + public Mana::Listener +{ + public: + /** + * Constructor. + */ + InventoryWindow(Inventory *inventory); + + /** + * Destructor. + */ + ~InventoryWindow(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Returns the selected item. + */ + Item* getSelectedItem() const; + + /** + * Handles the mouse clicks. + */ + void mouseClicked(gcn::MouseEvent &event); + + /** + * Handles the key presses. + */ + void keyPressed(gcn::KeyEvent &event); + + /** + * Handles the key releases. + */ + void keyReleased(gcn::KeyEvent &event); + + /** + * Updates labels to currently selected item. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Sets whether the split button should be shown. + */ + void setSplitAllowed(bool allowed); + + /** + * Closes the Storage Window, as well as telling the server that the + * window has been closed. + */ + void close(); + + void slotsChanged(Inventory* inventory); + + bool isMainInventory() + { return mInventory->isMainInventory(); } + + /** + * Returns true if any instances exist. + */ + static bool isStorageActive() + { return instances.size() > 1; } + + void updateDropButton(); + + void event(Channels channel, const Mana::Event &event); + + private: + /** + * Updates the weight bar. + */ + void updateWeight(); + + + typedef std::list<InventoryWindow*> WindowList; + static WindowList instances; + + Inventory *mInventory; + ItemContainer *mItems; + + std::string mWeight, mSlots; + + gcn::Button *mUseButton, *mUseButton2, *mDropButton, + *mSplitButton, *mOutfitButton, *mShopButton, + *mStoreButton, *mRetrieveButton, *mCloseButton; + + gcn::Label *mWeightLabel, *mSlotsLabel; + + ProgressBar *mWeightBar, *mSlotsBar; + + bool mSplit; +}; + +extern InventoryWindow *inventoryWindow; + +#endif diff --git a/src/gui/itemamount.cpp b/src/gui/itemamount.cpp new file mode 100644 index 000000000..25356823f --- /dev/null +++ b/src/gui/itemamount.cpp @@ -0,0 +1,432 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/itemamount.h" + +#include "item.h" +#include "keyboardconfig.h" + +#include "gui/trade.h" +#include "net/inventoryhandler.h" +#include "gui/itempopup.h" +#include "net/net.h" +#include "gui/shopwindow.h" +#include "gui/viewport.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/icon.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/label.h" +#include "gui/widgets/slider.h" + +#include "resources/itemdb.h" + +#include "utils/gettext.h" + +#include <math.h> + +class ItemsModal : public gcn::ListModel +{ +public: + ItemsModal() + { + std::map<int, ItemInfo*> info = ItemDB::getItemInfos(); + std::list<std::string> tempStrings; + + for (std::map<int, ItemInfo*>::const_iterator + i = info.begin(), i_end = info.end(); + i != i_end; ++i) + { + if (i->first < 0) + continue; + + ItemInfo info = (*i->second); + std::string name = info.getName(); + if (name != "unnamed" && !info.getName().empty() + && info.getName() != "unnamed") + { + tempStrings.push_back(name); + } + } + tempStrings.sort(); + for (std::list<std::string>::const_iterator i = tempStrings.begin(), + i_end = tempStrings.end(); i != i_end; ++i) + { + mStrings.push_back(*i); + } + } + + virtual ~ItemsModal() + { } + + virtual int getNumberOfElements() + { + return static_cast<int>(mStrings.size()); + } + + virtual std::string getElementAt(int i) + { + if (i < 0 || i >= getNumberOfElements()) + return _("???"); + + return mStrings.at(i); + } +private: + std::vector<std::string> mStrings; +}; + +void ItemAmountWindow::finish(Item *item, int amount, int price, Usage usage) +{ + switch (usage) + { + case TradeAdd: + if (tradeWindow) + tradeWindow->tradeItem(item, amount); + break; + case ItemDrop: + Net::getInventoryHandler()->dropItem(item, amount); + break; + case ItemSplit: + Net::getInventoryHandler()->splitItem(item, amount); + break; + case StoreAdd: + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + item->getInvIndex(), amount, + Inventory::STORAGE); + break; + case StoreRemove: + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + item->getInvIndex(), amount, + Inventory::INVENTORY); + break; + case ShopBuyAdd: + if (shopWindow) + shopWindow->addBuyItem(item, amount, price); + break; + case ShopSellAdd: + if (shopWindow) + shopWindow->addSellItem(item, amount, price); + break; + default: + break; + } +} + +ItemAmountWindow::ItemAmountWindow(Usage usage, Window *parent, Item *item, + int maxRange): + Window("", true, parent), + mItemPriceTextField(0), + mGPLabel(0), + mItem(item), + mMax(maxRange), + mUsage(usage), + mItemPriceSlide(0), + mItemsModal(0), + mPrice(0) +{ + if (!mItem) + { + setVisible(false); + return; + } + if (usage == ShopBuyAdd) + mMax = 10000; + else if (!mMax) + mMax = mItem->getQuantity(); + + // Save keyboard state + mEnabledKeyboard = keyboard.isEnabled(); + keyboard.setEnabled(false); + + // Integer field + mItemAmountTextField = new IntTextField(1); + mItemAmountTextField->setRange(1, mMax); + mItemAmountTextField->setWidth(35); + mItemAmountTextField->addKeyListener(this); + + // Slider + mItemAmountSlide = new Slider(1.0, mMax); + mItemAmountSlide->setHeight(10); + mItemAmountSlide->setActionEventId("slide"); + mItemAmountSlide->addActionListener(this); + + if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd) + { + // Integer field + mItemPriceTextField = new IntTextField(1); + mItemPriceTextField->setRange(1, 10000000); + mItemPriceTextField->setWidth(35); + mItemPriceTextField->addKeyListener(this); + + // Slider + mItemPriceSlide = new Slider(1.0, 10000000); + mItemPriceSlide->setHeight(10); + mItemPriceSlide->setActionEventId("slidePrice"); + mItemPriceSlide->addActionListener(this); + + mGPLabel = new Label(" GP"); + } + + if (mUsage == ShopBuyAdd) + { + mItemsModal = new ItemsModal; + mItemDropDown = new DropDown(mItemsModal); + mItemDropDown->setActionEventId("itemType"); + mItemDropDown->addActionListener(this); + } + + //Item icon + Image *image = item->getImage(); + mItemIcon = new Icon(image); + + // Buttons + Button *minusAmountButton = new Button(_("-"), "dec", this); + Button *plusAmountButton = new Button(_("+"), "inc", this); + Button *okButton = new Button(_("OK"), "ok", this); + Button *cancelButton = new Button(_("Cancel"), "cancel", this); + Button *addAllButton = new Button(_("All"), "all", this); + + minusAmountButton->adjustSize(); + minusAmountButton->setWidth(plusAmountButton->getWidth()); + + // Set positions + ContainerPlacer place; + place = getPlacer(0, 0); + int n = 0; + if (mUsage == ShopBuyAdd) + { + place(0, n, mItemDropDown, 8); + n++; + } + place(1, n, minusAmountButton); + place(2, n, mItemAmountTextField, 3); + place(5, n, plusAmountButton); + place(6, n, addAllButton); + + place(0, n, mItemIcon, 1, 3); + place(1, n + 1, mItemAmountSlide, 7); + + if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd) + { + Button *minusPriceButton = new Button(_("-"), "decPrice", this); + Button *plusPriceButton = new Button(_("+"), "incPrice", this); + minusPriceButton->adjustSize(); + minusPriceButton->setWidth(plusPriceButton->getWidth()); + + place(1, n + 2, minusPriceButton); + place(2, n + 2, mItemPriceTextField, 3); + place(5, n + 2, plusPriceButton); + place(6, n + 2, mGPLabel); + + place(1, n + 3, mItemPriceSlide, 7); + place(4, n + 5, cancelButton); + place(5, n + 5, okButton); + } + else + { + place(4, n + 2, cancelButton); + place(5, n + 2, okButton); + } + + reflowLayout(225, 0); + + resetAmount(); + + switch (usage) + { + case TradeAdd: + setCaption(_("Select amount of items to trade.")); + break; + case ItemDrop: + setCaption(_("Select amount of items to drop.")); + break; + case StoreAdd: + setCaption(_("Select amount of items to store.")); + break; + case StoreRemove: + setCaption(_("Select amount of items to retrieve.")); + break; + case ItemSplit: + setCaption(_("Select amount of items to split.")); + break; + case ShopBuyAdd: + setCaption(_("Add to buy shop.")); + break; + case ShopSellAdd: + setCaption(_("Add to sell shop.")); + break; + default: + setCaption(_("Unknown.")); + break; + } + + setLocationRelativeTo(getParentWindow()); + setVisible(true); + + mItemPopup = new ItemPopup; + mItemIcon->addMouseListener(this); +} + +ItemAmountWindow::~ItemAmountWindow() +{ + delete mItemPopup; + mItemPopup = 0; +} + +// Show ItemTooltip +void ItemAmountWindow::mouseMoved(gcn::MouseEvent &event) +{ + if (!viewport || !mItemPopup) + return; + + if (event.getSource() == mItemIcon) + { + mItemPopup->setItem(mItem); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } +} + +// Hide ItemTooltip +void ItemAmountWindow::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mItemPopup) + mItemPopup->setVisible(false); +} + +void ItemAmountWindow::resetAmount() +{ + mItemAmountTextField->setValue(1); +} + +void ItemAmountWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + close(); + return; + } + else if (event.getId() == "ok") + { + if (mItemPriceTextField) + { + finish(mItem, mItemAmountTextField->getValue(), + mItemPriceTextField->getValue(), mUsage); + } + else + { + finish(mItem, mItemAmountTextField->getValue(), + 0, mUsage); + } + close(); + return; + } + else if (event.getId() == "itemType") + { + if (!mItemDropDown || !mItemsModal) + return; + + std::string str = mItemsModal->getElementAt( + mItemDropDown->getSelected()); + int id = ItemDB::get(str).getId(); + + mItem = new Item(id, 10000); + + if (mUsage == ShopBuyAdd) + mMax = 10000; + else if (!mMax) + mMax = mItem->getQuantity(); + + Image *image = mItem->getImage(); + mItemIcon->setImage(image); + } + + int amount = mItemAmountTextField->getValue(); + + if (event.getId() == "inc" && amount < mMax) + amount++; + else if (event.getId() == "dec" && amount > 1) + amount--; + else if (event.getId() == "all") + amount = mMax; + else if (event.getId() == "slide") + amount = static_cast<int>(mItemAmountSlide->getValue()); + mItemAmountTextField->setValue(amount); + mItemAmountSlide->setValue(amount); + + if (mItemPriceTextField && mItemPriceSlide) + { + int price = 0; + + if (mPrice > 7) + mPrice = 7; + else if (mPrice < 0) + mPrice = 0; + + if (event.getId() == "incPrice") + { + mPrice++; + price = static_cast<int>(pow(10, mPrice)); + } + else if (event.getId() == "decPrice") + { + mPrice--; + price = static_cast<int>(pow(10, mPrice)); + } + else if (event.getId() == "slidePrice") + { + price = static_cast<int>(mItemPriceSlide->getValue()); + if (price) + mPrice = static_cast<int>(log(price)); + else + mPrice = 0; + } + mItemPriceTextField->setValue(price); + mItemPriceSlide->setValue(price); + } +} + +void ItemAmountWindow::close() +{ + keyboard.setEnabled(mEnabledKeyboard); + scheduleDelete(); +} + +void ItemAmountWindow::keyReleased(gcn::KeyEvent &keyEvent _UNUSED_) +{ + mItemAmountSlide->setValue(mItemAmountTextField->getValue()); +} + +void ItemAmountWindow::showWindow(Usage usage, Window *parent, Item *item, + int maxRange) +{ + if (!item) + return; + + if (!maxRange) + maxRange = item->getQuantity(); + + if (usage != ShopBuyAdd && usage != ShopSellAdd && maxRange <= 1) + finish(item, maxRange, 0, usage); + else + new ItemAmountWindow(usage, parent, item, maxRange); +} diff --git a/src/gui/itemamount.h b/src/gui/itemamount.h new file mode 100644 index 000000000..f28581294 --- /dev/null +++ b/src/gui/itemamount.h @@ -0,0 +1,124 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ITEM_AMOUNT_WINDOW_H +#define ITEM_AMOUNT_WINDOW_H + +#include "gui/widgets/window.h" + +#include <guichan/keylistener.hpp> +#include <guichan/actionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Icon; +class IntTextField; +class Item; +class ItemsModal; +class ItemPopup; +class Label; + +/** + * Window used for selecting the amount of items to drop, trade or split. + * + * \ingroup Interface + */ +class ItemAmountWindow : public Window, + public gcn::ActionListener, + public gcn::KeyListener +{ + public: + enum Usage + { + TradeAdd = 0, + ItemDrop, + StoreAdd, + StoreRemove, + ItemSplit, + ShopBuyAdd, + ShopSellAdd + }; + + /** + * Called when receiving actions from widget. + */ + void action(const gcn::ActionEvent &event); + + /** + * Sets default amount value. + */ + void resetAmount(); + + // MouseListener + void mouseMoved(gcn::MouseEvent &event); + void mouseExited(gcn::MouseEvent &event); + + /** + * Schedules the Item Amount window for deletion. + */ + void close(); + + void keyReleased(gcn::KeyEvent &keyEvent); + + /** + * Creates the dialog, or bypass it if there aren't enough items. + */ + static void showWindow(Usage usage, Window *parent, Item *item, + int maxRange = 0); + + ~ItemAmountWindow(); + + private: + static void finish(Item *item, int amount, int price, Usage usage); + + ItemAmountWindow(Usage usage, Window *parent, Item *item, + int maxRange = 0); + + IntTextField *mItemAmountTextField; /**< Item amount caption. */ + IntTextField *mItemPriceTextField; /**< Item price caption. */ + Label *mGPLabel; + Item *mItem; + Icon *mItemIcon; + + int mMax; + Usage mUsage; + ItemPopup *mItemPopup; + + /** + * Item Amount buttons. + */ + gcn::Slider *mItemAmountSlide; + + gcn::Slider *mItemPriceSlide; + + gcn::DropDown *mItemDropDown; + + ItemsModal *mItemsModal; + + bool mEnabledKeyboard; + int mPrice; +}; + +#endif // ITEM_AMOUNT_WINDOW_H diff --git a/src/gui/itempopup.cpp b/src/gui/itempopup.cpp new file mode 100644 index 000000000..15a0be6bc --- /dev/null +++ b/src/gui/itempopup.cpp @@ -0,0 +1,238 @@ +/* + * The Mana Client + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/itempopup.h" + +#include "graphics.h" +#include "item.h" +#include "units.h" + +#include "gui/gui.h" +#include "gui/theme.h" + +#include "gui/widgets/icon.h" +#include "gui/widgets/textbox.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include <guichan/font.hpp> + +#include <guichan/widgets/label.hpp> +#include <guichan/widgets/container.hpp> + +ItemPopup::ItemPopup(): + Popup("ItemPopup"), + mIcon(0) +{ + // Item Name + mItemName = new gcn::Label; + mItemName->setFont(boldFont); + mItemName->setPosition(getPadding(), getPadding()); + + const int fontHeight = getFont()->getHeight(); + + // Item Description + mItemDesc = new TextBox; + mItemDesc->setEditable(false); + mItemDesc->setPosition(getPadding(), fontHeight); + + // Item Effect + mItemEffect = new TextBox; + mItemEffect->setEditable(false); + mItemEffect->setPosition(getPadding(), 2 * fontHeight + 2 * getPadding()); + + // Item Weight + mItemWeight = new TextBox; + mItemWeight->setEditable(false); + mItemWeight->setPosition(getPadding(), 3 * fontHeight + 4 * getPadding()); + + mIcon = new Icon(0); + + add(mItemName); + add(mItemDesc); + add(mItemEffect); + add(mItemWeight); + add(mIcon); + + addMouseListener(this); +} + +ItemPopup::~ItemPopup() +{ + if (mIcon) + { + Image *image = mIcon->getImage(); + if (image) + image->decRef(); + } +} + +void ItemPopup::setItem(const Item *item, bool showImage) +{ + if (!item) + return; + + const ItemInfo &ii = item->getInfo(); + setItem(ii, showImage); + if (item->getRefine() > 0) + { + mItemName->setCaption(strprintf("%s (+%d), %d", ii.getName().c_str(), + item->getRefine(), ii.getId())); + mItemName->adjustSize(); + int minWidth = mItemName->getWidth() + 8; + if (getWidth() < minWidth) + setWidth(minWidth); + } +} + +void ItemPopup::setItem(const ItemInfo &item, bool showImage) +{ + if (!mIcon || item.getName() == mItemName->getCaption()) + return; + + int space = 0; + + Image *oldImage = mIcon->getImage(); + if (oldImage) + oldImage->decRef(); + + if (showImage) + { + ResourceManager *resman = ResourceManager::getInstance(); + Image *image = resman->getImage( + paths.getStringValue("itemIcons") + + item.getDisplay().image); + + mIcon->setImage(image); + if (image) + { + int x = getPadding(); + int y = getPadding(); + mIcon->setPosition(x, y); + space = mIcon->getWidth(); + } + } + else + { + mIcon->setImage(0); + } + + mItemType = item.getType(); + + mItemName->setCaption(item.getName() + _(", ") + toString(item.getId())); + mItemName->adjustSize(); + mItemName->setForegroundColor(getColor(mItemType)); + mItemName->setPosition(getPadding() + space, getPadding()); + + mItemDesc->setTextWrapped(item.getDescription(), 196); + mItemEffect->setTextWrapped(item.getEffect(), 196); + mItemWeight->setTextWrapped(strprintf(_("Weight: %s"), + Units::formatWeight(item.getWeight()).c_str()), + 196); + + int minWidth = mItemName->getWidth() + space; + + if (mItemName->getWidth() + space > minWidth) + minWidth = mItemName->getWidth() + space; + if (mItemDesc->getMinWidth() > minWidth) + minWidth = mItemDesc->getMinWidth(); + if (mItemEffect->getMinWidth() > minWidth) + minWidth = mItemEffect->getMinWidth(); + if (mItemWeight->getMinWidth() > minWidth) + minWidth = mItemWeight->getMinWidth(); + + minWidth += 8; + setWidth(minWidth); + + const int numRowsDesc = mItemDesc->getNumberOfRows(); + const int numRowsEffect = mItemEffect->getNumberOfRows(); + const int numRowsWeight = mItemWeight->getNumberOfRows(); + const int height = getFont()->getHeight(); + + if (item.getEffect().empty()) + { + setContentSize(minWidth, (numRowsDesc + numRowsWeight + getPadding()) * + height); + + mItemWeight->setPosition(getPadding(), (numRowsDesc + getPadding()) * + height); + } + else + { + setContentSize(minWidth, (numRowsDesc + numRowsEffect + numRowsWeight + + getPadding()) * height); + + mItemWeight->setPosition(getPadding(), (numRowsDesc + numRowsEffect + + getPadding()) * height); + } + + mItemDesc->setPosition(getPadding(), 2 * height); + mItemEffect->setPosition(getPadding(), + (numRowsDesc + getPadding()) * height); +} + +gcn::Color ItemPopup::getColor(ItemType type) +{ + switch (type) + { + case ITEM_UNUSABLE: + return Theme::getThemeColor(Theme::GENERIC); + case ITEM_USABLE: + return Theme::getThemeColor(Theme::USABLE); + case ITEM_EQUIPMENT_ONE_HAND_WEAPON: + return Theme::getThemeColor(Theme::ONEHAND); + case ITEM_EQUIPMENT_TWO_HANDS_WEAPON: + return Theme::getThemeColor(Theme::TWOHAND); + case ITEM_EQUIPMENT_TORSO: + return Theme::getThemeColor(Theme::TORSO); + case ITEM_EQUIPMENT_ARMS: + return Theme::getThemeColor(Theme::ARMS); + case ITEM_EQUIPMENT_HEAD: + return Theme::getThemeColor(Theme::HEAD); + case ITEM_EQUIPMENT_LEGS: + return Theme::getThemeColor(Theme::LEGS); + case ITEM_EQUIPMENT_SHIELD: + return Theme::getThemeColor(Theme::SHIELD); + case ITEM_EQUIPMENT_RING: + return Theme::getThemeColor(Theme::RING); + case ITEM_EQUIPMENT_NECKLACE: + return Theme::getThemeColor(Theme::NECKLACE); + case ITEM_EQUIPMENT_FEET: + return Theme::getThemeColor(Theme::FEET); + case ITEM_EQUIPMENT_AMMO: + return Theme::getThemeColor(Theme::AMMO); + default: + return Theme::getThemeColor(Theme::UNKNOWN_ITEM); + } +} + +void ItemPopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + // When the mouse moved on top of the popup, hide it + setVisible(false); +}
\ No newline at end of file diff --git a/src/gui/itempopup.h b/src/gui/itempopup.h new file mode 100644 index 000000000..0baf8fb79 --- /dev/null +++ b/src/gui/itempopup.h @@ -0,0 +1,71 @@ +/* + * The Mana Client + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ITEMPOPUP_H +#define ITEMPOPUP_H + +#include "gui/widgets/popup.h" + +#include "resources/iteminfo.h" + +#include <guichan/mouselistener.hpp> + +class Icon; +class TextBox; + +/** + * A popup that displays information about an item. + */ +class ItemPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + ItemPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~ItemPopup(); + + /** + * Sets the info to be displayed given a particular item. + */ + void setItem(const ItemInfo &item, bool showImage = false); + + void setItem(const Item *item, bool showImage = false); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + + private: + gcn::Label *mItemName; + TextBox *mItemDesc; + TextBox *mItemEffect; + TextBox *mItemWeight; + ItemType mItemType; + Icon *mIcon; + + static gcn::Color getColor(ItemType type); +}; + +#endif // ITEMPOPUP_H diff --git a/src/gui/killstats.cpp b/src/gui/killstats.cpp new file mode 100644 index 000000000..8ce38c719 --- /dev/null +++ b/src/gui/killstats.cpp @@ -0,0 +1,424 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "killstats.h" + +#include <math.h> +#include <guichan/widgets/label.hpp> + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/chattab.h" +#include "gui/chat.h" + +#include "actorspritemanager.h" +#include "event.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +KillStats::KillStats(): + Window(_("Kill stats")), mKillCounter(0), mExpCounter(0), + mKillTCounter(0), mExpTCounter(0), mKillTimer(0), + m1minExpTime(0), m1minExpNum(0), m1minSpeed(0), + m5minExpTime(0), m5minExpNum(0), m5minSpeed(0), + m15minExpTime(0), m15minExpNum(0), m15minSpeed(0), + mJackoSpawnTime(0), mValidateJackoTime(0), mJackoId(0), + mIsJackoAlive(false), mIsJackoMustSpawn(true), + mIsJackoSpawnTimeUnknown(true) +{ + setWindowName("Kill stats"); + setCloseButton(true); + setResizable(true); + setDefaultSize(250, 250, 350, 300); + + listen(CHANNEL_ATTRIBUTES); + int xp(PlayerInfo::getAttribute(EXP)); + int xpNextLevel(PlayerInfo::getAttribute(EXP_NEEDED)); + + mResetButton = new Button(_("Reset stats"), "reset", this); + mTimerButton = new Button(_("Reset timer"), "timer", this); + if (!xpNextLevel) + xpNextLevel = 1; + + mLine1 = new Label(_("Level: ") + toString(player_node->getLevel()) + + " at " + toString(static_cast<float>(xp) + / static_cast<float>(xpNextLevel) * 100.0f) + "%"); + + mLine2 = new Label(_("Exp: ") + toString(xp) + "/" + + toString(xpNextLevel) + _(" Left: ") + + toString(xpNextLevel-xp)); + mLine3 = new Label("1% = " + toString(xpNextLevel / 100) + + _(" exp, Avg Mob for 1%: ?")); + mLine4 = new Label(_("Kills: ?, Total Exp: ?")); + mLine5 = new Label(_("Avg Exp: ?, No. of Avg mob to next level: ?")); + mLine6 = new Label(_("Kills/Min: ?, Exp/Min: ?")); + + mExpSpeed1Label = new Label(_("Exp speed per 1 min: ?")); + mExpTime1Label = new Label(_("Time for next level per 1 min: ?")); + mExpSpeed5Label = new Label(_("Exp speed per 5 min: ?")); + mExpTime5Label = new Label(_("Time for next level per 5 min: ?")); + mExpSpeed15Label = new Label(_("Exp speed per 15 min: ?")); + mExpTime15Label = new Label(_("Time for Next level per 15 min: ?")); + + mLastKillExpLabel = new Label(_("Last kill exp: ?")); + mTimeBeforeJackoLabel = new Label(_("Time before jacko spawn: ?")); + + place(0, 0, mLine1, 6).setPadding(0); + place(0, 1, mLine2, 6).setPadding(0); + place(0, 2, mLine3, 6).setPadding(0); + place(0, 3, mLine4, 6).setPadding(0); + place(0, 4, mLine5, 6).setPadding(0); + place(0, 5, mLine6, 6).setPadding(0); + + place(0, 6, mLastKillExpLabel, 6).setPadding(0); + place(0, 7, mTimeBeforeJackoLabel, 6).setPadding(0); + place(0, 8, mExpSpeed1Label, 6).setPadding(0); + place(0, 9, mExpTime1Label, 6).setPadding(0); + place(0, 10, mExpSpeed5Label, 6).setPadding(0); + place(0, 11, mExpTime5Label, 6).setPadding(0); + place(0, 12, mExpSpeed15Label, 6).setPadding(0); + place(0, 13, mExpTime15Label, 6).setPadding(0); + + place(5, 12, mTimerButton).setPadding(0); + place(5, 13, mResetButton).setPadding(0); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + loadWindowState(); + +} + +KillStats::~KillStats() +{ +} + +void KillStats::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "reset") + { + mKillCounter = 0; + mExpCounter = 0; + mLine3->setCaption("1% = " + toString( + PlayerInfo::getAttribute(EXP_NEEDED) / 100) + + " exp, Avg Mob for 1%: ?"); + mLine4->setCaption(_("Kills: ?, Total Exp: ?")); + mLine5->setCaption(_("Avg Exp: ?, No. of Avg mob to next level: ?")); + + m1minExpTime = 0; + m1minExpNum = 0; + m1minSpeed = 0; + m5minExpTime = 0; + m5minExpNum = 0; + m5minSpeed = 0; + m15minExpTime = 0; + m15minExpNum = 0; + m15minSpeed = 0; + } + else if (event.getId() == "timer") + { + mKillTimer = 0; + mKillTCounter = 0; + mExpTCounter = 0; + mLine6->setCaption(_("Kills/Min: ?, Exp/Min: ?")); + + m1minExpTime = 0; + m1minExpNum = 0; + m1minSpeed = 0; + m5minExpTime = 0; + m5minExpNum = 0; + m5minSpeed = 0; + m15minExpTime = 0; + m15minExpNum = 0; + m15minSpeed = 0; + } +} + +void KillStats::gainXp(int xp) +{ + if (xp == PlayerInfo::getAttribute(EXP_NEEDED)) + xp = 0; + else if (!xp) + return; + + mKillCounter++; + mKillTCounter++; + + mExpCounter = mExpCounter + xp; + mExpTCounter = mExpTCounter + xp; + if (!mKillCounter) + mKillCounter = 1; + + float AvgExp = static_cast<float>(mExpCounter / mKillCounter); + int xpNextLevel(PlayerInfo::getAttribute(EXP_NEEDED)); + + if (mKillTimer == 0) + mKillTimer = cur_time; + + if (!xpNextLevel) + xpNextLevel = 1; + + double timeDiff = difftime(cur_time, mKillTimer) / 60; + + if (timeDiff <= 0.001) + timeDiff = 1; + + mLine1->setCaption("Level: " + toString(player_node->getLevel()) + " at " + + toString(static_cast<float>(PlayerInfo::getAttribute(EXP)) + / static_cast<float>(xpNextLevel) * 100.0f) + "%"); + + mLine2->setCaption("Exp: " + toString(PlayerInfo::getAttribute(EXP)) + "/" + + toString(xpNextLevel) + " Left: " + + toString(xpNextLevel - PlayerInfo::getAttribute(EXP))); + if (AvgExp >= 0.001 && AvgExp <= 0.001) + { + mLine3->setCaption("1% = " + toString(xpNextLevel / 100) + + " exp, Avg Mob for 1%: ?"); + + mLine5->setCaption("Avg Exp: " + toString(AvgExp) + + ", No. of Avg mob to next level: ?"); + } + else + { + mLine3->setCaption("1% = " + toString(xpNextLevel / 100) + + " exp, Avg Mob for 1%: " + + toString((static_cast<float>(xpNextLevel) / 100) / AvgExp)); + + mLine5->setCaption("Avg Exp: " + toString(AvgExp) + + ", No. of Avg mob to next level: " + + toString(static_cast<float>(xpNextLevel - + PlayerInfo::getAttribute(EXP)) / AvgExp)); + } + mLine4->setCaption("Kills: " + toString(mKillCounter) + + ", Total Exp: " + toString(mExpCounter)); + + mLine6->setCaption("Kills/Min: " + toString(mKillTCounter / timeDiff) + + ", Exp/Min: " + toString(mExpTCounter / timeDiff)); + + mLastKillExpLabel->setCaption("Last Kill Exp: " + toString(xp)); + + recalcStats(); + update(); +} + +void KillStats::recalcStats() +{ + int curTime = cur_time; + + // Need Update Exp Counter + if (curTime - m1minExpTime > 60) + { + int newExp = PlayerInfo::getAttribute(EXP); + if (m1minExpTime != 0) + m1minSpeed = newExp - m1minExpNum; + else + m1minSpeed = 0; + m1minExpTime = curTime; + m1minExpNum = newExp; + } + + if (curTime - m5minExpTime > 60*5) + { + int newExp = PlayerInfo::getAttribute(EXP); + if (m5minExpTime != 0) + m5minSpeed = newExp - m5minExpNum; + else + m5minSpeed = 0; + m5minExpTime = curTime; + m5minExpNum = newExp; + } + + if (curTime - m15minExpTime > 60*15) + { + int newExp = PlayerInfo::getAttribute(EXP); + if (m15minExpTime != 0) + m15minSpeed = newExp - m15minExpNum; + else + m15minSpeed = 0; + m15minExpTime = curTime; + m15minExpNum = newExp; + } + validateJacko(); +} + +void KillStats::update() +{ + mExpSpeed1Label->setCaption( + strprintf(_("Exp Speed per 1 min: %d"), m1minSpeed)); + mExpSpeed1Label->adjustSize(); + + if (m1minSpeed != 0) + { + mExpTime1Label->setCaption(strprintf(_(" Time For Next Level: %f"), + static_cast<float>((PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP)) / m1minSpeed))); + } + else + { + mExpTime1Label->setCaption(_(" Time For Next Level: ?")); + } + mExpTime1Label->adjustSize(); + + mExpSpeed5Label->setCaption( + strprintf(_("Exp Speed per 5 min: %d"), m5minSpeed / 5)); + mExpSpeed5Label->adjustSize(); + + if (m5minSpeed != 0) + { + mExpTime5Label->setCaption(strprintf(_(" Time For Next Level: %f"), + static_cast<float>((PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP)) / m5minSpeed * 5))); + } + else + { + mExpTime5Label->setCaption(_(" Time For Next Level: ?")); + } + mExpTime5Label->adjustSize(); + + mExpSpeed15Label->setCaption( + strprintf(_("Exp Speed per 15 min: %d"), m15minSpeed / 15)); + mExpSpeed15Label->adjustSize(); + + if (m15minSpeed != 0) + { + mExpTime15Label->setCaption(strprintf(_(" Time For Next Level: %f"), + static_cast<float>((PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP)) / m15minSpeed * 15))); + } + else + { + mExpTime15Label->setCaption(_(" Time For Next Level: ?")); + } + + validateJacko(); + updateJackoLabel(); +} +void KillStats::draw(gcn::Graphics *g) +{ + update(); + + Window::draw(g); +} + +void KillStats::updateJackoLabel() +{ + if (mIsJackoAlive) + { + mTimeBeforeJackoLabel->setCaption( + _("Time before jacko spawn: jacko alive")); + } + else if (mIsJackoSpawnTimeUnknown && mJackoSpawnTime != 0) + { + mTimeBeforeJackoLabel->setCaption(_("Time before jacko spawn: ") + + toString(mJackoSpawnTime - cur_time) + _("?")); + } + else if (mIsJackoMustSpawn) + { + mTimeBeforeJackoLabel->setCaption( + _("Time before jacko spawn: jacko spawning")); + } + else + { + mTimeBeforeJackoLabel->setCaption(_("Time before jacko spawn: ") + + toString(mJackoSpawnTime - cur_time)); + } +} + +void KillStats::jackoDead(int id) +{ + if (id == mJackoId && mIsJackoAlive) + { + mIsJackoAlive = false; + mJackoSpawnTime = cur_time + 60*4; + mIsJackoSpawnTimeUnknown = false; + updateJackoLabel(); + } +} + +void KillStats::addLog(std::string str) +{ + if (debugChatTab) + debugChatTab->chatLog(str, BY_SERVER); +} + +void KillStats::jackoAlive(int id) +{ + if (!mIsJackoAlive) + { + mJackoId = id; + mIsJackoAlive = true; + mIsJackoMustSpawn = false; + mJackoSpawnTime = 0; + mIsJackoSpawnTimeUnknown = false; + updateJackoLabel(); + } +} + +void KillStats::validateJacko() +{ + if (!actorSpriteManager || !player_node) + return; + + Map *currentMap = Game::instance()->getCurrentMap(); + if (currentMap) + { + if (currentMap->getProperty("_filename") == "018-1" + || currentMap->getProperty("_filename") == "maps/018-1.tmx") + { + if (player_node->getTileX() >= 167 + && player_node->getTileX() <= 175 + && player_node->getTileY() >= 21 + && player_node->getTileY() <= 46) + { + Being *dstBeing = actorSpriteManager->findBeingByName( + "Jack O", Being::MONSTER); + if (mIsJackoAlive && !dstBeing) + { + mIsJackoAlive = false; + mJackoSpawnTime = cur_time + 60*4; + mIsJackoSpawnTimeUnknown = true; + } + } + } + + if (!mIsJackoAlive && cur_time > mJackoSpawnTime + 15) + mIsJackoMustSpawn = true; + } +} + +void KillStats::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + int id = event.getInt("id"); + if (id == EXP || id == EXP_NEEDED) + { + gainXp(event.getInt("newValue") - event.getInt("oldValue")); +// update(); + } + } +} diff --git a/src/gui/killstats.h b/src/gui/killstats.h new file mode 100644 index 000000000..df53aa319 --- /dev/null +++ b/src/gui/killstats.h @@ -0,0 +1,132 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef KILLSTATS_H +#define KILLSTATS_H + +#include <guichan/actionlistener.hpp> + +#include "listener.h" + +#include "gui/widgets/window.h" + +class Label; +class Button; + +class KillStats : public Window, gcn::ActionListener, public Mana::Listener +{ + public: + /** + * Constructor. + */ + KillStats(); + + /** + * Destructor. + */ + ~KillStats(); + + /** + * Stuff. + */ + void action(const gcn::ActionEvent &event); + void gainXp(int Xp); + + /** + * Recalc stats if needed + */ + void recalcStats(); + + /** + * Draw this window + */ + void draw(gcn::Graphics *graphics); + + /** + * Updates this dialog + */ + void update(); + + /** + * Updates jacko info + */ + void updateJackoLabel(); + + void jackoDead(int id); + + void jackoAlive(int id); + + void addLog(std::string str); + + void event(Channels channel _UNUSED_, + const Mana::Event &event); + + private: + void validateJacko(); + + int mKillCounter; /**< Session Kill counter. */ + int mExpCounter; /**< Session Exp counter. */ + int mKillTCounter; /**< Timer Kill counter. */ + int mExpTCounter; /**< Timer Exp counter. */ + time_t mKillTimer; /**< Timer for kill stats. */ + Button *mResetButton; + Button *mTimerButton; + Label *mLine1; + Label *mLine2; + Label *mLine3; + Label *mLine4; + Label *mLine5; + Label *mLine6; + + gcn::Label *mExpSpeed1Label; + gcn::Label *mExpTime1Label; + gcn::Label *mExpSpeed5Label; + gcn::Label *mExpTime5Label; + gcn::Label *mExpSpeed15Label; + gcn::Label *mExpTime15Label; + + gcn::Label *mLastKillExpLabel; + gcn::Label *mTimeBeforeJackoLabel; + + int m1minExpTime; + int m1minExpNum; + int m1minSpeed; + + int m5minExpTime; + int m5minExpNum; + int m5minSpeed; + + int m15minExpTime; + int m15minExpNum; + int m15minSpeed; + + int mJackoSpawnTime; + int mValidateJackoTime; + int mJackoId; + bool mIsJackoAlive; + bool mIsJackoMustSpawn; + bool mIsJackoSpawnTimeUnknown; +}; + +extern KillStats *killStats; + +#endif diff --git a/src/gui/login.cpp b/src/gui/login.cpp new file mode 100644 index 000000000..997895c95 --- /dev/null +++ b/src/gui/login.cpp @@ -0,0 +1,231 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/login.h" + +#include "client.h" +#include "configuration.h" + +#include "gui/okdialog.h" +#include "gui/sdlinput.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +static const int MAX_SERVER_LIST_SIZE = 15; +static const int LOGIN_DIALOG_WIDTH = 300; +static const int LOGIN_DIALOG_HEIGHT = 140; +static const int FIELD_WIDTH = LOGIN_DIALOG_WIDTH - 70; + +std::string LoginDialog::savedPassword = ""; +std::string LoginDialog::savedPasswordKey = ""; + + +const char *UPDATE_TYPE_TEXT[3] = +{ + _("Normal"), + _("Auto Close"), + _("Skip"), +}; + +class UpdateTypeModel : public gcn::ListModel +{ +public: + virtual ~UpdateTypeModel() + { } + + virtual int getNumberOfElements() + { + return 3; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return UPDATE_TYPE_TEXT[i]; + } +}; + +LoginDialog::LoginDialog(LoginData *loginData, std::string serverName, + std::string *updateHost): + Window(_("Login")), + mLoginData(loginData), + mUpdateHost(updateHost) +{ + gcn::Label *serverLabel1 = new Label(_("Server:")); + gcn::Label *serverLabel2 = new Label(serverName); + serverLabel2->adjustSize(); + gcn::Label *userLabel = new Label(_("Name:")); + gcn::Label *passLabel = new Label(_("Password:")); + mCustomUpdateHost = new CheckBox(_("Custom update host"), + loginData->updateType & LoginData::Upd_Custom, this, "customhost"); + + mUpdateHostText = new TextField(serverConfig.getValue( + "customUpdateHost", "")); + + mUpdateHostText->adjustSize(); + + mUserField = new TextField(mLoginData->username); + mPassField = new PasswordField(mLoginData->password); + + if (mPassField->getText().empty() && LoginDialog::savedPassword != "") + mPassField->setText(LoginDialog::savedPassword); + + mKeepCheck = new CheckBox(_("Remember username"), mLoginData->remember); + mUpdateTypeLabel = new Label(_("Update:")); + mUpdateTypeDropDown = new DropDown(new UpdateTypeModel()); + mUpdateTypeDropDown->setActionEventId("updatetype"); + mUpdateTypeDropDown->setSelected((loginData->updateType + | LoginData::Upd_Custom) ^ LoginData::Upd_Custom); + + if (!mCustomUpdateHost->isSelected()) + mUpdateHostText->setVisible(false); + + mRegisterButton = new Button(_("Register"), "register", this); + mServerButton = new Button(_("Change Server"), "server", this); + mLoginButton = new Button(_("Login"), "login", this); + + mUserField->setActionEventId("login"); + mPassField->setActionEventId("login"); + + mUserField->addKeyListener(this); + mPassField->addKeyListener(this); + mUserField->addActionListener(this); + mPassField->addActionListener(this); + + place(0, 0, serverLabel1); + place(1, 0, serverLabel2, 8).setPadding(1); + + place(0, 1, userLabel); + place(0, 2, passLabel); + place(1, 1, mUserField, 8).setPadding(1); + place(1, 2, mPassField, 8).setPadding(1); + place(0, 6, mUpdateTypeLabel, 1); + place(1, 6, mUpdateTypeDropDown, 8); + place(0, 7, mCustomUpdateHost, 9); + place(0, 8, mUpdateHostText, 9); + place(0, 9, mKeepCheck, 9); + place(0, 10, mRegisterButton).setHAlign(LayoutCell::LEFT); + place(2, 10, mServerButton); + place(3, 10, mLoginButton); + + reflowLayout(); + + addKeyListener(this); + + center(); + setVisible(true); + + if (mUserField->getText().empty()) + mUserField->requestFocus(); + else + mPassField->requestFocus(); + + mLoginButton->setEnabled(canSubmit()); + mRegisterButton->setEnabled(Net::getLoginHandler() + ->isRegistrationEnabled()); +} + +LoginDialog::~LoginDialog() +{ +} + +void LoginDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "login" && canSubmit()) + { + mLoginData->username = mUserField->getText(); + mLoginData->password = mPassField->getText(); + mLoginData->remember = mKeepCheck->isSelected(); + int updateType = mUpdateTypeDropDown->getSelected(); + + if (mCustomUpdateHost->isSelected()) + { + updateType |= LoginData::Upd_Custom; + serverConfig.setValue("customUpdateHost", + mUpdateHostText->getText()); + + mLoginData->updateHost = mUpdateHostText->getText(); + *mUpdateHost = mUpdateHostText->getText(); + } + mLoginData->updateType = updateType; + serverConfig.setValue("updateType", updateType); + + mLoginData->registerLogin = false; + + mRegisterButton->setEnabled(false); + mServerButton->setEnabled(false); + mLoginButton->setEnabled(false); + + if (mLoginData->remember) + LoginDialog::savedPassword = mPassField->getText(); + + Client::setState(STATE_LOGIN_ATTEMPT); + } + else if (event.getId() == "server") + { + Client::setState(STATE_SWITCH_SERVER); + } + else if (event.getId() == "register") + { + mLoginData->username = mUserField->getText(); + mLoginData->password = mPassField->getText(); + + Client::setState(STATE_REGISTER_PREP); + } + else if (event.getId() == "customhost") + { + mUpdateHostText->setVisible(mCustomUpdateHost->isSelected()); + } +} + +void LoginDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + action(gcn::ActionEvent(NULL, mServerButton->getActionEventId())); + else if (key.getValue() == Key::ENTER) + action(gcn::ActionEvent(NULL, mLoginButton->getActionEventId())); + else + mLoginButton->setEnabled(canSubmit()); +} + +bool LoginDialog::canSubmit() const +{ + return !mUserField->getText().empty() && + !mPassField->getText().empty() && + Client::getState() == STATE_LOGIN; +} diff --git a/src/gui/login.h b/src/gui/login.h new file mode 100644 index 000000000..f20637032 --- /dev/null +++ b/src/gui/login.h @@ -0,0 +1,90 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LOGIN_H +#define LOGIN_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> +#include <guichan/listmodel.hpp> + +#include <string> +#include <vector> + +class LoginData; + +/** + * The login dialog. + * + * \ingroup Interface + */ +class LoginDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + LoginDialog(LoginData *loginData, std::string serverName, + std::string *updateHost); + + ~LoginDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Called when a key is pressed in one of the text fields. + */ + void keyPressed(gcn::KeyEvent &keyEvent); + + static std::string savedPasswordKey; + static std::string savedPassword; + + private: + /** + * Returns whether submit can be enabled. This is true in the login + * state, when all necessary fields have some text. + */ + bool canSubmit() const; + + gcn::TextField *mUserField; + gcn::TextField *mPassField; + gcn::CheckBox *mKeepCheck; + gcn::Label *mUpdateTypeLabel; + gcn::DropDown *mUpdateTypeDropDown; + gcn::Button *mServerButton; + gcn::Button *mLoginButton; + gcn::Button *mRegisterButton; + gcn::CheckBox *mCustomUpdateHost; + gcn::TextField *mUpdateHostText; + + LoginData *mLoginData; + std::string *mUpdateHost; +}; + +#endif diff --git a/src/gui/minimap.cpp b/src/gui/minimap.cpp new file mode 100644 index 000000000..6edefcfdd --- /dev/null +++ b/src/gui/minimap.cpp @@ -0,0 +1,292 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/minimap.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "configuration.h" +#include "graphics.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" + +#include "gui/userpalette.h" +#include "gui/setup.h" +#include "gui/viewport.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" + +#include <guichan/font.hpp> + +bool Minimap::mShow = true; + +Minimap::Minimap(): + Window(_("Map")), + mMapImage(0), + mWidthProportion(0.5), + mHeightProportion(0.5) +{ + setWindowName("Minimap"); + mShow = config.getValueBool(getWindowName() + "Show", true); + setDefaultSize(5, 25, 100, 100); + // set this to false as the minimap window size is changed + //depending on the map size + setResizable(false); + setupWindow->registerWindowForReset(this); + + setDefaultVisible(true); + setSaveVisible(true); + + setStickyButton(true); + setSticky(false); + + loadWindowState(); + setVisible(mShow, isSticky()); +} + +Minimap::~Minimap() +{ + config.setValue(getWindowName() + "Show", mShow); + + if (mMapImage) + mMapImage->decRef(); +} + +void Minimap::setMap(Map *map) +{ + std::string caption = ""; + std::string minimapName; + + if (map) + caption = map->getName(); + + if (caption.empty()) + caption = _("Map"); + + setCaption(caption); + + // Adapt the image + if (mMapImage) + { + mMapImage->decRef(); + mMapImage = 0; + } + + if (map) + { + std::string tempname = + "graphics/minimaps/" + map->getFilename() + ".png"; + ResourceManager *resman = ResourceManager::getInstance(); + + minimapName = map->getProperty("minimap"); + + if (minimapName.empty() && resman->exists(tempname)) + minimapName = tempname; + + mMapImage = resman->getImage(minimapName); + } + + if (mMapImage && map) + { + const int offsetX = 2 * getPadding(); + const int offsetY = getTitleBarHeight() + getPadding(); + const int titleWidth = getFont()->getWidth(getCaption()) + 15; + const int mapWidth = mMapImage->getWidth() < 100 ? + mMapImage->getWidth() + offsetX : 100; + const int mapHeight = mMapImage->getHeight() < 100 ? + mMapImage->getHeight() + offsetY : 100; + + setMinWidth(mapWidth > titleWidth ? mapWidth : titleWidth); + setMinHeight(mapHeight); + + mWidthProportion = static_cast<float>( + mMapImage->getWidth()) / static_cast<float>(map->getWidth()); + mHeightProportion = static_cast<float>( + mMapImage->getHeight()) / static_cast<float>(map->getHeight()); + + setMaxWidth(mMapImage->getWidth() > titleWidth ? + mMapImage->getWidth() + offsetX : titleWidth); + setMaxHeight(mMapImage->getHeight() + offsetY); + + setDefaultSize(getX(), getY(), getWidth(), getHeight()); + resetToDefaultSize(); + + if (mShow) + setVisible(true); + } + else + { + if (!isSticky()) + setVisible(false); + } +} + +void Minimap::toggle() +{ + setVisible(!isVisible(), isSticky()); + mShow = isVisible(); +} + +void Minimap::draw(gcn::Graphics *graphics) +{ + Window::draw(graphics); + + if (!userPalette || !player_node || !viewport) + return; + + Graphics *graph = static_cast<Graphics*>(graphics); + + const gcn::Rectangle a = getChildrenArea(); + + graphics->pushClipArea(a); + + int mapOriginX = 0; + int mapOriginY = 0; + + if (mMapImage) + { + if (mMapImage->getWidth() > a.width || + mMapImage->getHeight() > a.height) + { + const Vector &p = player_node->getPosition(); + mapOriginX = ((a.width) / 2) - static_cast<int>((p.x + + viewport->getCameraRelativeX()) * mWidthProportion) / 32; + mapOriginY = ((a.height) / 2) - static_cast<int>((p.y + + viewport->getCameraRelativeX()) * mHeightProportion) / 32; + + const int minOriginX = a.width - mMapImage->getWidth(); + const int minOriginY = a.height - mMapImage->getHeight(); + + if (mapOriginX < minOriginX) + mapOriginX = minOriginX; + if (mapOriginY < minOriginY) + mapOriginY = minOriginY; + if (mapOriginX > 0) + mapOriginX = 0; + if (mapOriginY > 0) + mapOriginY = 0; + } + + graph->drawImage(mMapImage, mapOriginX, mapOriginY); + } + + if (!actorSpriteManager) + return; + + const ActorSprites &actors = actorSpriteManager->getAll(); + + for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end(); + it != it_end; it++) + { + if (!(*it) || (*it)->getType() == ActorSprite::FLOOR_ITEM) + continue; + + const Being *being = static_cast<Being*>(*it); + if (!being) + continue; + + int dotSize = 2; + + int type = UserPalette::PC; + + if (being == player_node) + { + type = UserPalette::SELF; + dotSize = 3; + } + else if (being->isGM()) + { + type = UserPalette::GM; + } + else if (being->isInParty()) + { + type = UserPalette::PARTY; + } + else if (being) + { + switch (being->getType()) + { + case ActorSprite::MONSTER: + type = UserPalette::MONSTER; + break; + + case ActorSprite::NPC: + type = UserPalette::NPC; + break; + + default: + continue; + } + } + + if (userPalette) + graphics->setColor(userPalette->getColor(type)); + + const int offsetHeight = static_cast<int>(static_cast<float>( + dotSize - 1) * mHeightProportion); + const int offsetWidth = static_cast<int>(static_cast<float>( + dotSize - 1) * mWidthProportion); + const Vector &pos = being->getPosition(); + + graphics->fillRectangle(gcn::Rectangle( + static_cast<int>(pos.x * mWidthProportion) / 32 + + mapOriginX - offsetWidth, + static_cast<int>(pos.y * mHeightProportion) / 32 + + mapOriginY - offsetHeight, + dotSize, dotSize)); + } + + const Vector &pos = player_node->getPosition(); +// logger->log("width:" + toString(graph->getWidth())); + + int x = static_cast<int>((pos.x - (graph->getWidth() / 2) + + viewport->getCameraRelativeX()) + * mWidthProportion) / 32 + mapOriginX; + int y = static_cast<int>((pos.y - (graph->getHeight() / 2) + + viewport->getCameraRelativeY()) + * mHeightProportion) / 32 + mapOriginY; + + const int w = graph->getWidth() * mWidthProportion / 32; + const int h = graph->getHeight() * mHeightProportion / 32; + + if (w <= a.width) + { + if (x < 0 && w) + x = 0; + if (x + w > a.width) + x = a.width - w; + } + if (h <= a.height) + { + if (y < 0 && h) + y = 0; + if (y + h > a.height) + y = a.height - h; + } + + graphics->setColor(userPalette->getColor(UserPalette::PC)); + graphics->drawRectangle(gcn::Rectangle(x, y, w, h)); + graphics->popClipArea(); +} diff --git a/src/gui/minimap.h b/src/gui/minimap.h new file mode 100644 index 000000000..a376a15c1 --- /dev/null +++ b/src/gui/minimap.h @@ -0,0 +1,69 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MINIMAP_H +#define MINIMAP_H + +#include "gui/widgets/window.h" + +class Image; +class Map; + +/** + * Minimap window. Shows a minimap image and the name of the current map. + * + * The name of the map is defined by the map property "name". The minimap image + * is defined by the map property "minimap". The path to the image should be + * given relative to the root of the client data. + * + * \ingroup Interface + */ +class Minimap : public Window +{ + public: + Minimap(); + ~Minimap(); + + /** + * Sets the map image that should be displayed. + */ + void setMap(Map *map); + + /** + * Toggles the displaying of the minimap. + */ + void toggle(); + + /** + * Draws the minimap. + */ + void draw(gcn::Graphics *graphics); + + private: + Image *mMapImage; + float mWidthProportion; + float mHeightProportion; + static bool mShow; +}; + +extern Minimap *minimap; + +#endif diff --git a/src/gui/ministatus.cpp b/src/gui/ministatus.cpp new file mode 100644 index 000000000..406ad7ddd --- /dev/null +++ b/src/gui/ministatus.cpp @@ -0,0 +1,228 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/ministatus.h" + +#include "animatedsprite.h" +#include "configuration.h" +#include "graphics.h" +#include "playerinfo.h" + +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/statuswindow.h" +#include "gui/statuspopup.h" +#include "gui/textpopup.h" +#include "gui/theme.h" + +#include "gui/widgets/chattab.h" +#include "gui/widgets/label.h" +#include "gui/widgets/progressbar.h" + +#include "net/net.h" +#include "net/playerhandler.h" +#include "net/gamehandler.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern volatile int tick_time; + +MiniStatusWindow::MiniStatusWindow(): + Popup("MiniStatus") +{ + listen(CHANNEL_ATTRIBUTES); + + mHpBar = new ProgressBar(0, 100, 20, Theme::PROG_HP); + StatusWindow::updateHPBar(mHpBar); + + if (Net::getGameHandler()->canUseMagicBar()) + { + mMpBar = new ProgressBar(0, 100, 20, + Net::getPlayerHandler()->canUseMagic() + ? Theme::PROG_MP : Theme::PROG_NO_MP); + + StatusWindow::updateMPBar(mMpBar); + } + else + { + mMpBar = 0; + } + + mXpBar = new ProgressBar(0, 100, 20, Theme::PROG_EXP); + StatusWindow::updateXPBar(mXpBar); + + mStatusBar = new ProgressBar(100, 150, 20, Theme::PROG_EXP); + + mHpBar->setPosition(0, 3); + if (mMpBar) + mMpBar->setPosition(mHpBar->getWidth() + 3, 3); + mXpBar->setPosition(mMpBar ? mMpBar->getX() + mMpBar->getWidth() + 3 : + mHpBar->getX() + mHpBar->getWidth() + 3, 3); + mStatusBar->setPosition(mXpBar->getX() + mXpBar->getWidth() + 3, 3); + + add(mHpBar); + if (mMpBar) + add(mMpBar); + add(mXpBar); + add(mStatusBar); + + setContentSize(mStatusBar->getX() + mStatusBar->getWidth(), + mXpBar->getY() + mXpBar->getHeight()); + + setVisible(config.getValueBool(getPopupName() + "Visible", true)); + + mStatusPopup = new StatusPopup(); + mTextPopup = new TextPopup(); + + addMouseListener(this); + updateStatus(); +} + +MiniStatusWindow::~MiniStatusWindow() +{ + delete mTextPopup; + mTextPopup = 0; + delete mStatusPopup; + mStatusPopup = 0; +} + +void MiniStatusWindow::setIcon(int index, AnimatedSprite *sprite) +{ + if (index >= static_cast<int>(mIcons.size())) + mIcons.resize(index + 1, 0); + + delete mIcons[index]; + + mIcons[index] = sprite; +} + +void MiniStatusWindow::eraseIcon(int index) +{ + mIcons.erase(mIcons.begin() + index); +} + +void MiniStatusWindow::drawIcons(Graphics *graphics) +{ + // Draw icons + int icon_x = mStatusBar->getX() + mStatusBar->getWidth() + 4; + for (unsigned int i = 0; i < mIcons.size(); i++) + { + if (mIcons[i]) + { + mIcons[i]->draw(graphics, icon_x, 3); + icon_x += 2 + mIcons[i]->getWidth(); + } + } +} + +void MiniStatusWindow::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + int id = event.getInt("id"); + if (id == HP || id == MAX_HP) + StatusWindow::updateHPBar(mHpBar); + else if (id == MP || id == MAX_MP) + StatusWindow::updateMPBar(mMpBar); + else if (id == EXP || id == EXP_NEEDED) + StatusWindow::updateXPBar(mXpBar); + } + else if (event.getName() == EVENT_UPDATESTAT) + { + StatusWindow::updateMPBar(mMpBar); + } +} + +void MiniStatusWindow::updateStatus() +{ + StatusWindow::updateStatusBar(mStatusBar); + if (mStatusPopup && mStatusPopup->isVisible()) + mStatusPopup->update(); +} + +void MiniStatusWindow::logic() +{ + Popup::logic(); + + for (unsigned int i = 0; i < mIcons.size(); i++) + { + if (mIcons[i]) + mIcons[i]->update(tick_time * 10); + } +} + +void MiniStatusWindow::draw(gcn::Graphics *graphics) +{ + drawChildren(graphics); +} + +void MiniStatusWindow::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + const int x = event.getX(); + const int y = event.getY(); + + if (event.getSource() == mStatusBar) + { + mStatusPopup->view(x + getX(), y + getY()); + mTextPopup->hide(); + } + else if (event.getSource() == mXpBar) + { + mTextPopup->show(x + getX(), y + getY(), + strprintf("%u/%u", PlayerInfo::getAttribute(EXP), + PlayerInfo::getAttribute(EXP_NEEDED)), + strprintf("%s: %u", _("Need"), + PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP))); + mStatusPopup->hide(); + } + else if (event.getSource() == mHpBar) + { + mTextPopup->show(x + getX(), y + getY(), + strprintf("%u/%u", PlayerInfo::getAttribute(HP), + PlayerInfo::getAttribute(MAX_HP))); + mStatusPopup->hide(); + } + else if (event.getSource() == mMpBar) + { + mTextPopup->show(x + getX(), y + getY(), + strprintf("%u/%u", PlayerInfo::getAttribute(MP), + PlayerInfo::getAttribute(MAX_MP))); + mStatusPopup->hide(); + } + else + { + mTextPopup->hide(); + mStatusPopup->hide(); + } +} + +void MiniStatusWindow::mouseExited(gcn::MouseEvent &event) +{ + Popup::mouseExited(event); + + mTextPopup->hide(); + mStatusPopup->hide(); +} diff --git a/src/gui/ministatus.h b/src/gui/ministatus.h new file mode 100644 index 000000000..080beece3 --- /dev/null +++ b/src/gui/ministatus.h @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MINISTATUS_H +#define MINISTATUS_H + +#include "listener.h" + +#include "gui/widgets/popup.h" +#include "gui/widgets/window.h" + +#include <vector> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class AnimatedSprite; +class Graphics; +class ProgressBar; +class StatusPopup; +class TextPopup; + +/** + * The player mini-status dialog. + * + * \ingroup Interface + */ +class MiniStatusWindow : public Popup, public Mana::Listener +{ + public: + MiniStatusWindow(); + + ~MiniStatusWindow(); + + /** + * Sets one of the icons. + */ + void setIcon(int index, AnimatedSprite *sprite); + + void eraseIcon(int index); + + void drawIcons(Graphics *graphics); + + void event(Channels channel, const Mana::Event &event); + + void updateStatus(); + + void logic(); // Updates icons + + void draw(gcn::Graphics *graphics); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + void mouseExited(gcn::MouseEvent &event); + + private: + bool isInBar(ProgressBar *bar, int x, int y) const; + + /* + * Mini Status Bars + */ + ProgressBar *mHpBar; + ProgressBar *mMpBar; + ProgressBar *mXpBar; + ProgressBar *mStatusBar; + TextPopup *mTextPopup; + StatusPopup *mStatusPopup; + + std::vector<AnimatedSprite *> mIcons; +}; + +extern MiniStatusWindow *miniStatusWindow; + +#endif diff --git a/src/gui/npcdialog.cpp b/src/gui/npcdialog.cpp new file mode 100644 index 000000000..6414da6ed --- /dev/null +++ b/src/gui/npcdialog.cpp @@ -0,0 +1,483 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/npcdialog.h" + +#include "configuration.h" +#include "client.h" + +#include "gui/setup.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/textbox.h" +#include "gui/widgets/textfield.h" + +#include "net/net.h" +#include "net/npchandler.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> + +#define CAPTION_WAITING _("Waiting for server") +#define CAPTION_NEXT _("Next") +#define CAPTION_CLOSE _("Close") +#define CAPTION_SUBMIT _("Submit") + +NpcDialog::DialogList NpcDialog::instances; + +NpcDialog::NpcDialog(int npcId) + : Window(_("NPC")), + mNpcId(npcId), + mLogInteraction(config.getBoolValue("logNpcInGui")), + mDefaultInt(0), + mInputState(NPC_INPUT_NONE), + mActionState(NPC_ACTION_WAIT), + mLastNextTime(0) +{ + // Basic Window Setup + setWindowName("NpcText"); + setResizable(true); + //setupWindow->registerWindowForReset(this); + setFocusable(true); + + setMinWidth(200); + setMinHeight(150); + + setDefaultSize(260, 200, ImageRect::CENTER); + + // Setup output text box + mTextBox = new TextBox; + mTextBox->setEditable(false); + mTextBox->setOpaque(false); + + mScrollArea = new ScrollArea(mTextBox); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mScrollArea->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + + // Setup listbox + mItemList = new ListBox(this); + mItemList->setWrappingEnabled(true); + setContentSize(260, 175); + + mListScrollArea = new ScrollArea(mItemList); + mListScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mItemList->setVisible(true); + + // Setup string input box + mTextField = new TextField(""); + mTextField->setVisible(true); + + // Setup int input box + mIntField = new IntTextField; + mIntField->setVisible(true); + + mClearButton = new Button(_("Clear"), "clear", this); + + // Setup button + mButton = new Button("", "ok", this); + + //Setup more and less buttons (int input) + mPlusButton = new Button(_("+"), "inc", this); + mMinusButton = new Button(_("-"), "dec", this); + + int width = std::max(mButton->getFont()->getWidth(CAPTION_WAITING), + mButton->getFont()->getWidth(CAPTION_NEXT)); + width = std::max(width, mButton->getFont()->getWidth(CAPTION_CLOSE)); + width = std::max(width, mButton->getFont()->getWidth(CAPTION_SUBMIT)); + + mButton->setWidth(8 + width); + + mResetButton = new Button(_("Reset"), "reset", this); + + // Place widgets + buildLayout(); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); + requestFocus(); + + config.addListener("logNpcInGui", this); +} + +NpcDialog::~NpcDialog() +{ + config.removeListener("logNpcInGui", this); + + // These might not actually be in the layout, so lets be safe + delete mScrollArea; + mScrollArea = 0; + delete mItemList; + mItemList = 0; + delete mTextField; + mTextField = 0; + delete mIntField; + mIntField = 0; + delete mResetButton; + mResetButton = 0; + delete mPlusButton; + mPlusButton = 0; + delete mMinusButton; + mMinusButton = 0; + + instances.remove(this); + +} + +void NpcDialog::setText(const std::string &text) +{ + mText = text; + + mTextBox->setTextWrapped(mText, mScrollArea->getWidth() - 15); +} + +void NpcDialog::addText(const std::string &text, bool save) +{ + if (save || mLogInteraction) + { + if (mText.size() > 5000) + mText = ""; + + mNewText += text + "\n"; + setText(mText + text + "\n"); + } + mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll()); + mActionState = NPC_ACTION_WAIT; + buildLayout(); +} + +void NpcDialog::showNextButton() +{ + mActionState = NPC_ACTION_NEXT; + buildLayout(); +} + +void NpcDialog::showCloseButton() +{ + mActionState = NPC_ACTION_CLOSE; + buildLayout(); +} + +void NpcDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok") + { + if (mActionState == NPC_ACTION_NEXT) + { + if (!Client::limitPackets(PACKET_NPC_NEXT)) + return; + + nextDialog(); + // TRANSLATORS: Please leave the \n sequences intact. + addText(_("\n> Next\n"), false); + } + else if (mActionState == NPC_ACTION_CLOSE) + { + closeDialog(); + } + else if (mActionState == NPC_ACTION_INPUT) + { + std::string printText = ""; // Text that will get printed + // in the textbox + + if (mInputState == NPC_INPUT_LIST) + { + unsigned char choice = 0; + int selectedIndex = mItemList->getSelected(); + + if (selectedIndex >= static_cast<int>(mItems.size()) + || selectedIndex < 0 + || !Client::limitPackets(PACKET_NPC_INPUT)) + { + return; + } + choice = static_cast<unsigned char>(selectedIndex + 1); + printText = mItems[selectedIndex]; + + Net::getNpcHandler()->listInput(mNpcId, choice); + } + else if (mInputState == NPC_INPUT_STRING) + { + if (!Client::limitPackets(PACKET_NPC_INPUT)) + return; + printText = mTextField->getText(); + Net::getNpcHandler()->stringInput(mNpcId, printText); + } + else if (mInputState == NPC_INPUT_INTEGER) + { + if (!Client::limitPackets(PACKET_NPC_INPUT)) + return; + printText = strprintf("%d", mIntField->getValue()); + Net::getNpcHandler()->integerInput( + mNpcId, mIntField->getValue()); + } + // addText will auto remove the input layout + addText(strprintf("\n> \"%s\"\n", printText.c_str()), false); + + mNewText.clear(); + } + + if (!mLogInteraction) + setText(""); + } + else if (event.getId() == "reset") + { + if (mInputState == NPC_INPUT_STRING) + mTextField->setText(mDefaultString); + else if (mInputState == NPC_INPUT_INTEGER) + mIntField->setValue(mDefaultInt); + } + else if (event.getId() == "inc") + { + mIntField->setValue(mIntField->getValue() + 1); + } + else if (event.getId() == "dec") + { + mIntField->setValue(mIntField->getValue() - 1); + } + else if (event.getId() == "clear") + { + setText(mNewText); + } +} + +void NpcDialog::nextDialog() +{ + Net::getNpcHandler()->nextDialog(mNpcId); +} + +void NpcDialog::closeDialog() +{ + Net::getNpcHandler()->closeDialog(mNpcId); +} + +int NpcDialog::getNumberOfElements() +{ + return static_cast<int>(mItems.size()); +} + +std::string NpcDialog::getElementAt(int i) +{ + return mItems[i]; +} + +void NpcDialog::choiceRequest() +{ + mItems.clear(); + mActionState = NPC_ACTION_INPUT; + mInputState = NPC_INPUT_LIST; + buildLayout(); +} + +void NpcDialog::addChoice(const std::string &choice) +{ + mItems.push_back(choice); +} + +void NpcDialog::parseListItems(const std::string &itemString) +{ + std::istringstream iss(itemString); + + std::string tmp; + while (getline(iss, tmp, ':')) + mItems.push_back(tmp); +} + +void NpcDialog::textRequest(const std::string &defaultText) +{ + mActionState = NPC_ACTION_INPUT; + mInputState = NPC_INPUT_STRING; + mDefaultString = defaultText; + mTextField->setText(defaultText); + buildLayout(); +} + +bool NpcDialog::isTextInputFocused() const +{ + return mTextField->isFocused(); +} + +bool NpcDialog::isInputFocused() const +{ + return mTextField->isFocused() || mIntField->isFocused(); +} + +bool NpcDialog::isAnyInputFocused() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if ((*it)->isInputFocused()) + return true; + } + + return false; +} + +void NpcDialog::integerRequest(int defaultValue, int min, int max) +{ + mActionState = NPC_ACTION_INPUT; + mInputState = NPC_INPUT_INTEGER; + mDefaultInt = defaultValue; + mIntField->setRange(min, max); + mIntField->setValue(defaultValue); + buildLayout(); +} + +void NpcDialog::move(int amount) +{ + if (mActionState != NPC_ACTION_INPUT) + return; + + switch (mInputState) + { + case NPC_INPUT_INTEGER: + mIntField->setValue(mIntField->getValue() + amount); + break; + case NPC_INPUT_LIST: + mItemList->setSelected(mItemList->getSelected() - amount); + break; + case NPC_INPUT_NONE: + case NPC_INPUT_STRING: + default: + break; + } +} + +void NpcDialog::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); + + setText(mText); +} + +void NpcDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (!visible) + scheduleDelete(); +} + +void NpcDialog::optionChanged(const std::string &name) +{ + if (name == "logNpcInGui") + mLogInteraction = config.getBoolValue("logNpcInGui"); +} + +NpcDialog *NpcDialog::getActive() +{ + if (instances.size() == 1) + return instances.front(); + + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if ((*it)->isFocused()) + return (*it); + } + + return 0; +} + +void NpcDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + (*it)->close(); +} + +void NpcDialog::buildLayout() +{ + clearLayout(); + + if (mActionState != NPC_ACTION_INPUT) + { + if (mActionState == NPC_ACTION_WAIT) + mButton->setCaption(CAPTION_WAITING); + else if (mActionState == NPC_ACTION_NEXT) + mButton->setCaption(CAPTION_NEXT); + else if (mActionState == NPC_ACTION_CLOSE) + mButton->setCaption(CAPTION_CLOSE); + place(0, 0, mScrollArea, 5, 3); + place(3, 3, mClearButton); + place(4, 3, mButton); + } + else if (mInputState != NPC_INPUT_NONE) + { + if (!mLogInteraction) + setText(mNewText); + + mButton->setCaption(CAPTION_SUBMIT); + if (mInputState == NPC_INPUT_LIST) + { + place(0, 0, mScrollArea, 6, 3); + place(0, 3, mListScrollArea, 6, 3); + place(2, 6, mClearButton, 2); + place(4, 6, mButton, 2); + + mItemList->setSelected(-1); + } + else if (mInputState == NPC_INPUT_STRING) + { + place(0, 0, mScrollArea, 6, 3); + place(0, 3, mTextField, 6); + place(0, 4, mResetButton, 2); + place(2, 4, mClearButton, 2); + place(4, 4, mButton, 2); + } + else if (mInputState == NPC_INPUT_INTEGER) + { + place(0, 0, mScrollArea, 6, 3); + place(0, 3, mMinusButton, 1); + place(1, 3, mIntField, 4); + place(5, 3, mPlusButton, 1); + place(0, 4, mResetButton, 2); + place(2, 4, mClearButton, 2); + place(4, 4, mButton, 2); + } + } + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + mButton->setEnabled(mActionState != NPC_ACTION_WAIT); + + redraw(); + + mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll()); +}
\ No newline at end of file diff --git a/src/gui/npcdialog.h b/src/gui/npcdialog.h new file mode 100644 index 000000000..eee4b32e2 --- /dev/null +++ b/src/gui/npcdialog.h @@ -0,0 +1,232 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef NPCDIALOG_H +#define NPCDIALOG_H + +#include "configlistener.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/listmodel.hpp> + +#include <list> +#include <string> +#include <vector> + +class TextBox; +class ListBox; +class TextField; +class IntTextField; +class Button; + +/** + * The npc dialog. + * + * \ingroup Interface + */ +class NpcDialog : public Window, public gcn::ActionListener, + public gcn::ListModel, public ConfigListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + NpcDialog(int npcId); + + ~NpcDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Sets the text shows in the dialog. + * + * @param string The new text. + */ + void setText(const std::string &string); + + /** + * Adds the text to the text shows in the dialog. Also adds a newline + * to the end. + * + * @param string The text to add. + */ + void addText(const std::string &string, bool save = true); + + /** + * When called, the widget will show a "Next" button. + */ + void showNextButton(); + + /** + * When called, the widget will show a "Close" button and will close + * the dialog when clicked. + */ + void showCloseButton(); + + /** + * Notifies the server that client has performed a next action. + */ + void nextDialog(); + + /** + * Notifies the server that the client has performed a close action. + */ + void closeDialog(); + + /** + * Returns the number of items in the choices list. + */ + int getNumberOfElements(); + + /** + * Returns the name of item number i of the choices list. + */ + std::string getElementAt(int i); + + /** + * Makes this dialog request a choice selection from the user. + */ + void choiceRequest(); + + /** + * Adds a choice to the list box. + */ + void addChoice(const std::string &); + + /** + * Fills the options list for an NPC dialog. + * + * @param itemString A string with the options separated with colons. + */ + void parseListItems(const std::string &itemString); + + /** + * Requests a text string from the user. + */ + void textRequest(const std::string &defaultText = ""); + + bool isInputFocused() const; + + bool isTextInputFocused() const; + + static bool isAnyInputFocused(); + + /** + * Requests a interger from the user. + */ + void integerRequest(int defaultValue = 0, int min = 0, + int max = 2147483647); + + void move(int amount); + + /** + * Called when resizing the window. + * + * @param event The calling event + */ + void widgetResized(const gcn::Event &event); + + void setVisible(bool visible); + + void optionChanged(const std::string &name); + + /** + * Returns true if any instances exist. + */ + static bool isActive() { return !instances.empty(); } + + /** + * Returns the first active instance. Useful for pushing user + * interaction. + */ + static NpcDialog *getActive(); + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list<NpcDialog*> DialogList; + static DialogList instances; + + void buildLayout(); + + int mNpcId; + bool mLogInteraction; + + int mDefaultInt; + std::string mDefaultString; + + // Used for the main input area + gcn::ScrollArea *mScrollArea; + TextBox *mTextBox; + std::string mText; + std::string mNewText; + + // Used for choice input + ListBox *mItemList; + gcn::ScrollArea *mListScrollArea; + std::vector<std::string> mItems; + + // Used for string and integer input + TextField *mTextField; + IntTextField *mIntField; + Button *mPlusButton; + Button *mMinusButton; + + Button *mClearButton; + + // Used for the button + Button *mButton; + + // Will reset the text and integer input to the provided default + Button *mResetButton; + + enum NpcInputState + { + NPC_INPUT_NONE = 0, + NPC_INPUT_LIST, + NPC_INPUT_STRING, + NPC_INPUT_INTEGER + }; + + enum NpcActionState + { + NPC_ACTION_WAIT = 0, + NPC_ACTION_NEXT, + NPC_ACTION_INPUT, + NPC_ACTION_CLOSE + }; + + NpcInputState mInputState; + NpcActionState mActionState; + int mLastNextTime; +}; + +#endif // NPCDIALOG_H diff --git a/src/gui/npcpostdialog.cpp b/src/gui/npcpostdialog.cpp new file mode 100644 index 000000000..00bf55028 --- /dev/null +++ b/src/gui/npcpostdialog.cpp @@ -0,0 +1,128 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/npcpostdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/label.h" +#include "gui/widgets/textbox.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/scrollarea.h" + +#include "net/net.h" +#include "net/npchandler.h" + +#include "utils/gettext.h" + +NpcPostDialog::DialogList NpcPostDialog::instances; + +NpcPostDialog::NpcPostDialog(int npcId): + Window(_("NPC")), + mNpcId(npcId) +{ + setContentSize(400, 180); + + // create text field for receiver + gcn::Label *senderText = new Label(_("To:")); + senderText->setPosition(5, 5); + mSender = new TextField; + mSender->setPosition(senderText->getWidth() + 5, 5); + mSender->setWidth(65); + + // create button for sending + Button *sendButton = new Button(_("Send"), "send", this); + sendButton->setPosition(400 - sendButton->getWidth(), + 170 - sendButton->getHeight()); + Button *cancelButton = new Button(_("Cancel"), "cancel", this); + cancelButton->setPosition(sendButton->getX() + - (cancelButton->getWidth() + 2), sendButton->getY()); + + // create textfield for letter + mText = new TextBox; + mText->setHeight(400 - (mSender->getHeight() + sendButton->getHeight())); + mText->setEditable(true); + + // create scroll box for letter text + ScrollArea *scrollArea = new ScrollArea(mText); + scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + scrollArea->setDimension(gcn::Rectangle( + 5, mSender->getHeight() + 5, + 380, 140 - (mSender->getHeight() + sendButton->getHeight()))); + + add(senderText); + add(mSender); + add(scrollArea); + add(sendButton); + add(cancelButton); + + setLocationRelativeTo(getParent()); + + instances.push_back(this); + setVisible(true); +} + +NpcPostDialog::~NpcPostDialog() +{ + instances.remove(this); +} + +void NpcPostDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "send") + { + if (mSender->getText().empty() || mText->getText().empty()) + { + if (localChatTab) + { + localChatTab->chatLog(_("Failed to send as sender or letter " + "invalid.")); + } + } + else + { + Net::getNpcHandler()->sendLetter(mNpcId, mSender->getText(), + mText->getText()); + } + setVisible(false); + } + else if (event.getId() == "cancel") + { + setVisible(false); + } +} + +void NpcPostDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (!visible) + scheduleDelete(); +} + +void NpcPostDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + (*it)->close(); +}
\ No newline at end of file diff --git a/src/gui/npcpostdialog.h b/src/gui/npcpostdialog.h new file mode 100644 index 000000000..6cc64d1e2 --- /dev/null +++ b/src/gui/npcpostdialog.h @@ -0,0 +1,70 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_NPCPOSTDIALOG_H +#define GUI_NPCPOSTDIALOG_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +class TextBox; +class TextField; + +class NpcPostDialog : public Window, public gcn::ActionListener +{ +public: + /** + * Constructor + */ + NpcPostDialog(int npcId); + + ~NpcPostDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void setVisible(bool visible); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + +private: + typedef std::list<NpcPostDialog*> DialogList; + static DialogList instances; + + int mNpcId; + + TextBox *mText; + TextField *mSender; +}; + +#endif diff --git a/src/gui/okdialog.cpp b/src/gui/okdialog.cpp new file mode 100644 index 000000000..9812a171f --- /dev/null +++ b/src/gui/okdialog.cpp @@ -0,0 +1,81 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/okdialog.h" + +#include "gui/gui.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/textbox.h" + +#include "utils/gettext.h" + +#include <guichan/font.hpp> + +OkDialog::OkDialog(const std::string &title, const std::string &msg, + bool modal, bool showCenter, Window *parent): + Window(title, modal, parent) +{ + mTextBox = new TextBox; + mTextBox->setEditable(false); + mTextBox->setOpaque(false); + mTextBox->setTextWrapped(msg, 260); + + gcn::Button *okButton = new Button(_("OK"), "ok", this); + + const int numRows = mTextBox->getNumberOfRows(); + const int fontHeight = getFont()->getHeight(); + const int height = numRows * fontHeight; + int width = getFont()->getWidth(title); + + if (width < mTextBox->getMinWidth()) + width = mTextBox->getMinWidth(); + if (width < okButton->getWidth()) + width = okButton->getWidth(); + + setContentSize(mTextBox->getMinWidth() + fontHeight, height + + fontHeight + okButton->getHeight()); + mTextBox->setPosition(getPadding(), getPadding()); + + // 8 is the padding that GUIChan adds to button widgets + // (top and bottom combined) + okButton->setPosition((width - okButton->getWidth()) / 2, height + 8); + + add(mTextBox); + add(okButton); + + if (showCenter) + center(); + else + centerHorisontally(); + setVisible(true); + okButton->requestFocus(); +} + +void OkDialog::action(const gcn::ActionEvent &event) +{ + setActionEventId(event.getId()); + distributeActionEvent(); + + // Can we receive anything else anyway? + if (event.getId() == "ok") + scheduleDelete(); +} diff --git a/src/gui/okdialog.h b/src/gui/okdialog.h new file mode 100644 index 000000000..b7db20d79 --- /dev/null +++ b/src/gui/okdialog.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OK_DIALOG_H +#define OK_DIALOG_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +class TextBox; + +/** + * An 'Ok' button dialog. + * + * \ingroup GUI + */ +class OkDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + OkDialog(const std::string &title, const std::string &msg, + bool modal = true, bool showCenter = true, + Window *parent = NULL); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + TextBox *mTextBox; +}; + +#endif diff --git a/src/gui/outfitwindow.cpp b/src/gui/outfitwindow.cpp new file mode 100644 index 000000000..770c77572 --- /dev/null +++ b/src/gui/outfitwindow.cpp @@ -0,0 +1,914 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/outfitwindow.h" + +#include "configuration.h" +#include "equipment.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "log.h" +#include "playerinfo.h" + +#include "gui/chat.h" +#include "gui/viewport.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <vector> + +float OutfitWindow::mAlpha = 1.0; + +OutfitWindow::OutfitWindow(): + Window(_("Outfits")), + mBoxWidth(33), + mBoxHeight(33), + mGridWidth(4), + mGridHeight(3), + mItemClicked(false), + mItemMoved(NULL), + mItemSelected(-1), + mCurrentOutfit(0), + mAwayOutfit(0) +{ + setWindowName("Outfits"); + setResizable(true); + setCloseButton(true); + setDefaultSize(250, 400, 150, 230); + setMinWidth(145); + setMinHeight(220); + + addMouseListener(this); + + mPreviousButton = new Button(_("<"), "previous", this); + mNextButton = new Button(_(">"), "next", this); + mCurrentLabel = new Label(strprintf(_("Outfit: %d"), 1)); + mCurrentLabel->setAlignment(gcn::Graphics::CENTER); + mKeyLabel = new Label(strprintf(_("Key: %s"), + keyName(mCurrentOutfit).c_str())); + mKeyLabel->setAlignment(gcn::Graphics::CENTER); + mUnequipCheck = new CheckBox(_("Unequip first"), + serverConfig.getValueBool("OutfitUnequip0", true)); + + mAwayOutfitCheck = new CheckBox(_("Away outfit"), + serverConfig.getValue("OutfitAwayIndex", OUTFITS_COUNT - 1)); + + mUnequipCheck->setActionEventId("unequip"); + mUnequipCheck->addActionListener(this); + + mAwayOutfitCheck->setActionEventId("away"); + mAwayOutfitCheck->addActionListener(this); + + place(0, 3, mKeyLabel, 4); + place(0, 4, mPreviousButton, 1); + place(1, 4, mCurrentLabel, 2); + place(3, 4, mNextButton, 1); + place(0, 5, mUnequipCheck, 4); + place(0, 6, mAwayOutfitCheck, 4); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + layout.setColWidth(4, Layout::CENTER); + + loadWindowState(); + + load(); +} + +OutfitWindow::~OutfitWindow() +{ + save(); +} + +void OutfitWindow::load(bool oldConfig) +{ + Configuration *cfg; + if (oldConfig) + cfg = &config; + else + cfg = &serverConfig; + + memset(mItems, -1, sizeof(mItems)); + + for (int o = 0; o < OUTFITS_COUNT; o++) + { + std::string outfit = cfg->getValue("Outfit" + toString(o), "-1"); + std::string buf; + std::stringstream ss(outfit); + + std::vector<int> tokens; + + while (ss >> buf) + tokens.push_back(atoi(buf.c_str())); + + for (int i = 0; i < static_cast<int>(tokens.size()) + && i < OUTFIT_ITEM_COUNT; i++) + { + mItems[o][i] = tokens[i]; + } + + mItemsUnequip[o] = cfg->getValueBool("OutfitUnequip" + toString(o), + true); + } + mAwayOutfit = cfg->getValue("OutfitAwayIndex", OUTFITS_COUNT - 1); + if (mAwayOutfit >= OUTFITS_COUNT) + mAwayOutfit = OUTFITS_COUNT - 1; + + if (mAwayOutfitCheck) + mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit); +} + +void OutfitWindow::save() +{ + std::string outfitStr; + for (int o = 0; o < OUTFITS_COUNT; o++) + { + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + outfitStr += mItems[o][i] ? toString(mItems[o][i]) : toString(-1); + if (i < OUTFIT_ITEM_COUNT - 1) + outfitStr += " "; + } + serverConfig.setValue("Outfit" + toString(o), outfitStr); + serverConfig.setValue("OutfitUnequip" + toString(o), mItemsUnequip[o]); + outfitStr = ""; + } + serverConfig.setValue("OutfitAwayIndex", mAwayOutfit); +} + +void OutfitWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "next") + { + next(); + } + else if (event.getId() == "previous") + { + previous(); + } + else if (event.getId() == "unequip") + { + if (mCurrentOutfit < OUTFITS_COUNT) + mItemsUnequip[mCurrentOutfit] = mUnequipCheck->isSelected(); + } + else if (event.getId() == "away") + { + mAwayOutfit = mCurrentOutfit; + if (!mAwayOutfitCheck->isSelected()) + mAwayOutfitCheck->setSelected(true); + } +} + +void OutfitWindow::wearOutfit(int outfit, bool unwearEmpty, bool select) +{ + bool isEmpty = true; + + Item *item; + if (outfit < 0 || outfit > OUTFITS_COUNT) + return; + + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + item = PlayerInfo::getInventory()->findItem(mItems[outfit][i]); + if (item && !item->isEquipped() && item->getQuantity()) + { + if (item->isEquipment()) + { + Net::getInventoryHandler()->equipItem(item); + isEmpty = false; + } + } + } + + if ((!isEmpty || unwearEmpty) && outfit < OUTFITS_COUNT + && mItemsUnequip[outfit]) + { + unequipNotInOutfit(outfit); + } + if (select) + { + mCurrentOutfit = outfit; + showCurrentOutfit(); + } +} + +void OutfitWindow::copyOutfit(int outfit) +{ + copyOutfit(outfit, mCurrentOutfit); +} + +void OutfitWindow::copyOutfit(int src, int dst) +{ + if (src < 0 || src > OUTFITS_COUNT + || dst < 0 || dst > OUTFITS_COUNT) + { + return; + } + + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + mItems[dst][i] = mItems[src][i]; + } +} + +void OutfitWindow::draw(gcn::Graphics *graphics) +{ + Window::draw(graphics); + Graphics *g = static_cast<Graphics*>(graphics); + + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + const int itemX = 10 + ((i % mGridWidth) * mBoxWidth); + const int itemY = 25 + ((i / mGridWidth) * mBoxHeight); + + graphics->setColor(gcn::Color(0, 0, 0, 64)); + graphics->drawRectangle(gcn::Rectangle(itemX, itemY, 32, 32)); + graphics->setColor(gcn::Color(255, 255, 255, 32)); + graphics->fillRectangle(gcn::Rectangle(itemX, itemY, 32, 32)); + + if (mItems[mCurrentOutfit][i] < 0) + continue; + + bool foundItem = false; + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(mItems[mCurrentOutfit][i]); + if (item) + { + // Draw item icon. + Image* image = item->getImage(); + if (image) + { + g->drawImage(image, itemX, itemY); + foundItem = true; + } + } + } + if (!foundItem) + { + Image *image = Item::getImage(mItems[mCurrentOutfit][i]); + if (image) + g->drawImage(image, itemX, itemY); + } + } + if (mItemMoved) + { + // Draw the item image being dragged by the cursor. + Image* image = mItemMoved->getImage(); + if (image) + { + const int tPosX = mCursorPosX - (image->getWidth() / 2); + const int tPosY = mCursorPosY - (image->getHeight() / 2); + + g->drawImage(image, tPosX, tPosY); + } + } +} + + +void OutfitWindow::mouseDragged(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mItemMoved && mItemClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + Window::mouseDragged(event); + return; + } + const int itemId = mItems[mCurrentOutfit][index]; + if (itemId < 0) + { + Window::mouseDragged(event); + return; + } + mMoved = false; + event.consume(); + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(itemId); + if (item) + mItemMoved = item; + else + mItemMoved = 0; + mItems[mCurrentOutfit][index] = -1; + } + } + if (mItemMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } + Window::mouseDragged(event); +} + +void OutfitWindow::mousePressed(gcn::MouseEvent &event) +{ + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + if (event.getButton() == gcn::MouseEvent::RIGHT && viewport) + viewport->showOutfitsPopup(); + Window::mousePressed(event); + return; + } + mMoved = false; + event.consume(); + + // Stores the selected item if there is one. + if (isItemSelected()) + { + mItems[mCurrentOutfit][index] = mItemSelected; + mItemSelected = -1; + } + else if (mItems[mCurrentOutfit][index]) + { + mItemClicked = true; + } + Window::mousePressed(event); +} + +void OutfitWindow::mouseReleased(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (isItemSelected()) + mItemSelected = -1; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + mItemMoved = NULL; + Window::mouseReleased(event); + return; + } + mMoved = false; + event.consume(); + if (mItemMoved) + { + mItems[mCurrentOutfit][index] = mItemMoved->getId(); + mItemMoved = NULL; + } + if (mItemClicked) + mItemClicked = false; + } + Window::mouseReleased(event); +} + +int OutfitWindow::getIndexFromGrid(int pointX, int pointY) const +{ + const gcn::Rectangle tRect = gcn::Rectangle( + 10, 25, mGridWidth * mBoxWidth, mGridHeight * mBoxHeight); + if (!tRect.isPointInRect(pointX, pointY)) + return -1; + const int index = (((pointY - 25) / mBoxHeight) * mGridWidth) + + (pointX - 10) / mBoxWidth; + if (index >= OUTFIT_ITEM_COUNT || index < 0) + return -1; + return index; +} + +void OutfitWindow::unequipNotInOutfit(int outfit) +{ + // here we think that outfit is correct index + + Inventory *inventory = PlayerInfo::getInventory(); + if (!inventory) + return; + + for (unsigned i = 0; i < inventory->getSize(); i++) + { + if (inventory->getItem(i) && inventory->getItem(i)->isEquipped()) + { + bool found = false; + for (unsigned f = 0; f < OUTFIT_ITEM_COUNT; f++) + { + if (inventory->getItem(i)->getId() == mItems[outfit][f]) + { + found = true; + break; + } + } + if (!found) + Net::getInventoryHandler()->unequipItem(inventory->getItem(i)); + } + } +} + +int OutfitWindow::keyToNumber(SDLKey key) +{ + int outfitNum = -1; + switch (key) + { + case SDLK_1: + case SDLK_2: + case SDLK_3: + case SDLK_4: + case SDLK_5: + case SDLK_6: + case SDLK_7: + case SDLK_8: + case SDLK_9: + outfitNum = key - SDLK_1; + break; + + case SDLK_0: + outfitNum = 9; + break; + + case SDLK_MINUS: + outfitNum = 10; + break; + + case SDLK_EQUALS: + outfitNum = 11; + break; + + case SDLK_BACKSPACE: + outfitNum = 12; + break; + + case SDLK_INSERT: + outfitNum = 13; + break; + + case SDLK_HOME: + outfitNum = 14; + break; + + case SDLK_q: + outfitNum = 15; + break; + + case SDLK_w: + outfitNum = 16; + break; + + case SDLK_e: + outfitNum = 17; + break; + + case SDLK_r: + outfitNum = 18; + break; + + case SDLK_t: + outfitNum = 19; + break; + + case SDLK_y: + outfitNum = 20; + break; + + case SDLK_u: + outfitNum = 21; + break; + + case SDLK_i: + outfitNum = 22; + break; + + case SDLK_o: + outfitNum = 23; + break; + + case SDLK_p: + outfitNum = 24; + break; + + case SDLK_LEFTBRACKET: + outfitNum = 25; + break; + + case SDLK_RIGHTBRACKET: + outfitNum = 26; + break; + + case SDLK_BACKSLASH: + outfitNum = 27; + break; + + case SDLK_a: + outfitNum = 28; + break; + + case SDLK_s: + outfitNum = 29; + break; + + case SDLK_d: + outfitNum = 30; + break; + + case SDLK_f: + outfitNum = 31; + break; + + case SDLK_g: + outfitNum = 32; + break; + + case SDLK_h: + outfitNum = 33; + break; + + case SDLK_j: + outfitNum = 34; + break; + + case SDLK_k: + outfitNum = 35; + break; + + case SDLK_l: + outfitNum = 36; + break; + + case SDLK_SEMICOLON: + outfitNum = 37; + break; + + case SDLK_QUOTE: + outfitNum = 38; + break; + + case SDLK_z: + outfitNum = 39; + break; + + + case SDLK_x: + outfitNum = 40; + break; + + case SDLK_c: + outfitNum = 41; + break; + + case SDLK_v: + outfitNum = 42; + break; + + case SDLK_b: + outfitNum = 43; + break; + + case SDLK_n: + outfitNum = 44; + break; + + case SDLK_m: + outfitNum = 45; + break; + + case SDLK_COMMA: + outfitNum = 46; + break; + + case SDLK_PERIOD: + outfitNum = 47; + break; + + case SDLK_SLASH: + outfitNum = 48; + break; + + default: + break; + } + + return outfitNum; +} + +SDLKey OutfitWindow::numberToKey(int number) +{ + SDLKey key = SDLK_UNKNOWN; + switch (number) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + key = static_cast<SDLKey>( + static_cast<unsigned int>(SDLK_1) + number); + break; + + case 9: + key = SDLK_0; + break; + + case 10: + key = SDLK_MINUS; + break; + + case 11: + key = SDLK_EQUALS; + break; + + case 12: + key = SDLK_BACKSPACE; + break; + + case 13: + key = SDLK_INSERT; + break; + + case 14: + key = SDLK_HOME; + break; + + case 15: + key = SDLK_q; + break; + + case 16: + key = SDLK_w; + break; + + case 17: + key = SDLK_e; + break; + + case 18: + key = SDLK_r; + break; + + case 19: + key = SDLK_t; + break; + + case 20: + key = SDLK_y; + break; + + case 21: + key = SDLK_u; + break; + + case 22: + key = SDLK_i; + break; + + case 23: + key = SDLK_o; + break; + + case 24: + key = SDLK_p; + break; + + case 25: + key = SDLK_LEFTBRACKET; + break; + + case 26: + key = SDLK_RIGHTBRACKET; + break; + + case 27: + key = SDLK_BACKSLASH; + break; + + case 28: + key = SDLK_a; + break; + + case 29: + key = SDLK_s; + break; + + case 30: + key = SDLK_d; + break; + + case 31: + key = SDLK_f; + break; + + case 32: + key = SDLK_g; + break; + + case 33: + key = SDLK_h; + break; + + case 34: + key = SDLK_j; + break; + + case 35: + key = SDLK_k; + break; + + case 36: + key = SDLK_l; + break; + + case 37: + key = SDLK_SEMICOLON; + break; + + case 38: + key = SDLK_QUOTE; + break; + + case 39: + key = SDLK_z; + break; + + + case 40: + key = SDLK_x; + break; + + case 41: + key = SDLK_c; + break; + + case 42: + key = SDLK_v; + break; + + case 43: + key = SDLK_b; + break; + + case 44: + key = SDLK_n; + break; + + case 45: + key = SDLK_m; + break; + + case 46: + key = SDLK_COMMA; + break; + + case 47: + key = SDLK_PERIOD; + break; + + case 48: + key = SDLK_SLASH; + break; + + default: + break; + } + + return key; +} + +std::string OutfitWindow::keyName(int number) +{ + return SDL_GetKeyName(numberToKey(number)); +} + +void OutfitWindow::next() +{ + if (mCurrentOutfit < (OUTFITS_COUNT - 1)) + mCurrentOutfit++; + else + mCurrentOutfit = 0; + showCurrentOutfit(); +} + +void OutfitWindow::previous() +{ + if (mCurrentOutfit > 0) + mCurrentOutfit--; + else + mCurrentOutfit = OUTFITS_COUNT - 1; + showCurrentOutfit(); +} + +void OutfitWindow::showCurrentOutfit() +{ + mCurrentLabel->setCaption(strprintf(_("Outfit: %d"), mCurrentOutfit + 1)); + mUnequipCheck->setSelected(mItemsUnequip[mCurrentOutfit]); + mKeyLabel->setCaption(strprintf(_("Key: %s"), + keyName(mCurrentOutfit).c_str())); + mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit); +} + +void OutfitWindow::wearNextOutfit(bool all) +{ + bool fromStart = false; + next(); + if (!all && mCurrentOutfit < OUTFITS_COUNT) + { + while (!mItemsUnequip[mCurrentOutfit]) + { + next(); + if (mCurrentOutfit == 0) + { + if (!fromStart) + fromStart = true; + else + return; + } + } + } + wearOutfit(mCurrentOutfit); +} + +void OutfitWindow::wearPreviousOutfit(bool all) +{ + bool fromStart = false; + previous(); + if (!all && mCurrentOutfit < OUTFITS_COUNT) + { + while (!mItemsUnequip[mCurrentOutfit]) + { + previous(); + if (mCurrentOutfit == 0) + { + if (!fromStart) + fromStart = true; + else + return; + } + } + } + wearOutfit(mCurrentOutfit); +} + +void OutfitWindow::copyFromEquiped() +{ + copyFromEquiped(mCurrentOutfit); +} + +void OutfitWindow::copyFromEquiped(int dst) +{ + Inventory *inventory = PlayerInfo::getInventory(); + if (!inventory) + return; + + int outfitCell = 0; + + for (unsigned i = 0; i < inventory->getSize(); i++) + { + if (inventory->getItem(i) && inventory->getItem(i)->isEquipped()) + { + mItems[dst][outfitCell++] = inventory->getItem(i)->getId(); + if (outfitCell > 8) + break; + } + } +} + +void OutfitWindow::wearAwayOutfit() +{ + copyFromEquiped(OUTFITS_COUNT); + wearOutfit(mAwayOutfit, false); +} + +void OutfitWindow::unwearAwayOutfit() +{ + wearOutfit(OUTFITS_COUNT); +} diff --git a/src/gui/outfitwindow.h b/src/gui/outfitwindow.h new file mode 100644 index 000000000..684d1c6f1 --- /dev/null +++ b/src/gui/outfitwindow.h @@ -0,0 +1,135 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OUTFITWINDOW_H +#define OUTFITWINDOW_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/mouselistener.hpp> + +#define OUTFITS_COUNT 100 +#define OUTFIT_ITEM_COUNT 12 + +class Button; +class CheckBox; +class Item; +class Label; + +class OutfitWindow : public Window, gcn::ActionListener +{ + public: + /** + * Constructor. + */ + OutfitWindow(); + + /** + * Destructor. + */ + ~OutfitWindow(); + + void action(const gcn::ActionEvent &event); + + void draw(gcn::Graphics *graphics); + + void mousePressed(gcn::MouseEvent &event); + + void mouseDragged(gcn::MouseEvent &event); + + void mouseReleased(gcn::MouseEvent &event); + + void load(bool oldConfig = false); + + void setItemSelected(int itemId) + { mItemSelected = itemId; } + + bool isItemSelected() + { return mItemSelected > 0; } + + void wearOutfit(int outfit, bool unwearEmpty = true, + bool select = false); + + void copyOutfit(int outfit); + + void copyOutfit(int src, int dst); + + void copyFromEquiped(); + + void copyFromEquiped(int dst); + + void unequipNotInOutfit(int outfit); + + int keyToNumber(SDLKey key); + + SDLKey numberToKey(int number); + + void next(); + + void previous(); + + void wearNextOutfit(bool all = false); + + void wearPreviousOutfit(bool all = false); + + void wearAwayOutfit(); + + void unwearAwayOutfit(); + + void showCurrentOutfit(); + + std::string keyName(int number); + + private: + Button *mPreviousButton; + Button *mNextButton; + Label *mCurrentLabel; + CheckBox *mUnequipCheck; + CheckBox *mAwayOutfitCheck; + Label *mKeyLabel; + + int getIndexFromGrid(int pointX, int pointY) const; + + int mBoxWidth; + int mBoxHeight; + int mCursorPosX, mCursorPosY; + int mGridWidth, mGridHeight; + bool mItemClicked; + Item *mItemMoved; + + void save(); + + int mItems[OUTFITS_COUNT + 1][OUTFIT_ITEM_COUNT]; + bool mItemsUnequip[OUTFITS_COUNT]; + int mItemSelected; + + int mCurrentOutfit; + int mAwayOutfit; + + Image *mBackgroundImg; + + static float mAlpha; +}; + +extern OutfitWindow *outfitWindow; + +#endif diff --git a/src/gui/palette.cpp b/src/gui/palette.cpp new file mode 100644 index 000000000..72c490912 --- /dev/null +++ b/src/gui/palette.cpp @@ -0,0 +1,274 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net> + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "palette.h" + +#include "configuration.h" +#include "client.h" + +#include "gui/gui.h" + +#include "utils/gettext.h" +#include "utils/mathutils.h" +#include "utils/stringutils.h" + +#include <math.h> + +const gcn::Color Palette::BLACK = gcn::Color(0, 0, 0); +Palette::Palettes Palette::mInstances; + +const gcn::Color Palette::RAINBOW_COLORS[7] = { + gcn::Color(255, 0, 0), + gcn::Color(255, 153, 0), + gcn::Color(255, 255, 0), + gcn::Color(0, 153, 0), + gcn::Color(0, 204, 204), + gcn::Color(51, 0, 153), + gcn::Color(153, 0, 153) +}; +/** Number of Elemets of RAINBOW_COLORS */ +const int Palette::RAINBOW_COLOR_COUNT = 7; + +Palette::Palette(int size) : + mRainbowTime(tick_time), + mColors(Colors(size)) +{ + mInstances.insert(this); +} + +Palette::~Palette() +{ + mInstances.erase(this); +} + +const gcn::Color& Palette::getColor(char c, bool &valid) +{ + for (Colors::const_iterator col = mColors.begin(), + colEnd = mColors.end(); col != colEnd; ++col) + { + if (col->ch == c) + { + valid = true; + return col->color; + } + } + valid = false; + return BLACK; +} + +void Palette::advanceGradients() +{ + Palettes::iterator it = mInstances.begin(); + Palettes::iterator it_end = mInstances.end(); + + for (; it != it_end; it++) + (*it)->advanceGradient(); +} + +void Palette::advanceGradient() +{ + if (get_elapsed_time(mRainbowTime) > 5) + { + int pos, colIndex, colVal, delay, numOfColors; + // For slower systems, advance can be greater than one (advance > 1 + // skips advance-1 steps). Should make gradient look the same + // independent of the framerate. + int advance = get_elapsed_time(mRainbowTime) / 5; + double startColVal, destColVal; + + for (size_t i = 0; i < mGradVector.size(); i++) + { + if (!mGradVector[i]) + continue; + + delay = mGradVector[i]->delay; + + if (mGradVector[i]->grad == PULSE) + delay = delay / 20; + + numOfColors = (mGradVector[i]->grad == SPECTRUM ? 6 : + mGradVector[i]->grad == PULSE ? 127 : + RAINBOW_COLOR_COUNT); + + mGradVector[i]->gradientIndex = + (mGradVector[i]->gradientIndex + advance) % + (delay * numOfColors); + + pos = mGradVector[i]->gradientIndex % delay; + if (delay) + colIndex = mGradVector[i]->gradientIndex / delay; + else + colIndex = mGradVector[i]->gradientIndex; + + if (mGradVector[i]->grad == PULSE) + { + colVal = static_cast<int>(255.0 * + sin(M_PI * colIndex / numOfColors)); + + const gcn::Color &col = mGradVector[i]->testColor; + + mGradVector[i]->color.r = + ((colVal * col.r) / 255) % (col.r + 1); + mGradVector[i]->color.g = + ((colVal * col.g) / 255) % (col.g + 1); + mGradVector[i]->color.b = + ((colVal * col.b) / 255) % (col.b + 1); + } + if (mGradVector[i]->grad == SPECTRUM) + { + if (colIndex % 2) + { // falling curve + if (delay) + { + colVal = static_cast<int>(255.0 * + (cos(M_PI * pos / delay) + 1) / 2); + } + else + { + colVal = static_cast<int>(255.0 * + (cos(M_PI * pos) + 1) / 2); + } + } + else + { // ascending curve + if (delay) + { + colVal = static_cast<int>(255.0 * (cos(M_PI * + (delay - pos) / delay) + 1) / 2); + } + else + { + colVal = static_cast<int>(255.0 * (cos(M_PI * + (delay - pos)) + 1) / 2); + } + } + + mGradVector[i]->color.r = + (colIndex == 0 || colIndex == 5) ? 255 : + (colIndex == 1 || colIndex == 4) ? colVal : 0; + mGradVector[i]->color.g = + (colIndex == 1 || colIndex == 2) ? 255 : + (colIndex == 0 || colIndex == 3) ? colVal : 0; + mGradVector[i]->color.b = + (colIndex == 3 || colIndex == 4) ? 255 : + (colIndex == 2 || colIndex == 5) ? colVal : 0; + } + else if (mGradVector[i]->grad == RAINBOW) + { + const gcn::Color &startCol = RAINBOW_COLORS[colIndex]; + const gcn::Color &destCol = + RAINBOW_COLORS[(colIndex + 1) % numOfColors]; + + if (delay) + startColVal = (cos(M_PI * pos / delay) + 1) / 2; + else + startColVal = 0; + + destColVal = 1 - startColVal; + + mGradVector[i]->color.r = static_cast<int>(startColVal + * startCol.r + destColVal * destCol.r); + + mGradVector[i]->color.g = static_cast<int>(startColVal + * startCol.g + destColVal * destCol.g); + + mGradVector[i]->color.b = static_cast<int>(startColVal + * startCol.b + destColVal * destCol.b); + } + } + + if (advance) + mRainbowTime = tick_time; + } +} + +/* +gcn::Color Palette::produceHPColor(int hp, int maxHp, int alpha) +{ + float r1 = 255; + float g1 = 255; + float b1 = 255; + float r2 = 255; + float g2 = 255; + float b2 = 255; + + float weight = 1.0f; + + int thresholdLevel = maxHp / 4; + int thresholdProgress = hp % thresholdLevel; + + if (thresholdLevel) + weight = 1 - ((float)thresholdProgress) / ((float)thresholdLevel); + else + weight = 0; + + if (hp < (thresholdLevel)) + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_ONE_HALF); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_ONE_QUARTER); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + else if (hp < (thresholdLevel*2)) + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_THREE_QUARTERS); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_ONE_HALF); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + else if (hp < thresholdLevel*3) + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_FULL); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_THREE_QUARTERS); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + else + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_FULL); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_FULL); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + + // Safety checks + if (weight > 1.0f) weight = 1.0f; + if (weight < 0.0f) weight = 0.0f; + + // Do the color blend + r1 = (int) weightedAverage(r1, r2,weight); + g1 = (int) weightedAverage(g1, g2, weight); + b1 = (int) weightedAverage(b1, b2, weight); + + // More safety checks + if (r1 > 255) r1 = 255; + if (g1 > 255) g1 = 255; + if (b1 > 255) b1 = 255; + + return gcn::Color(r1, g1, b1, alpha); +} +*/ + diff --git a/src/gui/palette.h b/src/gui/palette.h new file mode 100644 index 000000000..0c8af1d7f --- /dev/null +++ b/src/gui/palette.h @@ -0,0 +1,191 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net> + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PALETTE_H +#define PALETTE_H + +#include "log.h" +#include "utils/stringutils.h" + +#include <guichan/color.hpp> + +#include <cstdlib> +#include <string> +#include <set> +#include <vector> + +// Default Gradient Delay +#define GRADIENT_DELAY 40 + +/** + * Class controlling the game's color palette. + */ +class Palette +{ + public: + /** Black Color Constant */ + static const gcn::Color BLACK; + + /** Colors can be static or can alter over time. */ + enum GradientType + { + STATIC = 0, + PULSE, + SPECTRUM, + RAINBOW + }; + + /** + * Returns the color associated with a character, if it exists. Returns + * Palette::BLACK if the character is not found. + * + * @param c character requested + * @param valid indicate whether character is known + * + * @return the requested color or Palette::BLACK + */ + const gcn::Color &getColor(char c, bool &valid); + + /** + * Gets the color associated with the type. Sets the alpha channel + * before returning. + * + * @param type the color type requested + * @param alpha alpha channel to use + * + * @return the requested color + */ + inline const gcn::Color &getColor(int type, int alpha = 255) + { + gcn::Color* col = &mColors[type].color; + col->a = alpha; + return *col; + } + + inline const gcn::Color &getColorWithAlpha(int type) + { + gcn::Color* col = &mColors[type].color; + col->a = mColors[type].delay; + return *col; + } + + /** + * Gets the GradientType associated with the specified type. + * + * @param type the color type of the color + * + * @return the gradient type of the color with the given index + */ + inline GradientType getGradientType(int type) const + { + return mColors[type].grad; + } + + /** + * Get the character used by the specified color. + * + * @param type the color type of the color + * + * @return the color char of the color with the given index + */ + inline char getColorChar(int type) const + { + return mColors[type].ch; + } + + /** + * Gets the gradient delay for the specified type. + * + * @param type the color type of the color + * + * @return the gradient delay of the color with the given index + */ + inline int getGradientDelay(int type) const + { return mColors[type].delay; } + + /** + * Updates all colors, that are non-static. + */ + static void advanceGradients(); + + gcn::Color static produceHPColor(int hp, int maxHp, int alpha = 255); + + protected: + /** Colors used for the rainbow gradient */ + static const gcn::Color RAINBOW_COLORS[]; + static const int RAINBOW_COLOR_COUNT; + + /** Time tick, that gradient-type colors were updated the last time. */ + int mRainbowTime; + + typedef std::set<Palette*> Palettes; + static Palettes mInstances; + + /** + * Constructor + */ + Palette(int size); + + /** + * Destructor + */ + ~Palette(); + + void advanceGradient(); + + struct ColorElem + { + int type; + gcn::Color color; + gcn::Color testColor; + gcn::Color committedColor; + std::string text; + char ch; + GradientType grad; + GradientType committedGrad; + int gradientIndex; + int delay; + int committedDelay; + + void set(int type, gcn::Color& color, GradientType grad, int delay) + { + ColorElem::type = type; + ColorElem::color = color; + ColorElem::testColor = color; + ColorElem::grad = grad; + ColorElem::delay = delay; + ColorElem::gradientIndex = rand(); + } + + inline int getRGB() const + { + return (committedColor.r << 16) | (committedColor.g << 8) | + committedColor.b; + } + }; + typedef std::vector<ColorElem> Colors; + /** Vector containing the colors. */ + Colors mColors; + std::vector<ColorElem*> mGradVector; +}; + +#endif diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp new file mode 100644 index 000000000..662b184b9 --- /dev/null +++ b/src/gui/popupmenu.cpp @@ -0,0 +1,1286 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/popupmenu.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "dropshortcut.h" +#include "guild.h" +#include "flooritem.h" +#include "graphics.h" +#include "item.h" +#include "itemshortcut.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" +#include "party.h" +#include "playerinfo.h" +#include "playerrelations.h" +#include "spellmanager.h" + +#include "gui/buy.h" +#include "gui/chat.h" +#include "gui/inventorywindow.h" +#include "gui/itemamount.h" +#include "gui/outfitwindow.h" +#include "gui/sell.h" +#include "gui/socialwindow.h" +#include "gui/textcommandeditor.h" +#include "gui/textdialog.h" +#include "gui/trade.h" +#include "gui/viewport.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/whispertab.h" + +#include "net/adminhandler.h" +#include "net/beinghandler.h" +#include "net/buysellhandler.h" +#include "net/guildhandler.h" +#include "net/inventoryhandler.h" +#include "net/net.h" +#include "net/npchandler.h" +#include "net/partyhandler.h" +#include "gui/shortcutwindow.h" +#include "net/tradehandler.h" + +#include "resources/itemdb.h" +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <cassert> + +std::string tradePartnerName(""); + +PopupMenu::PopupMenu(): + Popup("PopupMenu"), + mBeingId(0), + mFloorItem(0), + mItem(0), + mItemId(0), + mMapItem(0), + mTab(0), + mSpell(0), + mDialog(0), + mNick("") +{ + mBrowserBox = new BrowserBox; + mBrowserBox->setPosition(4, 4); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mBrowserBox->setOpaque(false); + mBrowserBox->setLinkHandler(this); + add(mBrowserBox); +} + +void PopupMenu::showPopup(int x, int y, Being *being) +{ + if (!being || !player_node) + return; + + mBeingId = being->getId(); + mNick = being->getName(); + mBrowserBox->clearRows(); + + const std::string &name = mNick; + + mBrowserBox->addRow(name); + + switch (being->getType()) + { + case ActorSprite::PLAYER: + { + // Players can be traded with. + mBrowserBox->addRow(_("@@trade|Trade@@")); + // TRANSLATORS: Attacking a player. + mBrowserBox->addRow(_("@@attack|Attack@@")); + // TRANSLATORS: Whispering a player. + mBrowserBox->addRow(_("@@whisper|Whisper@@")); + + mBrowserBox->addRow("##3---"); + + mBrowserBox->addRow(_("@@heal|Heal@@")); + mBrowserBox->addRow("##3---"); + + switch (player_relations.getRelation(name)) + { + case PlayerRelation::NEUTRAL: + mBrowserBox->addRow(_("@@friend|Befriend@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::FRIEND: + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::DISREGARDED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::IGNORED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::ERASED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + break; + + default: + break; + } + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@follow|Follow@@")); + mBrowserBox->addRow(_("@@imitation|Imitation@@")); + + if (player_node->isInParty()) + { + if (player_node->getParty()) + { + if (player_node->getParty()->getName() + != being->getPartyName()) + { + mBrowserBox->addRow( + _("@@party|Invite to party@@")); + } + else + { + mBrowserBox->addRow( + _("@@kick party|Kick from party@@")); + } + mBrowserBox->addRow("##3---"); + } + } + + Guild *guild1 = being->getGuild(); + Guild *guild2 = player_node->getGuild(); + if (guild2) + { + if (guild1) + { + if (guild1->getId() == guild2->getId()) + { + mBrowserBox->addRow( + _("@@guild-kick|Kick from guild@@")); + mBrowserBox->addRow( + _("@@guild-pos|Change pos in guild >@@")); + } + } + else + { + mBrowserBox->addRow(_("@@guild|Invite to guild@@")); + } + } + + if (player_node->isGM()) + { + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@admin-kick|Kick player@@")); + } + mBrowserBox->addRow(_("@@nuke|Nuke@@")); + mBrowserBox->addRow(_("@@move|Move@@")); + mBrowserBox->addRow(_("@@undress|Undress@@")); + + if (player_relations.getDefault() & PlayerRelation::TRADE) + { + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + } + } + break; + + case ActorSprite::NPC: + // NPCs can be talked to (single option, candidate for removal + // unless more options would be added) + mBrowserBox->addRow(_("@@talk|Talk@@")); + + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@move|Move@@")); + break; + + case ActorSprite::MONSTER: + { + // Monsters can be attacked + mBrowserBox->addRow(_("@@attack|Attack@@")); + + if (player_node->isGM()) + mBrowserBox->addRow(_("@@admin-kick|Kick@@")); + } + break; + + default: + /* Other beings aren't interesting... */ + return; + } + mBrowserBox->addRow(_("@@name|Add name to chat@@")); + + //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showPopup(int x, int y, std::list<Being*> &beings) +{ + mBrowserBox->clearRows(); + mBrowserBox->addRow("Players"); + std::list<Being*>::iterator it, it_end; + for (it = beings.begin(), it_end = beings.end(); it != it_end; it++) + { + Being *being = *it; + if (!being->getName().empty()) + { + mBrowserBox->addRow(strprintf(_("@@player_%u|%s >@@"), + being->getId(), being->getName().c_str())); + } + } + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + showPopup(x, y); +} + +void PopupMenu::showPlayerPopup(int x, int y, std::string nick) +{ + if (nick.empty() || !player_node) + return; + + mNick = nick; + mBeingId = 0; + mBrowserBox->clearRows(); + + const std::string &name = mNick; + + mBrowserBox->addRow(name); + + mBrowserBox->addRow(_("@@whisper|Whisper@@")); + mBrowserBox->addRow("##3---"); + + switch (player_relations.getRelation(name)) + { + case PlayerRelation::NEUTRAL: + mBrowserBox->addRow(_("@@friend|Befriend@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::FRIEND: + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::DISREGARDED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::IGNORED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::ERASED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + break; + + default: + break; + } + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@follow|Follow@@")); + mBrowserBox->addRow(_("@@imitation|Imitation@@")); + + Guild *guild2 = player_node->getGuild(); + if (guild2) + { + if (guild2->getMember(mNick)) + { + mBrowserBox->addRow(_("@@guild-kick|Kick from guild@@")); + mBrowserBox->addRow(_("@@guild-pos|Change pos in guild >@@")); + } + else + { + mBrowserBox->addRow(_("@@guild|Invite to guild@@")); + } + } + + mBrowserBox->addRow("##3---"); + if (player_relations.getDefault() & PlayerRelation::TRADE) + { + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + } + + mBrowserBox->addRow(_("@@name|Add name to chat@@")); + + //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); + +} + +void PopupMenu::showPopup(int x, int y, FloorItem *floorItem) +{ + if (!floorItem) + return; + + mFloorItem = floorItem; + ItemInfo info = floorItem->getInfo(); + mBrowserBox->clearRows(); + + // Floor item can be picked up (single option, candidate for removal) + std::string name = info.getName(); + mBrowserBox->addRow(name); + mBrowserBox->addRow(_("@@pickup|Pick up@@")); + mBrowserBox->addRow(_("@@chat|Add to chat@@")); + + //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showPopup(int x, int y, MapItem *mapItem) +{ + if (!mapItem) + return; + + mMapItem = mapItem; + + mBrowserBox->clearRows(); + + mBrowserBox->addRow(_("Map Item")); + mBrowserBox->addRow(_("@@rename map|Rename@@")); + mBrowserBox->addRow(_("@@remove map|Remove@@")); + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showOutfitsPopup(int x, int y) +{ + mBrowserBox->clearRows(); + + mBrowserBox->addRow(_("Outfits")); + mBrowserBox->addRow(_("@@load old outfits|Load old outfits@@")); + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showSpellPopup(int x, int y, TextCommand *cmd) +{ + if (!cmd) + return; + + mBrowserBox->clearRows(); + + mSpell = cmd; + mBrowserBox->addRow(_("Spells")); + mBrowserBox->addRow(_("@@load old spells|Load old spells@@")); + mBrowserBox->addRow(_("@@edit spell|Edit spell@@")); + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showChatPopup(int x, int y, ChatTab *tab) +{ + if (!tab || !actorSpriteManager || !player_node) + return; + + mTab = tab; + + mBrowserBox->clearRows(); + + if (tab->getType() == ChatTab::TAB_WHISPER) + mBrowserBox->addRow(_("@@chat close|Close@@")); + + mBrowserBox->addRow(strprintf("@@chat clear|%s@@", _("Clear"))); + mBrowserBox->addRow("##3---"); + + if (tab->getAllowHighlight()) + { + mBrowserBox->addRow(strprintf("@@disable highlight|%s@@", + _("Disable highlight"))); + mBrowserBox->addRow("##3---"); + } + else + { + mBrowserBox->addRow(strprintf("@@enable highlight|%s@@", + _("Enable highlight"))); + mBrowserBox->addRow("##3---"); + } + + if (tab->getType() == ChatTab::TAB_PARTY) + { + mBrowserBox->addRow(_("@@leave party|Leave@@")); + mBrowserBox->addRow("##3---"); + } + + if (tab->getType() == ChatTab::TAB_WHISPER) + { + WhisperTab *wTab = static_cast<WhisperTab*>(tab); + std::string name = wTab->getNick(); + + Being* being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + + if (being) + { + mBeingId = being->getId(); + mNick = being->getName(); + + mBrowserBox->addRow(_("@@trade|Trade@@")); + mBrowserBox->addRow(_("@@attack|Attack@@")); + + mBrowserBox->addRow("##3---"); + + mBrowserBox->addRow(_("@@heal|Heal@@")); + mBrowserBox->addRow("##3---"); + + switch (player_relations.getRelation(name)) + { + case PlayerRelation::NEUTRAL: + mBrowserBox->addRow(_("@@friend|Befriend@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::FRIEND: + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::DISREGARDED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::IGNORED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::ERASED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + break; + + default: + break; + } + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@follow|Follow@@")); + mBrowserBox->addRow(_("@@imitation|Imitation@@")); + mBrowserBox->addRow(_("@@move|Move@@")); + mBrowserBox->addRow(_("@@undress|Undress@@")); + + if (player_relations.getDefault() & PlayerRelation::TRADE) + { + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + } + + mBrowserBox->addRow("##3---"); + + if (player_node->isInParty()) + { + if (player_node->getParty()) + { + if (!player_node->getParty()->isMember(wTab->getNick())) + { + mBrowserBox->addRow(_("@@party|Invite to party@@")); + } + else + { + mBrowserBox->addRow( + _("@@kick party|Kick from party@@")); + } + mBrowserBox->addRow("##3---"); + } + } + Guild *guild1 = being->getGuild(); + Guild *guild2 = player_node->getGuild(); + if (guild2) + { + if (guild1) + { + if (guild1->getId() == guild2->getId()) + { + mBrowserBox->addRow( + _("@@guild-kick|Kick from guild@@")); + mBrowserBox->addRow( + _("@@guild-pos|Change pos in guild >@@")); + } + } + else + { + mBrowserBox->addRow(_("@@guild|Invite to guild@@")); + } + } + } + } + mBrowserBox->addRow(strprintf(_("@@cancel|Cancel@@"))); + + showPopup(x, y); +} + +void PopupMenu::showChangePos(int x, int y) +{ + mBrowserBox->clearRows(); + mBrowserBox->addRow(_("Change guild position")); + + if (!player_node) + return; + + const Guild *guild = player_node->getGuild(); + if (guild) + { + PositionsMap map = guild->getPositions(); + PositionsMap::iterator itr = map.begin(); + PositionsMap::iterator itr_end = map.end(); + for (; itr != itr_end; ++itr) + { + mBrowserBox->addRow(strprintf(_("@@guild-pos-%d|%s@@"), + itr->first, itr->second.c_str())); + } + mBrowserBox->addRow(strprintf(_("@@cancel|Cancel@@"))); + + showPopup(x, y); + } + else + { + mBeingId = 0; + mFloorItem = 0; + mItem = 0; + mMapItem = 0; + mNick = ""; + setVisible(false); + } +} + +void PopupMenu::handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_) +{ + if (!actorSpriteManager) + return; + + Being *being = actorSpriteManager->findBeing(mBeingId); + + // Talk To action + if (link == "talk" && being && being->canTalk()) + { + being->talkTo(); + } + // Trade action + else if (link == "trade" && being && + being->getType() == ActorSprite::PLAYER) + { + Net::getTradeHandler()->request(being); + tradePartnerName = being->getName(); + if (tradeWindow) + tradeWindow->clear(); + } + else if (link == "buy" && being && mBeingId != 0) + { + if (being->getType() == Being::NPC) + Net::getNpcHandler()->buy(mBeingId); + else if (being->getType() == Being::PLAYER) + Net::getBuySellHandler()->requestSellList(being->getName()); + } + else if (link == "buy" && !mNick.empty()) + { + Net::getBuySellHandler()->requestSellList(mNick); + } + else if (link == "sell" && being && mBeingId != 0) + { + if (being->getType() == Being::NPC) + Net::getNpcHandler()->sell(mBeingId); + else if (being->getType() == Being::PLAYER) + Net::getBuySellHandler()->requestBuyList(being->getName()); + } + else if (link == "sell" && !mNick.empty()) + { + Net::getBuySellHandler()->requestBuyList(mNick); + } + // Attack action + else if (link == "attack" && being) + { + if (player_node) + player_node->attack(being, true); + } + else if (link == "heal" && being && being->getType() != Being::MONSTER) + { + actorSpriteManager->heal(player_node, being); + } + else if (link == "unignore" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), + PlayerRelation::NEUTRAL); + } + else if (link == "unignore" && !mNick.empty()) + { + player_relations.setRelation(mNick, + PlayerRelation::NEUTRAL); + } + else if (link == "ignore" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), + PlayerRelation::IGNORED); + } + else if (link == "ignore" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::IGNORED); + } + else if (link == "erase" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), PlayerRelation::ERASED); + } + else if (link == "erase" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::ERASED); + } + else if (link == "disregard" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), + PlayerRelation::DISREGARDED); + } + else if (link == "disregard" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::DISREGARDED); + } + else if (link == "friend" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), PlayerRelation::FRIEND); + } + else if (link == "friend" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::FRIEND); + } + // Guild action + else if (link == "guild" && !mNick.empty()) + { + if (player_node) + { + const Guild *guild = player_node->getGuild(); + if (guild) + Net::getGuildHandler()->invite(guild->getId(), mNick); + } + } + else if (link == "nuke" && being) + { + actorSpriteManager->addBlock(being->getId()); + actorSpriteManager->destroy(being); + } + // Follow Player action + else if (link == "follow" && !mNick.empty()) + { + if (player_node) + player_node->setFollow(mNick); + } + else if (link == "imitation" && !mNick.empty()) + { + if (player_node) + player_node->setImitate(mNick); + } + // Pick Up Floor Item action + else if ((link == "pickup") && mFloorItem) + { + if (player_node) + player_node->pickUp(mFloorItem); + } + // Look To action + else if (link == "look") + { + } + else if (link == "use" && mItem) + { + if (mItem->isEquipment()) + { + if (mItem->isEquipped()) + Net::getInventoryHandler()->unequipItem(mItem); + else + Net::getInventoryHandler()->equipItem(mItem); + } + else + { + Net::getInventoryHandler()->useItem(mItem); + } + } + else if (link == "use" && mItemId) + { + if (mItemId < SPELL_MIN_ID) + { + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(mItemId); + if (item) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + else + Net::getInventoryHandler()->equipItem(item); + } + else + { + Net::getInventoryHandler()->useItem(item); + } + } + } + } + else if (spellManager) + { + spellManager->useItem(mItemId); + } + } + else if (link == "chat") + { + if (chatWindow) + { + if (mItem) + chatWindow->addItemText(mItem->getInfo().getName()); + else if (mFloorItem) + chatWindow->addItemText(mFloorItem->getInfo().getName()); + } + } + else if (link == "whisper" && !mNick.empty() && chatWindow) + { + if (chatWindow) + { + if (config.getBoolValue("whispertab")) + chatWindow->localChatInput("/q " + mNick); + else + chatWindow->addInputText("/w \"" + mNick + "\" "); + } + } + else if (link == "move" && being) + { + if (player_node) + player_node->navigateTo(being->getTileX(), being->getTileY()); + } + else if (link == "split" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, + inventoryWindow, mItem); + } + else if (link == "drop" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop, + inventoryWindow, mItem); + } + else if (link == "store" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd, + inventoryWindow, mItem); + } + else if (link == "store 10" && mItem) + { + int cnt = 10; + if (cnt > mItem->getQuantity()) + cnt = mItem->getQuantity(); + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + mItem->getInvIndex(), cnt, + Inventory::STORAGE); + } + else if (link == "store half" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + mItem->getInvIndex(), mItem->getQuantity() / 2, + Inventory::STORAGE); + } + else if (link == "store all" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + mItem->getInvIndex(), mItem->getQuantity(), + Inventory::STORAGE); + } + else if (link == "retrieve" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, mWindow, + mItem); + } + else if (link == "retrieve 10" && mItem) + { + int cnt = 10; + if (cnt > mItem->getQuantity()) + cnt = mItem->getQuantity(); + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + mItem->getInvIndex(), cnt, + Inventory::INVENTORY); + } + else if (link == "retrieve half" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + mItem->getInvIndex(), mItem->getQuantity() / 2, + Inventory::INVENTORY); + } + else if (link == "retrieve all" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + mItem->getInvIndex(), mItem->getQuantity(), + Inventory::INVENTORY); + } + else if (link == "party" && being && + being->getType() == ActorSprite::PLAYER) + { + Net::getPartyHandler()->invite(being); + } + else if (link == "kick party" && being + && being->getType() == Being::PLAYER) + { + Net::getPartyHandler()->kick(being); + } + else if (link == "name" && !mNick.empty()) + { + const std::string &name = mNick; + if (chatWindow) + chatWindow->addInputText(name); + } + else if (link == "admin-kick" && being && + (being->getType() == ActorSprite::PLAYER || + being->getType() == ActorSprite::MONSTER)) + { + Net::getAdminHandler()->kick(being->getId()); + } + else if (link == "chat close" && mTab) + { + mTab->handleCommand("close", ""); + } + else if (link == "leave party" && mTab) + { + Net::getPartyHandler()->leave(); + } + else if (link == "chat clear" && mTab) + { + if (chatWindow) + chatWindow->clearTab(); + } + else if (link == "remove map" && mMapItem) + { + if (viewport) + { + Map *map = viewport->getCurrentMap(); + if (map) + { + SpecialLayer *specialLayer = map->getSpecialLayer(); + if (specialLayer) + { + const int x = mMapItem->getX(); + const int y = mMapItem->getY(); + specialLayer->setTile(x, y, MapItem::EMPTY); + if (socialWindow) + socialWindow->removePortal(x, y); + } + } + } + } + else if (link == "rename map" && mMapItem) + { + mRenameListener.setMapItem(mMapItem); + mDialog = new TextDialog(_("Rename map sign "), + _("Name: ")); + mRenameListener.setDialog(mDialog); + mDialog->setText(mMapItem->getComment()); + mDialog->setActionEventId("ok"); + mDialog->addActionListener(&mRenameListener); + } + else if (link == "load old outfits") + { + if (outfitWindow) + outfitWindow->load(true); + } + else if (link == "load old spells") + { + if (spellManager) + { + spellManager->load(true); + spellManager->save(); + } + } + else if (link == "load old item shortcuts") + { + if (itemShortcutWindow) + { + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num]) + { + itemShortcut[num]->load(true); + itemShortcut[num]->save(); + } + } + } + else if (link == "load old drop shortcuts") + { + if (dropShortcut) + { + dropShortcut->load(true); + dropShortcut->save(); + } + } + else if (link == "edit spell" && mSpell) + { + new TextCommandEditor(mSpell); + } + else if (link == "undress" && being) + { + Net::getBeingHandler()->undress(being); + } + else if (link == "guild-kick" && !mNick.empty()) + { + if (player_node) + { + const Guild *guild = player_node->getGuild(); + if (guild) + Net::getGuildHandler()->kick(guild->getMember(mNick)); + } + } + else if (link == "enable highlight" && mTab) + { + mTab->setAllowHighlight(true); + } + else if (link == "disable highlight" && mTab) + { + mTab->setAllowHighlight(false); + } + else if (link == "guild-pos" && !mNick.empty()) + { + showChangePos(getX(), getY()); + return; + } + else if (!link.find("guild-pos-")) + { + if (player_node) + { + int num = atoi(link.substr(10).c_str()); + const Guild *guild = player_node->getGuild(); + if (guild) + { + Net::getGuildHandler()->changeMemberPostion( + guild->getMember(mNick), num); + } + } + } + else if (!link.find("player_")) + { + mBeingId = atoi(link.substr(7).c_str()); + Being *being = actorSpriteManager->findBeing(mBeingId); + if (being) + { + showPopup(getX(), getY(), being); + return; + } + } + // Unknown actions + else if (link != "cancel") + { + logger->log("PopupMenu: Warning, unknown action '%s'", link.c_str()); + } + + setVisible(false); + + mBeingId = 0; + mFloorItem = 0; + mItem = 0; + mItemId = 0; + mMapItem = 0; + mNick = ""; +} + +void PopupMenu::showPopup(Window *parent, int x, int y, Item *item, + bool isInventory) +{ + if (!item) + return; + + mItem = item; + mWindow = parent; + mBrowserBox->clearRows(); + + int cnt = item->getQuantity(); + + if (isInventory) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip"))); + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip"))); + } + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + + if (cnt > 1) + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop..."))); + else + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop"))); + + if (Net::getInventoryHandler()->canSplit(item)) + mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split"))); + + if (InventoryWindow::isStorageActive()) + { + mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store"))); + if (cnt > 1) + { + if (cnt > 10) + { + mBrowserBox->addRow(strprintf("@@store 10|%s@@", + _("Store 10"))); + } + mBrowserBox->addRow(strprintf("@@store half|%s@@", + _("Store half"))); + mBrowserBox->addRow(strprintf("@@store all|%s@@", + _("Store all"))); + } + } + } + // Assume in storage for now + // TODO: make this whole system more flexible, if needed + else + { + mBrowserBox->addRow(strprintf("@@retrieve|%s@@", _("Retrieve"))); + if (cnt > 1) + { + if (cnt > 10) + { + mBrowserBox->addRow(strprintf("@@retrieve 10|%s@@", + _("Retrieve 10"))); + } + mBrowserBox->addRow(strprintf("@@retrieve half|%s@@", + _("Retrieve half"))); + mBrowserBox->addRow(strprintf("@@retrieve all|%s@@", + _("Retrieve all"))); + } + } + mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); +} + +void PopupMenu::showItemPopup(int x, int y, int itemId) +{ + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(itemId); + if (item) + { + showItemPopup(x, y, item); + } + else + { + mItem = 0; + mItemId = itemId; + mBrowserBox->clearRows(); + + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@load old item shortcuts|%s@@", + _("Load old item shortcuts"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); + } +} + +void PopupMenu::showItemPopup(int x, int y, Item *item) +{ + mItem = item; + if (item) + mItemId = item->getId(); + else + mItemId = 0; + mBrowserBox->clearRows(); + + if (item) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip"))); + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip"))); + } + else + { + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + } + + if (item->getQuantity() > 1) + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop..."))); + else + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop"))); + + if (Net::getInventoryHandler()->canSplit(item)) + mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split"))); + + if (InventoryWindow::isStorageActive()) + mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store"))); + mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat"))); + mBrowserBox->addRow("##3---"); + } + mBrowserBox->addRow(strprintf("@@load old item shortcuts|%s@@", + _("Load old item shortcuts"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); +} + +void PopupMenu::showDropPopup(int x, int y, Item *item) +{ + mItem = item; + mBrowserBox->clearRows(); + + if (item) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip"))); + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip"))); + } + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + + if (item->getQuantity() > 1) + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop..."))); + else + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop"))); + + if (Net::getInventoryHandler()->canSplit(item)) + mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split"))); + + if (InventoryWindow::isStorageActive()) + mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store"))); + mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat"))); + mBrowserBox->addRow("##3---"); + } + mBrowserBox->addRow(strprintf("@@load old drop shortcuts|%s@@", + _("Load old drop shortcuts"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); +} + +void PopupMenu::showPopup(int x _UNUSED_, int y _UNUSED_, Button *button) +{ + if (!button) + return; + +} + +void PopupMenu::showPopup(int x, int y) +{ + setContentSize(mBrowserBox->getWidth() + 8, mBrowserBox->getHeight() + 8); + if (graphics->getWidth() < (x + getWidth() + 5)) + x = graphics->getWidth() - getWidth(); + if (graphics->getHeight() < (y + getHeight() + 5)) + y = graphics->getHeight() - getHeight(); + setPosition(x, y); + setVisible(true); + requestMoveToTop(); +} + + +void RenameListener::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok" && mMapItem && viewport && mDialog) + { + Map *map = viewport->getMap(); + if (!map) + return; + + SpecialLayer *sl = map->getSpecialLayer(); + MapItem *item = 0; + if (sl) + { + item = sl->getTile(mMapItem->getX(), mMapItem->getY()); + if (!item) + { + sl->setTile(mMapItem->getX(), mMapItem->getY(), + mMapItem->getType()); + item = sl->getTile(mMapItem->getX(), mMapItem->getY()); + } + item->setComment(mDialog->getText()); + } + item = map->findPortalXY(mMapItem->getX(), mMapItem->getY()); + if (item) + item->setComment(mDialog->getText()); + + if (socialWindow) + socialWindow->updatePortalNames(); + } + mDialog = 0; +} diff --git a/src/gui/popupmenu.h b/src/gui/popupmenu.h new file mode 100644 index 000000000..d6c66aa81 --- /dev/null +++ b/src/gui/popupmenu.h @@ -0,0 +1,149 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef POPUP_MENU_H +#define POPUP_MENU_H + +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/popup.h" + +#include <guichan/actionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Being; +class BrowserBox; +class Button; +class ChatTab; +class FloorItem; +class Item; +class ItemShortcut; +class MapItem; +class TextCommand; +class TextDialog; +class Window; + +class RenameListener : public gcn::ActionListener +{ + public: + void action(const gcn::ActionEvent &event); + + void setMapItem(MapItem* mapItem) + { mMapItem = mapItem; } + + void setDialog(TextDialog *dialog) + { mDialog = dialog; } + + private: + MapItem *mMapItem; + TextDialog *mDialog; +}; + +/** + * Window showing popup menu. + */ +class PopupMenu : public Popup, public LinkHandler +{ + public: + /** + * Constructor. + */ + PopupMenu(); + + /** + * Shows the being related popup menu at the specified mouse coords. + */ + void showPopup(int x, int y, Being *being); + + /** + * Shows the beings related popup menu at the specified mouse coords. + */ + void showPopup(int x, int y, std::list<Being*> &beings); + + void showPlayerPopup(int x, int y, std::string nick); + + /** + * Shows the floor item related popup menu at the specified + * mouse coords. + */ + void showPopup(int x, int y, FloorItem *floorItem); + + /** + * Shows the related popup menu when right click on the inventory + * at the specified mouse coordinates. + */ + void showPopup(Window *parent, int x, int y, Item *item, + bool isInventory); + + void showPopup(int x, int y, Button *button); + + void showPopup(int x, int y, MapItem *mapItem); + + void showItemPopup(int x, int y, Item *item); + + void showItemPopup(int x, int y, int itemId); + + void showDropPopup(int x, int y, Item *item); + + void showOutfitsPopup(int x, int y); + + void showSpellPopup(int x, int y, TextCommand *cmd); + + /** + * Shows the related popup menu when right click on the chat + * at the specified mouse coordinates. + */ + void showChatPopup(int x, int y, ChatTab *tab); + + void showChangePos(int x, int y); + + /** + * Handles link action. + */ + void handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_); + + private: + BrowserBox* mBrowserBox; + + int mBeingId; + FloorItem* mFloorItem; + Item *mItem; + int mItemId; + MapItem *mMapItem; + ChatTab *mTab; + TextCommand *mSpell; + Window *mWindow; + RenameListener mRenameListener; + TextDialog *mDialog; + std::string mNick; + + /** + * Shared code for the various showPopup functions. + */ + void showPopup(int x, int y); +}; + +#endif diff --git a/src/gui/quitdialog.cpp b/src/gui/quitdialog.cpp new file mode 100644 index 000000000..7b587dccc --- /dev/null +++ b/src/gui/quitdialog.cpp @@ -0,0 +1,204 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/quitdialog.h" + +#include "client.h" + +#include "gui/chat.h" +#include "gui/sdlinput.h" +#include "gui/viewport.h" + +#include "gui/widgets/checkbox.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/button.h" +#include "gui/widgets/radiobutton.h" + +#include "net/charhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" + +#include <assert.h> + +QuitDialog::QuitDialog(QuitDialog** pointerToMe): + Window(_("Quit"), true, NULL), mMyPointer(pointerToMe) +{ +// int width = 200; +// int height = 120; + + mForceQuit = new RadioButton(_("Quit"), "quitdialog"); + mLogoutQuit = new RadioButton(_("Quit"), "quitdialog"); + mSaveState = new CheckBox(_("Save state"), true); + mSwitchAccountServer = new RadioButton(_("Switch server"), "quitdialog"); + mSwitchCharacter = new RadioButton(_("Switch character"), "quitdialog"); + mOkButton = new Button(_("OK"), "ok", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); +// setContentSize(width, height); + + addKeyListener(this); + + ContainerPlacer place = getPlacer(0, 0); + + const State state = Client::getState(); + + // All states, when we're not logged in to someone. + if (state == STATE_CHOOSE_SERVER || + state == STATE_CONNECT_SERVER || + state == STATE_LOGIN || + state == STATE_LOGIN_ATTEMPT || + state == STATE_UPDATE || + state == STATE_LOAD_DATA) + { + placeOption(place, mForceQuit); + } + else + { + // Only added if we are connected to an accountserver or gameserver + placeOption(place, mLogoutQuit); + placeOption(place, mSwitchAccountServer); + + // Only added if we are connected to a gameserver + if (state == STATE_GAME) + placeOption(place, mSwitchCharacter); + } + + mOptions[0]->setSelected(true); + + place = getPlacer(0, 1); + + place(0, 0, mSaveState, 3); + place(1, 1, mOkButton, 1); + place(2, 1, mCancelButton, 1); + + reflowLayout(200, 0); + setLocationRelativeTo(getParent()); + setVisible(true); + requestModalFocus(); + mOkButton->requestFocus(); +} + +QuitDialog::~QuitDialog() +{ + if (mMyPointer) + *mMyPointer = 0; + // Optional widgets, so delete them by hand. + delete mForceQuit; + mForceQuit = 0; + delete mLogoutQuit; + mLogoutQuit = 0; + delete mSwitchAccountServer; + mSwitchAccountServer = 0; + delete mSwitchCharacter; + mSwitchCharacter = 0; +} + +void QuitDialog::placeOption(ContainerPlacer &place, gcn::RadioButton *option) +{ + place(0, static_cast<int>(mOptions.size()), option, 3); + mOptions.push_back(option); +} + +void QuitDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok") + { + if (viewport) + { + Map *map = viewport->getCurrentMap(); + if (map) + map->saveExtraLayer(); + } + if (chatWindow && mSaveState->isSelected()) + chatWindow->saveState(); + + if (mForceQuit->isSelected()) + { + Client::setState(STATE_FORCE_QUIT); + } + else if (mLogoutQuit->isSelected()) + { + Client::setState(STATE_EXIT); + } + else if (mSwitchAccountServer->isSelected()) + { + Client::setState(STATE_SWITCH_SERVER); + } + else if (mSwitchCharacter->isSelected()) + { + assert(Client::getState() == STATE_GAME); + + Net::getCharHandler()->switchCharacter(); + } + } + scheduleDelete(); +} + +void QuitDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + const gcn::Key &key = keyEvent.getKey(); + int dir = 0; + + switch (key.getValue()) + { + case Key::ENTER: + action(gcn::ActionEvent(NULL, mOkButton->getActionEventId())); + break; + case Key::ESCAPE: + action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId())); + break; + case Key::UP: + dir = -1; + break; + case Key::DOWN: + dir = 1; + break; + default: + break; + } + + if (dir != 0) + { + std::vector<gcn::RadioButton*>::iterator it = mOptions.begin(); + + for (; it < mOptions.end(); it++) + { + if ((*it)->isSelected()) + break; + } + + if (it == mOptions.end()) + { + if (mOptions[0]) + mOptions[0]->setSelected(true); + return; + } + else if (it == mOptions.begin() && dir < 0) + it = mOptions.end(); + + it += dir; + + if (it == mOptions.end()) + it = mOptions.begin(); + + (*it)->setSelected(true); + } +} diff --git a/src/gui/quitdialog.h b/src/gui/quitdialog.h new file mode 100644 index 000000000..a6763079e --- /dev/null +++ b/src/gui/quitdialog.h @@ -0,0 +1,77 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUITDIALOG_H +#define QUITDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> + +#include <vector> + +/** + * The quit dialog. + * + * \ingroup Interface + */ +class QuitDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor + * + * @pointerToMe will be set to NULL when the QuitDialog is destroyed + */ + QuitDialog(QuitDialog **pointerToMe); + + /** + * Destructor + */ + ~QuitDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + private: + void placeOption(ContainerPlacer &place, gcn::RadioButton *option); + std::vector<gcn::RadioButton*> mOptions; + + gcn::RadioButton *mLogoutQuit; + gcn::CheckBox *mSaveState; + gcn::RadioButton *mForceQuit; + gcn::RadioButton *mSwitchAccountServer; + gcn::RadioButton *mSwitchCharacter; + gcn::Button *mOkButton; + gcn::Button *mCancelButton; + + QuitDialog **mMyPointer; +}; + +#endif diff --git a/src/gui/register.cpp b/src/gui/register.cpp new file mode 100644 index 000000000..fbd63195f --- /dev/null +++ b/src/gui/register.cpp @@ -0,0 +1,258 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/register.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" + +#include "gui/login.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/radiobutton.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +WrongDataNoticeListener::WrongDataNoticeListener(): + mTarget(0) +{ +} + +void WrongDataNoticeListener::setTarget(gcn::TextField *textField) +{ + mTarget = textField; +} + +void WrongDataNoticeListener::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok" && mTarget) + mTarget->requestFocus(); +} + +RegisterDialog::RegisterDialog(LoginData *loginData): + Window(_("Register")), + mEmailField(0), + mMaleButton(0), + mFemaleButton(0), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + int optionalActions = Net::getLoginHandler()->supportedOptionalActions(); + + gcn::Label *userLabel = new Label(_("Name:")); + gcn::Label *passwordLabel = new Label(_("Password:")); + gcn::Label *confirmLabel = new Label(_("Confirm:")); + mUserField = new TextField(loginData->username); + mPasswordField = new PasswordField(loginData->password); + mConfirmField = new PasswordField; + mRegisterButton = new Button(_("Register"), "register", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + ContainerPlacer place; + place = getPlacer(0, 0); + place(0, 0, userLabel); + place(0, 1, passwordLabel); + place(0, 2, confirmLabel); + + place(1, 0, mUserField, 3).setPadding(2); + place(1, 1, mPasswordField, 3).setPadding(2); + place(1, 2, mConfirmField, 3).setPadding(2); + + int row = 3; + + if (optionalActions & Net::LoginHandler::SetGenderOnRegister) + { + mMaleButton = new RadioButton(_("Male"), "sex", true); + mFemaleButton = new RadioButton(_("Female"), "sex", false); + place(1, row, mMaleButton); + place(2, row, mFemaleButton); + + row++; + } + + if (optionalActions & Net::LoginHandler::SetEmailOnRegister) + { + gcn::Label *emailLabel = new Label(_("Email:")); + mEmailField = new TextField; + place(0, row, emailLabel); + place(1, row, mEmailField, 3).setPadding(2); + + row++; + } + + place = getPlacer(0, 2); + place(1, 0, mRegisterButton); + place(2, 0, mCancelButton); + reflowLayout(250, 0); + + mUserField->addKeyListener(this); + mPasswordField->addKeyListener(this); + mConfirmField->addKeyListener(this); + + /* TODO: + * This is a quick and dirty way to respond to the ENTER key, regardless of + * which text field is selected. There may be a better way now with the new + * input system of Guichan 0.6.0. See also the login dialog. + */ + mUserField->setActionEventId("register"); + mPasswordField->setActionEventId("register"); + mConfirmField->setActionEventId("register"); + + mUserField->addActionListener(this); + mPasswordField->addActionListener(this); + mConfirmField->addActionListener(this); + + center(); + setVisible(true); + mUserField->requestFocus(); + mUserField->setCaretPosition(static_cast<unsigned>( + mUserField->getText().length())); + + mRegisterButton->setEnabled(canSubmit()); +} + +RegisterDialog::~RegisterDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void RegisterDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_LOGIN); + } + else if (event.getId() == "register" && canSubmit()) + { + const std::string user = mUserField->getText(); + logger->log("RegisterDialog::register Username is %s", user.c_str()); + + std::string errorMessage; + int error = 0; + + unsigned int minUser = Net::getLoginHandler()->getMinUserNameLength(); + unsigned int maxUser = Net::getLoginHandler()->getMaxUserNameLength(); + unsigned int minPass = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int maxPass = Net::getLoginHandler()->getMaxPasswordLength(); + + if (user.length() < minUser) + { + // Name too short + errorMessage = strprintf + (_("The username needs to be at least %d characters long."), + minUser); + error = 1; + } + else if (user.length() > maxUser - 1 ) + { + // Name too long + errorMessage = strprintf + (_("The username needs to be less than %d characters long."), + maxUser); + error = 1; + } + else if (mPasswordField->getText().length() < minPass) + { + // Pass too short + errorMessage = strprintf + (_("The password needs to be at least %d characters long."), + minPass); + error = 2; + } + else if (mPasswordField->getText().length() > maxPass - 1 ) + { + // Pass too long + errorMessage = strprintf + (_("The password needs to be less than %d characters long."), + maxPass); + error = 2; + } + else if (mPasswordField->getText() != mConfirmField->getText()) + { + // Password does not match with the confirmation one + errorMessage = _("Passwords do not match."); + error = 2; + } + + // TODO: Check if a valid email address was given + + if (error > 0) + { + if (error == 1) + { + mWrongDataNoticeListener->setTarget(this->mUserField); + } + else if (error == 2) + { + // Reset password confirmation + mPasswordField->setText(""); + mConfirmField->setText(""); + + mWrongDataNoticeListener->setTarget(this->mPasswordField); + } + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage); + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, register the new user. + mRegisterButton->setEnabled(false); + + mLoginData->username = mUserField->getText(); + mLoginData->password = mPasswordField->getText(); + if (mFemaleButton) + mLoginData->gender = mFemaleButton->isSelected() ? + GENDER_FEMALE : GENDER_MALE; + if (mEmailField) + mLoginData->email = mEmailField->getText(); + mLoginData->registerLogin = true; + + Client::setState(STATE_REGISTER_ATTEMPT); + } + } +} + +void RegisterDialog::keyPressed(gcn::KeyEvent &keyEvent _UNUSED_) +{ + mRegisterButton->setEnabled(canSubmit()); +} + +bool RegisterDialog::canSubmit() const +{ + return !mUserField->getText().empty() && + !mPasswordField->getText().empty() && + !mConfirmField->getText().empty() && + Client::getState() == STATE_REGISTER; +} diff --git a/src/gui/register.h b/src/gui/register.h new file mode 100644 index 000000000..f9c866768 --- /dev/null +++ b/src/gui/register.h @@ -0,0 +1,110 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef REGISTER_H +#define REGISTER_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> + +#include <string> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class LoginData; +class OkDialog; + +/** + * Listener used while dealing with wrong data. It is used to direct the focus + * to the field which contained wrong data when the Ok button was pressed on + * the error notice. + */ +class WrongDataNoticeListener : public gcn::ActionListener +{ + public: + WrongDataNoticeListener(); + void setTarget(gcn::TextField *textField); + void action(const gcn::ActionEvent &event); + private: + gcn::TextField *mTarget; +}; + +/** + * The registration dialog. + * + * \ingroup Interface + */ +class RegisterDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor. Name, password and server fields will be initialized to + * the information already present in the LoginData instance. + * + * @see Window::Window + */ + RegisterDialog(LoginData *loginData); + + /** + * Destructor + */ + ~RegisterDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Called when a key is pressed in one of the text fields. + */ + void keyPressed(gcn::KeyEvent &keyEvent); + + private: + /** + * Returns whether submit can be enabled. This is true in the register + * state, when all necessary fields have some text. + */ + bool canSubmit() const; + + gcn::TextField *mUserField; + gcn::TextField *mPasswordField; + gcn::TextField *mConfirmField; + gcn::TextField *mEmailField; + + gcn::Button *mRegisterButton; + gcn::Button *mCancelButton; + gcn::RadioButton *mMaleButton; + gcn::RadioButton *mFemaleButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif diff --git a/src/gui/sdlinput.cpp b/src/gui/sdlinput.cpp new file mode 100644 index 000000000..a48f35be8 --- /dev/null +++ b/src/gui/sdlinput.cpp @@ -0,0 +1,432 @@ +/* _______ __ __ __ ______ __ __ _______ __ __ + * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\ + * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / / + * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / / + * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / / + * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ / + * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/ + * + * Copyright (c) 2004, 2005, 2006, 2007 Olof Naessén and Per Larsson + * Copyright (C) 2007-2010 The Mana World Development Team + * + * Js_./ + * Per Larsson a.k.a finalman _RqZ{a<^_aa + * Olof Naessén a.k.a jansem/yakslem _asww7!uY`> )\a// + * _Qhm`] _f "'c 1!5m + * Visit: http://guichan.darkbits.org )Qk<P ` _: :+' .' "{[ + * .)j(] .d_/ '-( P . S + * License: (BSD) <Td/Z <fP"5(\"??"\a. .L + * Redistribution and use in source and _dV>ws?a-?' ._/L #' + * binary forms, with or without )4d[#7r, . ' )d`)[ + * modification, are permitted provided _Q-5'5W..j/?' -?!\)cam' + * that the following conditions are met: j<<WP+k/);. _W=j f + * 1. Redistributions of source code must .$%w\/]Q . ."' . mj$ + * retain the above copyright notice, ]E.pYY(Q]>. a J@\ + * this list of conditions and the j(]1u<sE"L,. . ./^ ]{a + * following disclaimer. 4'_uomm\. )L);-4 (3= + * 2. Redistributions in binary form must )_]X{Z('a_"a7'<a"a, ]"[ + * reproduce the above copyright notice, #}<]m7`Za??4,P-"'7. ).m + * this list of conditions and the ]d2e)Q(<Q( ?94 b- LQ/ + * following disclaimer in the <B!</]C)d_, '(<' .f. =C+m + * documentation and/or other materials .Z!=J ]e []('-4f _ ) -.)m]' + * provided with the distribution. .w[5]' _[ /.)_-"+? _/ <W" + * 3. Neither the name of Guichan nor the :$we` _! + _/ . j? + * names of its contributors may be used =3)= _f (_yQmWW$#( " + * to endorse or promote products derived - W, sQQQQmZQ#Wwa].. + * from this software without specific (js, \[QQW$QWW#?!V"". + * prior written permission. ]y:.<\.. . + * -]n w/ ' [. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT )/ )/ ! + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY < (; sac , ' + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, ]^ .- % + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF c < r + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR aga< <La + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 5% )P'-3L + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR _bQf` y`..)a + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ,J?4P'.P"_(\?d'., + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES _Pa,)!f/<[]/ ?" + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT _2-..:. .r+_,.. . + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ?a.<%"' " -'.a_ _, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ^ + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sdlinput.h" + +#include <guichan/exception.hpp> + +SDLInput::SDLInput() +{ + mMouseInWindow = true; + mMouseDown = false; +} + +bool SDLInput::isKeyQueueEmpty() +{ + return mKeyInputQueue.empty(); +} + +gcn::KeyInput SDLInput::dequeueKeyInput() +{ + gcn::KeyInput keyInput; + + if (mKeyInputQueue.empty()) + { + throw GCN_EXCEPTION("The queue is empty."); + } + + keyInput = mKeyInputQueue.front(); + mKeyInputQueue.pop(); + + return keyInput; +} + +bool SDLInput::isMouseQueueEmpty() +{ + return mMouseInputQueue.empty(); +} + +gcn::MouseInput SDLInput::dequeueMouseInput() +{ + gcn::MouseInput mouseInput; + + if (mMouseInputQueue.empty()) + { + throw GCN_EXCEPTION("The queue is empty."); + } + + mouseInput = mMouseInputQueue.front(); + mMouseInputQueue.pop(); + + return mouseInput; +} + +void SDLInput::pushInput(SDL_Event event) +{ + gcn::KeyInput keyInput; + gcn::MouseInput mouseInput; + + switch (event.type) + { + case SDL_KEYDOWN: + keyInput.setKey(gcn::Key(convertKeyCharacter(event))); + keyInput.setType(gcn::KeyInput::PRESSED); + keyInput.setShiftPressed(event.key.keysym.mod & KMOD_SHIFT); + keyInput.setControlPressed(event.key.keysym.mod & KMOD_CTRL); + keyInput.setAltPressed(event.key.keysym.mod & KMOD_ALT); + keyInput.setMetaPressed(event.key.keysym.mod & KMOD_META); + keyInput.setNumericPad(event.key.keysym.sym >= SDLK_KP0 + && event.key.keysym.sym <= SDLK_KP_EQUALS); + + mKeyInputQueue.push(keyInput); + break; + + case SDL_KEYUP: + keyInput.setKey(gcn::Key(convertKeyCharacter(event))); + keyInput.setType(gcn::KeyInput::RELEASED); + keyInput.setShiftPressed(event.key.keysym.mod & KMOD_SHIFT); + keyInput.setControlPressed(event.key.keysym.mod & KMOD_CTRL); + keyInput.setAltPressed(event.key.keysym.mod & KMOD_ALT); + keyInput.setMetaPressed(event.key.keysym.mod & KMOD_META); + keyInput.setNumericPad(event.key.keysym.sym >= SDLK_KP0 + && event.key.keysym.sym <= SDLK_KP_EQUALS); + + mKeyInputQueue.push(keyInput); + break; + + case SDL_MOUSEBUTTONDOWN: + mMouseDown = true; + mouseInput.setX(event.button.x); + mouseInput.setY(event.button.y); + mouseInput.setButton(convertMouseButton(event.button.button)); + + if (event.button.button == SDL_BUTTON_WHEELDOWN) + { + mouseInput.setType(gcn::MouseInput::WHEEL_MOVED_DOWN); + } + else if (event.button.button == SDL_BUTTON_WHEELUP) + { + mouseInput.setType(gcn::MouseInput::WHEEL_MOVED_UP); + } + else + { + mouseInput.setType(gcn::MouseInput::PRESSED); + } + mouseInput.setTimeStamp(SDL_GetTicks()); + mMouseInputQueue.push(mouseInput); + break; + + case SDL_MOUSEBUTTONUP: + mMouseDown = false; + mouseInput.setX(event.button.x); + mouseInput.setY(event.button.y); + mouseInput.setButton(convertMouseButton(event.button.button)); + mouseInput.setType(gcn::MouseInput::RELEASED); + mouseInput.setTimeStamp(SDL_GetTicks()); + mMouseInputQueue.push(mouseInput); + break; + + case SDL_MOUSEMOTION: + mouseInput.setX(event.button.x); + mouseInput.setY(event.button.y); + mouseInput.setButton(gcn::MouseInput::EMPTY); + mouseInput.setType(gcn::MouseInput::MOVED); + mouseInput.setTimeStamp(SDL_GetTicks()); + mMouseInputQueue.push(mouseInput); + break; + + case SDL_ACTIVEEVENT: + /* + * This occurs when the mouse leaves the window and the Gui-chan + * application loses its mousefocus. + */ + if ((event.active.state & SDL_APPMOUSEFOCUS) + && !event.active.gain) + { + mMouseInWindow = false; + + if (!mMouseDown) + { + mouseInput.setX(-1); + mouseInput.setY(-1); + mouseInput.setButton(gcn::MouseInput::EMPTY); + mouseInput.setType(gcn::MouseInput::MOVED); + mMouseInputQueue.push(mouseInput); + } + } + + if ((event.active.state & SDL_APPMOUSEFOCUS) + && event.active.gain) + { + mMouseInWindow = true; + } + break; + + default: + break; + + } // end switch +} + +int SDLInput::convertMouseButton(int button) +{ + switch (button) + { + case SDL_BUTTON_LEFT: + return gcn::MouseInput::LEFT; + case SDL_BUTTON_RIGHT: + return gcn::MouseInput::RIGHT; + case SDL_BUTTON_MIDDLE: + return gcn::MouseInput::MIDDLE; + default: + // We have an unknown mouse type which is ignored. + return button; + } +} + +int SDLInput::convertKeyCharacter(SDL_Event event) +{ + SDL_keysym keysym = event.key.keysym; + + int value = keysym.unicode; + + switch (keysym.sym) + { + case SDLK_TAB: + value = Key::TAB; + break; + case SDLK_LALT: + value = Key::LEFT_ALT; + break; + case SDLK_RALT: + value = Key::RIGHT_ALT; + break; + case SDLK_LSHIFT: + value = Key::LEFT_SHIFT; + break; + case SDLK_RSHIFT: + value = Key::RIGHT_SHIFT; + break; + case SDLK_LCTRL: + value = Key::LEFT_CONTROL; + break; + case SDLK_RCTRL: + value = Key::RIGHT_CONTROL; + break; + case SDLK_BACKSPACE: + value = Key::BACKSPACE; + break; + case SDLK_PAUSE: + value = Key::PAUSE; + break; + case SDLK_SPACE: + // Special characters like ~ (tilde) ends up + // with the keysym.sym SDLK_SPACE which + // without this check would be lost. The check + // is only valid on key down events in SDL. + if (event.type == SDL_KEYUP || keysym.unicode == ' ') + { + value = Key::SPACE; + } + break; + case SDLK_ESCAPE: + value = Key::ESCAPE; + break; + case SDLK_DELETE: + value = Key::DELETE; + break; + case SDLK_INSERT: + value = Key::INSERT; + break; + case SDLK_HOME: + value = Key::HOME; + break; + case SDLK_END: + value = Key::END; + break; + case SDLK_PAGEUP: + value = Key::PAGE_UP; + break; + case SDLK_PRINT: + value = Key::PRINT_SCREEN; + break; + case SDLK_PAGEDOWN: + value = Key::PAGE_DOWN; + break; + case SDLK_F1: + value = Key::F1; + break; + case SDLK_F2: + value = Key::F2; + break; + case SDLK_F3: + value = Key::F3; + break; + case SDLK_F4: + value = Key::F4; + break; + case SDLK_F5: + value = Key::F5; + break; + case SDLK_F6: + value = Key::F6; + break; + case SDLK_F7: + value = Key::F7; + break; + case SDLK_F8: + value = Key::F8; + break; + case SDLK_F9: + value = Key::F9; + break; + case SDLK_F10: + value = Key::F10; + break; + case SDLK_F11: + value = Key::F11; + break; + case SDLK_F12: + value = Key::F12; + break; + case SDLK_F13: + value = Key::F13; + break; + case SDLK_F14: + value = Key::F14; + break; + case SDLK_F15: + value = Key::F15; + break; + case SDLK_NUMLOCK: + value = Key::NUM_LOCK; + break; + case SDLK_CAPSLOCK: + value = Key::CAPS_LOCK; + break; + case SDLK_SCROLLOCK: + value = Key::SCROLL_LOCK; + break; + case SDLK_RMETA: + value = Key::RIGHT_META; + break; + case SDLK_LMETA: + value = Key::LEFT_META; + break; + case SDLK_LSUPER: + value = Key::LEFT_SUPER; + break; + case SDLK_RSUPER: + value = Key::RIGHT_SUPER; + break; + case SDLK_MODE: + value = Key::ALT_GR; + break; + case SDLK_UP: + value = Key::UP; + break; + case SDLK_DOWN: + value = Key::DOWN; + break; + case SDLK_LEFT: + value = Key::LEFT; + break; + case SDLK_RIGHT: + value = Key::RIGHT; + break; + case SDLK_RETURN: + value = Key::ENTER; + break; + case SDLK_KP_ENTER: + value = Key::ENTER; + break; + + default: + break; + } + + if (!(keysym.mod & KMOD_NUM)) + { + switch (keysym.sym) + { + case SDLK_KP0: + value = Key::INSERT; + break; + case SDLK_KP1: + value = Key::END; + break; + case SDLK_KP2: + value = Key::DOWN; + break; + case SDLK_KP3: + value = Key::PAGE_DOWN; + break; + case SDLK_KP4: + value = Key::LEFT; + break; + case SDLK_KP5: + value = 0; + break; + case SDLK_KP6: + value = Key::RIGHT; + break; + case SDLK_KP7: + value = Key::HOME; + break; + case SDLK_KP8: + value = Key::UP; + break; + case SDLK_KP9: + value = Key::PAGE_UP; + break; + default: + break; + } + } + + return value; +} diff --git a/src/gui/sdlinput.h b/src/gui/sdlinput.h new file mode 100644 index 000000000..b441380a4 --- /dev/null +++ b/src/gui/sdlinput.h @@ -0,0 +1,188 @@ +/* _______ __ __ __ ______ __ __ _______ __ __ + * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\ + * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / / + * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / / + * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / / + * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ / + * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/ + * + * Copyright (c) 2004, 2005, 2006, 2007 Olof Naessén and Per Larsson + * Copyright (C) 2007-2010 The Mana World Development Team + * + * Js_./ + * Per Larsson a.k.a finalman _RqZ{a<^_aa + * Olof Naessén a.k.a jansem/yakslem _asww7!uY`> )\a// + * _Qhm`] _f "'c 1!5m + * Visit: http://guichan.darkbits.org )Qk<P ` _: :+' .' "{[ + * .)j(] .d_/ '-( P . S + * License: (BSD) <Td/Z <fP"5(\"??"\a. .L + * Redistribution and use in source and _dV>ws?a-?' ._/L #' + * binary forms, with or without )4d[#7r, . ' )d`)[ + * modification, are permitted provided _Q-5'5W..j/?' -?!\)cam' + * that the following conditions are met: j<<WP+k/);. _W=j f + * 1. Redistributions of source code must .$%w\/]Q . ."' . mj$ + * retain the above copyright notice, ]E.pYY(Q]>. a J@\ + * this list of conditions and the j(]1u<sE"L,. . ./^ ]{a + * following disclaimer. 4'_uomm\. )L);-4 (3= + * 2. Redistributions in binary form must )_]X{Z('a_"a7'<a"a, ]"[ + * reproduce the above copyright notice, #}<]m7`Za??4,P-"'7. ).m + * this list of conditions and the ]d2e)Q(<Q( ?94 b- LQ/ + * following disclaimer in the <B!</]C)d_, '(<' .f. =C+m + * documentation and/or other materials .Z!=J ]e []('-4f _ ) -.)m]' + * provided with the distribution. .w[5]' _[ /.)_-"+? _/ <W" + * 3. Neither the name of Guichan nor the :$we` _! + _/ . j? + * names of its contributors may be used =3)= _f (_yQmWW$#( " + * to endorse or promote products derived - W, sQQQQmZQ#Wwa].. + * from this software without specific (js, \[QQW$QWW#?!V"". + * prior written permission. ]y:.<\.. . + * -]n w/ ' [. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT )/ )/ ! + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY < (; sac , ' + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, ]^ .- % + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF c < r + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR aga< <La + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 5% )P'-3L + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR _bQf` y`..)a + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ,J?4P'.P"_(\?d'., + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES _Pa,)!f/<[]/ ?" + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT _2-..:. .r+_,.. . + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ?a.<%"' " -'.a_ _, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ^ + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SDLINPUT_H +#define SDLINPUT_H + +#include <queue> + +#include <SDL/SDL.h> + +#include <guichan/input.hpp> +#include <guichan/keyinput.hpp> +#include <guichan/mouseinput.hpp> +#include <guichan/platform.hpp> + +namespace Key +{ + enum + { + SPACE = ' ', + TAB = '\t', + ENTER = '\n', + // Negative values, to avoid conflicts with higher character codes. + LEFT_ALT = -1000, + RIGHT_ALT, + LEFT_SHIFT, + RIGHT_SHIFT, + LEFT_CONTROL, + RIGHT_CONTROL, + LEFT_META, + RIGHT_META, + LEFT_SUPER, + RIGHT_SUPER, + INSERT, + HOME, + PAGE_UP, + DELETE, + END, + PAGE_DOWN, + ESCAPE, + CAPS_LOCK, + BACKSPACE, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + PRINT_SCREEN, + SCROLL_LOCK, + PAUSE, + NUM_LOCK, + ALT_GR, + LEFT, + RIGHT, + UP, + DOWN + }; +} + +/** + * SDL implementation of Input. + */ +class SDLInput : public gcn::Input +{ +public: + + /** + * Constructor. + */ + SDLInput(); + + /** + * Pushes an SDL event. It should be called at least once per frame to + * update input with user input. + * + * @param event an event from SDL. + */ + virtual void pushInput(SDL_Event event); + + /** + * Polls all input. It exists for input driver compatibility. If you + * only use SDL and plan sticking with SDL you can safely ignore this + * function as it in the SDL case does nothing. + */ + virtual void _pollInput() { } + + + // Inherited from Input + + virtual bool isKeyQueueEmpty(); + + virtual gcn::KeyInput dequeueKeyInput(); + + virtual bool isMouseQueueEmpty(); + + virtual gcn::MouseInput dequeueMouseInput(); + +protected: + /** + * Converts a mouse button from SDL to a Guichan mouse button + * representation. + * + * @param button an SDL mouse button. + * @return a Guichan mouse button. + */ + int convertMouseButton(int button); + + /** + * Converts an SDL event key to a key value. + * + * @param event an SDL event with a key to convert. + * @return a key value. + * @see Key + */ + int convertKeyCharacter(SDL_Event event); + + std::queue<gcn::KeyInput> mKeyInputQueue; + std::queue<gcn::MouseInput> mMouseInputQueue; + + bool mMouseDown; + bool mMouseInWindow; +}; + +#endif diff --git a/src/gui/sell.cpp b/src/gui/sell.cpp new file mode 100644 index 000000000..85577b245 --- /dev/null +++ b/src/gui/sell.cpp @@ -0,0 +1,333 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/sell.h" + +#include "shopitem.h" +#include "units.h" + +#include "gui/setup.h" +#include "gui/trade.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shopitems.h" +#include "gui/widgets/shoplistbox.h" +#include "gui/widgets/slider.h" + +#include "net/buysellhandler.h" +#include "net/net.h" +#include "net/npchandler.h" + +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +SellDialog::DialogList SellDialog::instances; + +SellDialog::SellDialog(int npcId): + Window(_("Sell")), + mNpcId(npcId), mMaxItems(0), mAmountItems(0), mNick("") +{ + init(); +} + +SellDialog::SellDialog(std::string nick): + Window(_("Sell")), + mNpcId(-1), mMaxItems(0), mAmountItems(0), mNick(nick) +{ + init(); +} + +void SellDialog::init() +{ + setWindowName("Sell"); + //setupWindow->registerWindowForReset(this); + setResizable(true); + setCloseButton(true); + setMinWidth(260); + setMinHeight(230); + setDefaultSize(260, 230, ImageRect::CENTER); + + // Create a ShopItems instance, that is aware of duplicate entries. + mShopItems = new ShopItems(true); + + mShopItemList = new ShopListBox(mShopItems, mShopItems); + mScrollArea = new ScrollArea(mShopItemList); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mSlider = new Slider(1.0); + + mQuantityLabel = new Label(strprintf("%d / %d", mAmountItems, mMaxItems)); + mQuantityLabel->setAlignment(gcn::Graphics::CENTER); + mMoneyLabel = new Label(strprintf(_("Price: %s / Total: %s"), + "", "")); + + mIncreaseButton = new Button(_("+"), "inc", this); + mDecreaseButton = new Button(_("-"), "dec", this); + mSellButton = new Button(_("Sell"), "sell", this); + mQuitButton = new Button(_("Quit"), "quit", this); + mAddMaxButton = new Button(_("Max"), "max", this); + + mDecreaseButton->adjustSize(); + mDecreaseButton->setWidth(mIncreaseButton->getWidth()); + + mIncreaseButton->setEnabled(false); + mDecreaseButton->setEnabled(false); + mSellButton->setEnabled(false); + mSlider->setEnabled(false); + + mShopItemList->setPriceCheck(false); + mShopItemList->addSelectionListener(this); + mSlider->setActionEventId("slider"); + mSlider->addActionListener(this); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mScrollArea, 8, 5).setPadding(3); + place(0, 5, mDecreaseButton); + place(1, 5, mSlider, 3); + place(4, 5, mIncreaseButton); + place(5, 5, mQuantityLabel, 2); + place(7, 5, mAddMaxButton); + place(0, 6, mMoneyLabel, 8); + place(6, 7, mSellButton); + place(7, 7, mQuitButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); +} + +SellDialog::~SellDialog() +{ + delete mShopItems; + mShopItems = 0; + + instances.remove(this); +} + +void SellDialog::reset() +{ + mShopItems->clear(); + mSlider->setValue(0); + + // Reset previous selected item to prevent failing asserts + mShopItemList->setSelected(-1); + + updateButtonsAndLabels(); +} + +void SellDialog::addItem(const Item *item, int price) +{ + if (!item) + return; + + mShopItems->addItem(item->getInvIndex(), item->getId(), + item->getQuantity(), price); + + mShopItemList->adjustSize(); +} + +void SellDialog::addItem(int id, int amount, int price) +{ + mShopItems->addItem(id, amount, price); + mShopItemList->adjustSize(); +} + + +void SellDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "quit") + { + close(); + return; + } + + int selectedItem = mShopItemList->getSelected(); + + // The following actions require a valid item selection + if (selectedItem == -1 || + selectedItem >= static_cast<int>(mShopItems->getNumberOfElements())) + { + return; + } + + if (event.getId() == "slider") + { + mAmountItems = static_cast<int>(mSlider->getValue()); + updateButtonsAndLabels(); + } + else if (event.getId() == "inc" && mAmountItems < mMaxItems) + { + mAmountItems++; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "dec" && mAmountItems > 1) + { + mAmountItems--; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "max") + { + mAmountItems = mMaxItems; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "sell" && mAmountItems > 0 + && mAmountItems <= mMaxItems) + { + if (mNpcId != -1) + { + // Attempt sell + ShopItem *item = mShopItems->at(selectedItem); + int sellCount, itemIndex; + mPlayerMoney += + mAmountItems * mShopItems->at(selectedItem)->getPrice(); + mMaxItems -= mAmountItems; + while (mAmountItems > 0) + { + // This order is important, item->getCurrentInvIndex() would return + // the inventory index of the next Duplicate otherwise. + itemIndex = item->getCurrentInvIndex(); + sellCount = item->sellCurrentDuplicate(mAmountItems); + + // For Manaserv, the Item id is to be given as index. + if ((Net::getNetworkType() == ServerInfo::MANASERV)) + itemIndex = item->getId(); + + Net::getNpcHandler()->sellItem(mNpcId, itemIndex, sellCount); + mAmountItems -= sellCount; + } + + mPlayerMoney += + mAmountItems * mShopItems->at(selectedItem)->getPrice(); + mAmountItems = 1; + mSlider->setValue(0); + + if (!mMaxItems) + { + // All were sold + mShopItemList->setSelected(-1); + delete mShopItems->at(selectedItem); + mShopItems->erase(selectedItem); + + gcn::Rectangle scroll; + scroll.y = mShopItemList->getRowHeight() * (selectedItem + 1); + scroll.height = mShopItemList->getRowHeight(); + mShopItemList->showPart(scroll); + } + } + else + { + ShopItem *item = mShopItems->at(selectedItem); + + Net::getBuySellHandler()->sendSellRequest(mNick, + item, mAmountItems); + + if (tradeWindow) + tradeWindow->addAutoItem(mNick, item, mAmountItems); + } + } +} + +void SellDialog::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + // Reset amount of items and update labels + mAmountItems = 1; + mSlider->setValue(0); + + updateButtonsAndLabels(); + mSlider->gcn::Slider::setScale(1, mMaxItems); +} + +void SellDialog::setMoney(int amount) +{ + mPlayerMoney = amount; + mShopItemList->setPlayersMoney(amount); +} + +void SellDialog::updateButtonsAndLabels() +{ + int selectedItem = mShopItemList->getSelected(); + int income = 0; + + if (selectedItem > -1 && mShopItems->at(selectedItem)) + { + mMaxItems = mShopItems->at(selectedItem)->getQuantity(); + if (mAmountItems > mMaxItems) + mAmountItems = mMaxItems; + + income = mAmountItems * mShopItems->at(selectedItem)->getPrice(); + } + else + { + mMaxItems = 0; + mAmountItems = 0; + } + + // Update Buttons and slider + mSellButton->setEnabled(mAmountItems > 0); + mDecreaseButton->setEnabled(mAmountItems > 1); + mIncreaseButton->setEnabled(mAmountItems < mMaxItems); + mSlider->setEnabled(mMaxItems > 1); + + // Update the quantity and money labels + mQuantityLabel->setCaption(strprintf("%d / %d", mAmountItems, mMaxItems)); + mMoneyLabel->setCaption(strprintf(_("Price: %s / Total: %s"), + Units::formatCurrency(income).c_str(), + Units::formatCurrency(mPlayerMoney + income).c_str())); +} + +void SellDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (visible) + { + if (mShopItemList) + mShopItemList->requestFocus(); + } + else + { + scheduleDelete(); + } +} + +void SellDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + (*it)->close(); +} diff --git a/src/gui/sell.h b/src/gui/sell.h new file mode 100644 index 000000000..fd6245eb8 --- /dev/null +++ b/src/gui/sell.h @@ -0,0 +1,145 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SELL_H +#define SELL_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/selectionlistener.hpp> + +#include <SDL_types.h> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Item; +class ShopItems; +class ShopListBox; + +/** + * The sell dialog. + * + * \ingroup Interface + */ +class SellDialog : public Window, gcn::ActionListener, gcn::SelectionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + SellDialog(int npcId); + + /** + * Constructor. + */ + SellDialog(std::string nick); + + /** + * Destructor + */ + virtual ~SellDialog(); + + void init(); + + /** + * Resets the dialog, clearing inventory. + */ + void reset(); + + /** + * Adds an item to the inventory. + */ + void addItem(const Item *item, int price); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Updates labels according to selected item. + * + * @see SelectionListener::selectionChanged + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Gives Player's Money amount + */ + void setMoney(int amount); + + /** + * Sets the visibility of this window. + */ + void setVisible(bool visible); + + void addItem(int id, int amount, int price); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list<SellDialog*> DialogList; + static DialogList instances; + + /** + * Updates the state of buttons and labels. + */ + void updateButtonsAndLabels(); + + int mNpcId; + + gcn::Button *mSellButton; + gcn::Button *mQuitButton; + gcn::Button *mAddMaxButton; + gcn::Button *mIncreaseButton; + gcn::Button *mDecreaseButton; + ShopListBox *mShopItemList; + gcn::ScrollArea *mScrollArea; + gcn::Label *mMoneyLabel; + gcn::Label *mQuantityLabel; + gcn::Slider *mSlider; + + ShopItems *mShopItems; + int mPlayerMoney; + + int mMaxItems; + int mAmountItems; + + std::string mNick; +}; + +#endif diff --git a/src/gui/serverdialog.cpp b/src/gui/serverdialog.cpp new file mode 100644 index 000000000..c7e1d0f94 --- /dev/null +++ b/src/gui/serverdialog.cpp @@ -0,0 +1,768 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/serverdialog.h" + +#include "chatlog.h" +#include "client.h" +#include "configuration.h" +#include "gui.h" +#include "log.h" +#include "main.h" + +#include "gui/login.h" +#include "gui/okdialog.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/textfield.h" + +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" +#include "widgets/dropdown.h" + +#include <guichan/font.hpp> + +#include <cstdlib> +#include <iostream> +#include <string> + +static const int MAX_SERVERLIST = 15; + +static std::string serverTypeToString(ServerInfo::Type type) +{ + switch (type) + { + case ServerInfo::TMWATHENA: + return "TmwAthena"; + case ServerInfo::MANASERV: + return "ManaServ"; + default: + return ""; + } +} + +static unsigned short defaultPortForServerType(ServerInfo::Type type) +{ + switch (type) + { + default: + case ServerInfo::TMWATHENA: + return 6901; + case ServerInfo::MANASERV: + return 9601; + } +} + +ServersListModel::ServersListModel(ServerInfos *servers, ServerDialog *parent): + mServers(servers), + mVersionStrings(servers->size(), VersionString(0, "")), + mParent(parent) +{ +} + +int ServersListModel::getNumberOfElements() +{ + MutexLocker lock = mParent->lock(); + return static_cast<int>(mServers->size()); +} + +std::string ServersListModel::getElementAt(int elementIndex) +{ + MutexLocker lock = mParent->lock(); + const ServerInfo &server = mServers->at(elementIndex); + std::string myServer; + myServer += server.hostname; + myServer += ":"; + myServer += toString(server.port); + return myServer; +} + +void ServersListModel::setVersionString(int index, const std::string &version) +{ + if (version.empty()) + { + mVersionStrings[index] = VersionString(0, ""); + } + else + { + int width = gui->getFont()->getWidth(version); + mVersionStrings[index] = VersionString(width, version); + } +} + +std::string TypeListModel::getElementAt(int elementIndex) +{ + if (elementIndex == 0) + return "TmwAthena"; + else if (elementIndex == 1) + return "ManaServ"; + else + return "Unknown"; +} + +class ServersListBox : public ListBox +{ +public: + ServersListBox(ServersListModel *model): + ListBox(model) + { + } + + void draw(gcn::Graphics *graphics) + { + if (!mListModel) + return; + + ServersListModel *model = static_cast<ServersListModel*>(mListModel); + + updateAlpha(); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast<int>(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + const int height = getRowHeight(); + const gcn::Color unsupported = + Theme::getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED, + static_cast<int>(mAlpha * 255.0f)); + + // Draw filled rectangle around the selected list element + if (mSelected >= 0) + { + graphics->fillRectangle(gcn::Rectangle(0, height * mSelected, + getWidth(), height)); + } + + // Draw the list elements + for (int i = 0, y = 0; i < model->getNumberOfElements(); + ++i, y += height) + { + ServerInfo info = model->getServer(i); + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + int top; + + if (!info.name.empty()) + { + graphics->setFont(boldFont); + graphics->drawText(info.name, 2, y); + top = y + height / 2; + } + else + { + top = y + height / 4; + } + + graphics->setFont(getFont()); + + graphics->drawText(model->getElementAt(i), 2, top); + + if (info.version.first > 0) + { + graphics->setColor(unsupported); + + graphics->drawText(info.version.second, + getWidth() - info.version.first - 2, top); + } + } + } + + unsigned int getRowHeight() const + { + return 2 * getFont()->getHeight(); + } +}; + + +ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir): + Window(_("Choose Your Server")), + mDir(dir), +// mDownloadStatus(DOWNLOADING_PREPARING), + mDownloadStatus(DOWNLOADING_UNKNOWN), + mDownload(0), + mDownloadProgress(-1.0f), + mServers(ServerInfos()), + mServerInfo(serverInfo) +{ + if (isSafeMode) + setCaption("Choose Your Server *** SAFE MODE ***"); + + setWindowName("ServerDialog"); + + Label *serverLabel = new Label(_("Server:")); + Label *portLabel = new Label(_("Port:")); + Label *typeLabel = new Label(_("Server type:")); + mServerNameField = new TextField(mServerInfo->hostname); + mPortField = new TextField(toString(mServerInfo->port)); + + loadCustomServers(); + + mServersListModel = new ServersListModel(&mServers, this); + + mServersList = new ServersListBox(mServersListModel); + mServersList->addMouseListener(this); + + ScrollArea *usedScroll = new ScrollArea(mServersList); + usedScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mTypeListModel = new TypeListModel(); + mTypeField = new DropDown(mTypeListModel); + mTypeField->setSelected((serverInfo->type == ServerInfo::MANASERV) ? + 1 : 0); + + mDescription = new Label(std::string()); + + mQuitButton = new Button(_("Quit"), "quit", this); + mLoadButton = new Button(_("Load"), "load", this); + mConnectButton = new Button(_("Connect"), "connect", this); + mManualEntryButton = new Button(_("Custom Server"), "addEntry", this); + mDeleteButton = new Button(_("Delete"), "remove", this); + + mServerNameField->setActionEventId("connect"); + mPortField->setActionEventId("connect"); + + mServerNameField->addActionListener(this); + mPortField->addActionListener(this); + mManualEntryButton->addActionListener(this); + mServersList->addSelectionListener(this); + usedScroll->setVerticalScrollAmount(0); + + place(0, 0, serverLabel); + place(1, 0, mServerNameField, 5).setPadding(3); + place(0, 1, portLabel); + place(1, 1, mPortField, 5).setPadding(3); + place(0, 2, typeLabel); + place(1, 2, mTypeField, 5).setPadding(3); + place(0, 3, usedScroll, 6, 5).setPadding(3); + place(0, 8, mDescription, 6); + place(0, 9, mManualEntryButton); + place(1, 9, mDeleteButton); + place(2, 9, mLoadButton); + place(4, 9, mQuitButton); + place(5, 9, mConnectButton); + + // Make sure the list has enough height + getLayout().setRowHeight(3, 80); + +/* + reflowLayout(400, 300); + setDefaultSize(400, 300, ImageRect::CENTER); +*/ + // Do this manually instead of calling reflowLayout so we can enforce a + // minimum width. + int width = 0, height = 0; + getLayout().reflow(width, height); + if (width < 400) + { + width = 400; + getLayout().reflow(width, height); + } + + setContentSize(width, height); + + setMinWidth(getWidth()); + setMinHeight(getHeight()); + setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER); + + setResizable(true); + addKeyListener(this); + + loadWindowState(); + + setFieldsReadOnly(true); + mServersList->setSelected(0); // Do this after for the Delete button + setVisible(true); + + if (mServerNameField->getText().empty()) + { + mServerNameField->requestFocus(); + } + else + { + if (mPortField->getText().empty()) + mPortField->requestFocus(); + else + mConnectButton->requestFocus(); + } + + loadServers(false); + + if (mServers.size() == 0) + downloadServerList(); +} + +ServerDialog::~ServerDialog() +{ + if (mDownload) + { + mDownload->cancel(); + delete mDownload; + mDownload = 0; + } + delete mServersListModel; + mServersListModel = 0; + delete mTypeListModel; + mTypeListModel = 0; +} + +void ServerDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok") + { + // Give focus back to the server dialog. + mServerNameField->requestFocus(); + } + else if (event.getId() == "connect") + { + // Check login + if (mServerNameField->getText().empty() + || mPortField->getText().empty()) + { + OkDialog *dlg = new OkDialog(_("Error"), + _("Please type both the address and the port of a server.")); + dlg->addActionListener(this); + } + else + { + if (mDownload) + mDownload->cancel(); + + mQuitButton->setEnabled(false); + mConnectButton->setEnabled(false); + mLoadButton->setEnabled(false); + + mServerInfo->hostname = mServerNameField->getText(); + mServerInfo->port = static_cast<short>( + atoi(mPortField->getText().c_str())); + + switch (mTypeField->getSelected()) + { + case 0: + mServerInfo->type = ServerInfo::TMWATHENA; + break; + case 1: + mServerInfo->type = ServerInfo::MANASERV; + break; + default: + mServerInfo->type = ServerInfo::UNKNOWN; + } + + // Save the selected server + mServerInfo->save = true; + + if (chatLogger) + chatLogger->setServerName(mServerInfo->hostname); + + saveCustomServers(*mServerInfo); + + if (!LoginDialog::savedPasswordKey.empty()) + { + if (mServerInfo->hostname != LoginDialog::savedPasswordKey) + LoginDialog::savedPassword = ""; + } + + Client::setState(STATE_CONNECT_SERVER); + } + } + else if (event.getId() == "quit") + { + if (mDownload) + mDownload->cancel(); + Client::setState(STATE_FORCE_QUIT); + } + else if (event.getId() == "load") + { + downloadServerList(); + } + else if (event.getId() == "addEntry") + { + setFieldsReadOnly(false); + } + else if (event.getId() == "remove") + { + int index = mServersList->getSelected(); + mServersList->setSelected(0); + mServers.erase(mServers.begin() + index); + + saveCustomServers(); + } +} + +void ServerDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + Client::setState(STATE_EXIT); + else if (key.getValue() == Key::ENTER) + action(gcn::ActionEvent(NULL, mConnectButton->getActionEventId())); +} + +void ServerDialog::valueChanged(const gcn::SelectionEvent &) +{ + const int index = mServersList->getSelected(); + if (index == -1) + { + mDeleteButton->setEnabled(false); + return; + } + + // Update the server and post fields according to the new selection + const ServerInfo &myServer = mServersListModel->getServer(index); + mDescription->setCaption(myServer.description); + mServerNameField->setText(myServer.hostname); + mPortField->setText(toString(myServer.port)); + switch (myServer.type) + { + case ServerInfo::TMWATHENA: + case ServerInfo::UNKNOWN: + default: + mTypeField->setSelected(0); + break; + case ServerInfo::MANASERV: + mTypeField->setSelected(1); + break; + } + setFieldsReadOnly(true); + + mDeleteButton->setEnabled(myServer.save); +} + +void ServerDialog::mouseClicked(gcn::MouseEvent &mouseEvent) +{ + if (mouseEvent.getClickCount() == 2 && + mouseEvent.getSource() == mServersList) + { + action(gcn::ActionEvent(mConnectButton, + mConnectButton->getActionEventId())); + } +} + +void ServerDialog::logic() +{ + { + MutexLocker lock(&mMutex); + if (mDownloadStatus == DOWNLOADING_COMPLETE) + { + mDownloadStatus = DOWNLOADING_OVER; + + mDescription->setCaption(std::string()); + } + else if (mDownloadStatus == DOWNLOADING_IN_PROGRESS) + { + mDescription->setCaption(strprintf(_("Downloading server list..." + "%2.2f%%"), + mDownloadProgress * 100)); + } + else if (mDownloadStatus == DOWNLOADING_IDLE) + { + mDescription->setCaption(_("Waiting for server...")); + } + else if (mDownloadStatus == DOWNLOADING_PREPARING) + { + mDescription->setCaption(_("Preparing download")); + } + else if (mDownloadStatus == DOWNLOADING_ERROR) + { + mDescription->setCaption(_("Error retreiving server list!")); + } + } + + Window::logic(); +} + +void ServerDialog::setFieldsReadOnly(bool readOnly) +{ + if (!readOnly) + { + mDescription->setCaption(std::string()); + mServersList->setSelected(-1); + + mServerNameField->setText(std::string()); + mPortField->setText(std::string("6901")); + + mServerNameField->requestFocus(); + } + + mManualEntryButton->setEnabled(readOnly); + mDeleteButton->setEnabled(false); + mLoadButton->setEnabled(readOnly); + mDescription->setVisible(readOnly); + + mServerNameField->setEnabled(!readOnly); + mPortField->setEnabled(!readOnly); + mTypeField->setEnabled(!readOnly); +} + +void ServerDialog::downloadServerList() +{ + // Try to load the configuration value for the onlineServerList + std::string listFile = branding.getStringValue("onlineServerList"); + + if (listFile.empty()) + listFile = config.getStringValue("onlineServerList"); + + // Fall back to manasource.org when neither branding nor config set it + if (listFile.empty()) + listFile = "http://manasource.org/serverlist.xml"; + + mDownload = new Net::Download(this, listFile, &downloadUpdate); + mDownload->setFile(mDir + "/serverlist.xml"); + mDownload->start(); +} + +void ServerDialog::loadServers(bool addNew) +{ + XML::Document doc(mDir + "/serverlist.xml", false); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "serverlist")) + { + logger->log1("Error loading server list!"); + return; + } + + int version = XML::getProperty(rootNode, "version", 0); + if (version != 1) + { + logger->log("Error: unsupported online server list version: %d", + version); + return; + } + + for_each_xml_child_node(serverNode, rootNode) + { + if (!xmlStrEqual(serverNode->name, BAD_CAST "server")) + continue; + + ServerInfo server; + + std::string type = XML::getProperty(serverNode, "type", "unknown"); + + server.type = ServerInfo::parseType(type); + + // Ignore unknown server types + if (server.type == ServerInfo::UNKNOWN) + { + logger->log("Ignoring server entry with unknown type: %s", + type.c_str()); + continue; + } + + server.name = XML::getProperty(serverNode, "name", std::string()); + + std::string version = XML::getProperty(serverNode, "minimumVersion", + std::string()); + + bool meetsMinimumVersion = (compareStrI(version, PACKAGE_VERSION) + <= 0); + + // For display in the list + if (meetsMinimumVersion) + version.clear(); + else if (version.empty()) + version = _("requires a newer version"); + else + version = strprintf(_("requires v%s"), version.c_str()); + + for_each_xml_child_node(subNode, serverNode) + { + if (xmlStrEqual(subNode->name, BAD_CAST "connection")) + { + server.hostname = XML::getProperty(subNode, "hostname", ""); + server.port = static_cast<short unsigned>( + XML::getProperty(subNode, "port", 0)); + + if (server.port == 0) + { + // If no port is given, use the default for the given type + server.port = defaultPortForServerType(server.type); + } + } + else if (xmlStrEqual(subNode->name, BAD_CAST "description")) + { + server.description = + (const char*)subNode->xmlChildrenNode->content; + } + } + + server.version.first = gui->getFont()->getWidth(version); + server.version.second = version; + + MutexLocker lock(&mMutex); + // Add the server to the local list if it's not already present + bool found = false; + for (unsigned int i = 0; i < mServers.size(); i++) + { + if (mServers[i] == server) + { + // Use the name listed in the server list + mServers[i].name = server.name; + mServers[i].version = server.version; + mServersListModel->setVersionString(i, version); + found = true; + break; + } + } + + if (!found && addNew) + mServers.push_back(server); + } +} + +void ServerDialog::loadCustomServers() +{ + for (int i = 0; i < MAX_SERVERLIST; ++i) + { + const std::string index = toString(i); + const std::string nameKey = "MostUsedServerName" + index; + const std::string typeKey = "MostUsedServerType" + index; + const std::string portKey = "MostUsedServerPort" + index; + + ServerInfo server; + server.hostname = config.getValue(nameKey, ""); + server.type = ServerInfo::parseType(config.getValue(typeKey, "")); + + const int defaultPort = defaultPortForServerType(server.type); + server.port = static_cast<unsigned short>( + config.getValue(portKey, defaultPort)); + + // Stop on the first invalid server + if (!server.isValid()) + break; + + server.save = true; + mServers.push_back(server); + } +} + +void ServerDialog::saveCustomServers(const ServerInfo ¤tServer) +{ + // Make sure the current server is mentioned first + if (currentServer.isValid()) + { + ServerInfos::iterator i, i_end = mServers.end(); + for (i = mServers.begin(); i != i_end; ++i) + { + if (*i == currentServer) + { + mServers.erase(i); + break; + } + } + mServers.insert(mServers.begin(), currentServer); + } + + int savedServerCount = 0; + + for (unsigned i = 0; + i < mServers.size() && savedServerCount < MAX_SERVERLIST; ++i) + { + const ServerInfo &server = mServers.at(i); + + // Only save servers that were loaded from settings + if (!(server.save && server.isValid())) + continue; + + const std::string index = toString(savedServerCount); + const std::string nameKey = "MostUsedServerName" + index; + const std::string typeKey = "MostUsedServerType" + index; + const std::string portKey = "MostUsedServerPort" + index; + + config.setValue(nameKey, toString(server.hostname)); + config.setValue(typeKey, serverTypeToString(server.type)); + config.setValue(portKey, toString(server.port)); + ++savedServerCount; + } + + // Insert an invalid entry at the end to make the loading stop there + if (savedServerCount < MAX_SERVERLIST) + config.setValue("MostUsedServerName" + toString(savedServerCount), ""); +} + +int ServerDialog::downloadUpdate(void *ptr, DownloadStatus status, + size_t total, size_t remaining) +{ + if (status == DOWNLOAD_STATUS_CANCELLED) + return -1; + + ServerDialog *sd = reinterpret_cast<ServerDialog*>(ptr); + bool finished = false; + + if (!sd->mDownload) + return -1; + + if (status == DOWNLOAD_STATUS_COMPLETE) + { + finished = true; + } + else if (status < 0) + { + logger->log("Error retreiving server list: %s\n", + sd->mDownload->getError()); + sd->mDownloadStatus = DOWNLOADING_ERROR; + } + else + { + float progress = static_cast<float>(remaining); + if (total) + progress /= static_cast<float>(total); + + if (progress != progress) + { + progress = 0.0f; // check for NaN + } + else if (progress < 0.0f) + { + progress = 0.0f; // no idea how this could ever happen, + // but why not check for it anyway. + } + else if (progress > 1.0f) + { + progress = 1.0f; + } + + MutexLocker lock(&sd->mMutex); + sd->mDownloadStatus = DOWNLOADING_IN_PROGRESS; + sd->mDownloadProgress = progress; + } + + if (finished) + { + sd->loadServers(); + + MutexLocker lock(&sd->mMutex); + sd->mDownloadStatus = DOWNLOADING_COMPLETE; + } + + return 0; +} diff --git a/src/gui/serverdialog.h b/src/gui/serverdialog.h new file mode 100644 index 000000000..b9a67e246 --- /dev/null +++ b/src/gui/serverdialog.h @@ -0,0 +1,204 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SERVERDIALOG_H +#define SERVERDIALOG_H + +#include "gui/widgets/window.h" + +#include "net/download.h" +#include "net/serverinfo.h" + +#include "utils/mutex.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> +#include <guichan/listmodel.hpp> +#include <guichan/mouselistener.hpp> +#include <guichan/selectionlistener.hpp> + +#include <string> +#include <vector> + +class Button; +class Label; +class ListBox; +class ServerDialog; +class TextField; +class DropDown; + +/** + * Server and Port List Model + */ +class ServersListModel : public gcn::ListModel +{ + public: + typedef std::pair<int, std::string> VersionString; + + ServersListModel(ServerInfos *servers, ServerDialog *parent); + + /** + * Used to get number of line in the list + */ + int getNumberOfElements(); + + /** + * Used to get an element from the list + */ + std::string getElementAt(int elementIndex); + + /** + * Used to get the corresponding Server struct + */ + const ServerInfo &getServer(int elementIndex) const + { return mServers->at(elementIndex); } + + void setVersionString(int index, const std::string &version); + + private: + typedef std::vector<VersionString> VersionStrings; + + ServerInfos *mServers; + VersionStrings mVersionStrings; + ServerDialog *mParent; +}; + +/** + * Server Type List Model + */ +class TypeListModel : public gcn::ListModel +{ + public: + TypeListModel() {} + + /** + * Used to get number of line in the list + */ + int getNumberOfElements() + { return 2; } + + /** + * Used to get an element from the list + */ + std::string getElementAt(int elementIndex); +}; + + +/** + * The server choice dialog. + * + * \ingroup Interface + */ +class ServerDialog : public Window, + public gcn::ActionListener, + public gcn::KeyListener, + public gcn::SelectionListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + ServerDialog(ServerInfo *serverInfo, const std::string &dir); + + /** + * Destructor + */ + ~ServerDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + /** + * Called when the selected value changed in the servers list box. + */ + void valueChanged(const gcn::SelectionEvent &event); + + void mouseClicked(gcn::MouseEvent &mouseEvent); + + void logic(); + + protected: + friend class ServersListModel; + MutexLocker lock() + { return MutexLocker(&mMutex); } + + private: + /** + * Called to load a list of available server from an online xml file. + */ + void downloadServerList(); + void loadServers(bool addNew = true); + + void loadCustomServers(); + void saveCustomServers(const ServerInfo ¤tServer = ServerInfo()); + + static int downloadUpdate(void *ptr, DownloadStatus status, + size_t total, size_t remaining); + + void setFieldsReadOnly(bool readOnly); + + TextField *mServerNameField; + TextField *mPortField; + Label *mDescription; + Button *mQuitButton; + Button *mConnectButton; + Button *mManualEntryButton; + Button *mDeleteButton; + Button *mLoadButton; + + ListBox *mServersList; + ServersListModel *mServersListModel; + + DropDown *mTypeField; + TypeListModel *mTypeListModel; + + const std::string &mDir; + + enum ServerDialogDownloadStatus + { + DOWNLOADING_UNKNOWN = 0, + DOWNLOADING_ERROR, + DOWNLOADING_PREPARING, + DOWNLOADING_IDLE, + DOWNLOADING_IN_PROGRESS, + DOWNLOADING_COMPLETE, + DOWNLOADING_OVER + }; + + /** Status of the current download. */ + ServerDialogDownloadStatus mDownloadStatus; + + Net::Download *mDownload; + + Mutex mMutex; + float mDownloadProgress; + + ServerInfos mServers; + ServerInfo *mServerInfo; +}; + +#endif diff --git a/src/gui/setup.cpp b/src/gui/setup.cpp new file mode 100644 index 000000000..8206b0e7e --- /dev/null +++ b/src/gui/setup.cpp @@ -0,0 +1,178 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "setup.h" + +#include "configuration.h" +#include "main.h" + +#include "gui/setup_audio.h" +#include "gui/setup_colors.h" +#include "gui/setup_joystick.h" +#include "gui/setup_other.h" +#include "gui/setup_theme.h" +#include "gui/setup_keyboard.h" +#include "gui/setup_players.h" +#include "gui/setup_video.h" +#include "gui/setup_chat.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/tabbedarea.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +extern Window *statusWindow; + +Setup::Setup(): + Window(_("Setup")) +{ + setCloseButton(true); + setResizable(true); + + int width = 620; + int height = 450; + + if (config.getIntValue("screenwidth") >= 730) + width += 100; + + setContentSize(width, height); + //setMaxHeight(height); + + static const char *buttonNames[] = + { + N_("Apply"), + N_("Cancel"), + N_("Store"), + N_("Reset Windows"), + 0 + }; + int x = width; + for (const char **curBtn = buttonNames; *curBtn; ++curBtn) + { + Button *btn = new Button(gettext(*curBtn), *curBtn, this); + x -= btn->getWidth() + 5; + btn->setPosition(x, height - btn->getHeight() - 5); + add(btn); + + // Store this button, as it needs to be enabled/disabled + if (!strcmp(*curBtn, "Reset Windows")) + mResetWindows = btn; + } + + mPanel = new TabbedArea; + mPanel->setDimension(gcn::Rectangle(5, 5, width - 10, height - 40)); + + mTabs.push_back(new Setup_Video); + mTabs.push_back(new Setup_Audio); + mTabs.push_back(new Setup_Joystick); + mTabs.push_back(new Setup_Keyboard); + mTabs.push_back(new Setup_Colors); + mTabs.push_back(new Setup_Chat); + mTabs.push_back(new Setup_Players); + mTabs.push_back(new Setup_Theme); + mTabs.push_back(new Setup_Other); + + for (std::list<SetupTab*>::iterator i = mTabs.begin(), i_end = mTabs.end(); + i != i_end; ++i) + { + SetupTab *tab = *i; + mPanel->addTab(tab->getName(), tab); + } + + add(mPanel); + + Label *version = new Label(FULL_VERSION); +// version->setPosition(9, height - version->getHeight() - 9); + if (mResetWindows) + { + version->setPosition(9, + height - version->getHeight() - mResetWindows->getHeight() - 9); + } + else + { + version->setPosition(9, height - version->getHeight() - 30); + } + add(version); + + center(); + + setInGame(false); +} + +Setup::~Setup() +{ + delete_all(mTabs); +} + +void Setup::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "Apply") + { + setVisible(false); + for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::apply)); + } + else if (event.getId() == "Cancel") + { + setVisible(false); + for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::cancel)); + } + else if (event.getId() == "Store") + { + config.write(); + serverConfig.write(); + } + else if (event.getId() == "Reset Windows") + { + // Bail out if this action happens to be activated before the windows + // are created (though it should be disabled then) + if (!statusWindow) + return; + + for (std::list<Window*>::iterator it = mWindowsToReset.begin(); + it != mWindowsToReset.end(); it++) + { + (*it)->resetToDefaultSize(); + } + } +} + +void Setup::setInGame(bool inGame) +{ + mResetWindows->setEnabled(inGame); +} + +void Setup::externalUpdate() +{ + for (std::list<SetupTab*>::iterator it = mTabs.begin(); + it != mTabs.end(); it++) + { + (*it)->externalUpdated(); + } +} + +void Setup::registerWindowForReset(Window *window) +{ + mWindowsToReset.push_back(window); +} + +Setup *setupWindow; diff --git a/src/gui/setup.h b/src/gui/setup.h new file mode 100644 index 000000000..1f10a2a0d --- /dev/null +++ b/src/gui/setup.h @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SETUP_H +#define SETUP_H + +#include "gui/widgets/tabbedarea.h" + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +#include <list> + +class SetupTab; + +/** + * The setup dialog. Displays several tabs for configuring different aspects + * of the game. + * + * @see Setup_Audio + * @see Setup_Colors + * @see Setup_Joystick + * @see Setup_Keyboard + * @see Setup_Players + * @see Setup_Video + * + * \ingroup GUI + */ +class Setup : public Window, public gcn::ActionListener +{ + public: + Setup(); + ~Setup(); + + /** + * Event handling method. + */ + void action(const gcn::ActionEvent &event); + + /** + * Enables the reset button when in game. + */ + void setInGame(bool inGame); + + void externalUpdate(); + + void registerWindowForReset(Window *window); + + void clearWindowsForReset() + { mWindowsToReset.clear(); } + + private: + std::list<SetupTab*> mTabs; + std::list<Window*> mWindowsToReset; + gcn::Button *mResetWindows; + TabbedArea *mPanel; +}; + +extern Setup* setupWindow; + +#endif diff --git a/src/gui/setup_audio.cpp b/src/gui/setup_audio.cpp new file mode 100644 index 000000000..66a7a4e88 --- /dev/null +++ b/src/gui/setup_audio.cpp @@ -0,0 +1,179 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/setup_audio.h" + +#include "configuration.h" +#include "log.h" +#include "sound.h" + +#include "gui/okdialog.h" + +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/slider.h" + +#include "utils/gettext.h" + +Setup_Audio::Setup_Audio(): + mMusicVolume(config.getIntValue("musicVolume")), + mSfxVolume(config.getIntValue("sfxVolume")), + mAudioEnabled(config.getBoolValue("sound")), + mGameSoundEnabled(config.getBoolValue("playBattleSound")), + mGuiSoundEnabled(config.getBoolValue("playGuiSound")), + mMusicEnabled(config.getBoolValue("playMusic")), + mMumbleEnabled(config.getBoolValue("enableMumble")), + mDownloadEnabled(config.getBoolValue("download-music")), + mAudioCheckBox(new CheckBox(_("Enable Audio"), mAudioEnabled)), + mGameSoundCheckBox(new CheckBox(_("Enable game sfx"), mGameSoundEnabled)), + mGuiSoundCheckBox(new CheckBox(_("Enable gui sfx"), mGuiSoundEnabled)), + mMusicCheckBox(new CheckBox(_("Enable music"), mMusicEnabled)), + mMumbleCheckBox(new CheckBox(_("Enable mumble voice chat"), + mMumbleEnabled)), + mDownloadMusicCheckBox(new CheckBox(_("Download music"), + mDownloadEnabled)), + mSfxSlider(new Slider(0, sound.getMaxVolume())), + mMusicSlider(new Slider(0, sound.getMaxVolume())) +{ + setName(_("Audio")); + setDimension(gcn::Rectangle(0, 0, 250, 200)); + + gcn::Label *sfxLabel = new Label(_("Sfx volume")); + gcn::Label *musicLabel = new Label(_("Music volume")); + + mSfxSlider->setActionEventId("sfx"); + mMusicSlider->setActionEventId("music"); + + mSfxSlider->addActionListener(this); + mMusicSlider->addActionListener(this); + + mAudioCheckBox->setPosition(10, 10); + + mSfxSlider->setValue(mSfxVolume); + mMusicSlider->setValue(mMusicVolume); + + mSfxSlider->setWidth(90); + mMusicSlider->setWidth(90); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mAudioCheckBox); + place(0, 1, mMusicCheckBox); + place(0, 2, mGameSoundCheckBox); + place(0, 3, mGuiSoundCheckBox); + place(0, 4, mSfxSlider); + place(1, 4, sfxLabel); + place(0, 5, mMusicSlider); + place(1, 5, musicLabel); + place(0, 6, mMumbleCheckBox); + place(0, 7, mDownloadMusicCheckBox); + + setDimension(gcn::Rectangle(0, 0, 365, 280)); +} + +void Setup_Audio::apply() +{ + mAudioEnabled = mAudioCheckBox->isSelected(); + mGameSoundEnabled = mGameSoundCheckBox->isSelected(); + mGuiSoundEnabled = mGuiSoundCheckBox->isSelected(); + mMusicEnabled = mMusicCheckBox->isSelected(); + mMumbleEnabled = mMumbleCheckBox->isSelected(); + mDownloadEnabled = mDownloadMusicCheckBox->isSelected(); + mSfxVolume = config.getIntValue("sfxVolume"); + mMusicVolume = config.getIntValue("musicVolume"); + + config.setValue("sound", mAudioEnabled); + config.setValue("playBattleSound", mGameSoundEnabled); + config.setValue("playGuiSound", mGuiSoundEnabled); + config.setValue("playMusic", mMusicEnabled); + + config.setValue("enableMumble", mMumbleEnabled); + + // Display a message if user has selected to download music, + // And if downloadmusic is not already enabled + if (mDownloadEnabled && !config.getBoolValue("download-music")) + { + new OkDialog(_("Notice"), _("You may have to restart your client " + "if you want to download new music")); + } + config.setValue("download-music", mDownloadEnabled); + + if (mAudioEnabled) + { + try + { + sound.init(); + } + catch (const char *err) + { + new OkDialog(_("Sound Engine"), err); + logger->log("Warning: %s", err); + } + } + else + { + sound.close(); + } +} + +void Setup_Audio::cancel() +{ + mAudioCheckBox->setSelected(mAudioEnabled); + mGameSoundCheckBox->setSelected(mGameSoundEnabled); + mGuiSoundCheckBox->setSelected(mGuiSoundEnabled); + mMusicCheckBox->setSelected(mMusicEnabled); + mMumbleCheckBox->setSelected(mMumbleEnabled); + mDownloadMusicCheckBox->setSelected(mDownloadEnabled); + + sound.setSfxVolume(mSfxVolume); + mSfxSlider->setValue(mSfxVolume); + + sound.setMusicVolume(mMusicVolume); + mMusicSlider->setValue(mMusicVolume); + + config.setValue("sound", mAudioEnabled); + config.setValue("playBattleSound", mGameSoundEnabled); + config.setValue("playGuiSound", mGuiSoundEnabled); + config.setValue("playMusic", mMusicEnabled); + config.setValue("enableMumble", mMumbleEnabled); + config.setValue("download-music", mDownloadEnabled); + config.setValue("sfxVolume", mSfxVolume); + config.setValue("musicVolume", mMusicVolume); +} + +void Setup_Audio::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "sfx") + { + config.setValueInt("sfxVolume", + static_cast<int>(mSfxSlider->getValue())); + sound.setSfxVolume(static_cast<int>(mSfxSlider->getValue())); + } + else if (event.getId() == "music") + { + config.setValueInt("musicVolume", + static_cast<int>(mMusicSlider->getValue())); + sound.setMusicVolume(static_cast<int>(mMusicSlider->getValue())); + } +} diff --git a/src/gui/setup_audio.h b/src/gui/setup_audio.h new file mode 100644 index 000000000..c16c4fb23 --- /dev/null +++ b/src/gui/setup_audio.h @@ -0,0 +1,53 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_SETUP_AUDIO_H +#define GUI_SETUP_AUDIO_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> + +class Setup_Audio : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Audio(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + int mMusicVolume, mSfxVolume; + bool mAudioEnabled, mGameSoundEnabled, mGuiSoundEnabled; + bool mMusicEnabled, mMumbleEnabled; + bool mDownloadEnabled; + + gcn::CheckBox *mAudioCheckBox, *mGameSoundCheckBox, *mGuiSoundCheckBox; + gcn::CheckBox *mMusicCheckBox, *mMumbleCheckBox; + gcn::CheckBox *mDownloadMusicCheckBox; + gcn::Slider *mSfxSlider, *mMusicSlider; +}; + +#endif diff --git a/src/gui/setup_chat.cpp b/src/gui/setup_chat.cpp new file mode 100644 index 000000000..1dba2ed85 --- /dev/null +++ b/src/gui/setup_chat.cpp @@ -0,0 +1,306 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/setup_chat.h" +#include "gui/editdialog.h" +#include "gui/chat.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/chattab.h" + +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "utils/gettext.h" + +#define ACTION_REMOVE_COLORS "remove colors" +#define ACTION_MAGIC_IN_DEBUG "magic in debug" +#define ACTION_ALLOW_COMMANDS_IN_CHATTABS "allow commands" +#define ACTION_SERVER_MSG_IN_DEBUG "server in debug" +#define ACTION_SHOW_CHAT_COLORS "show chat colors" +#define ACTION_MAX_CHAR_LIMIT "char limit" +#define ACTION_EDIT_CHAR_LIMIT "edit char limit" +#define ACTION_EDIT_CHAR_OK "edit char ok" +#define ACTION_MAX_LINES_LIMIT "lines limit" +#define ACTION_EDIT_LINES_LIMIT "edit lines limit" +#define ACTION_EDIT_LINES_OK "edit lines ok" +#define ACTION_CHAT_LOGGER "chat logger" +#define ACTION_TRADE_TAB "trade tab" +#define ACTION_HIDE_SHOP_MESSAGES "hide shop messages" +#define ACTION_SHOW_CHAT_HISTORY "show chat history" +#define ACTION_ENABLE_BATTLE_TAB "show battle tab" +#define ACTION_SHOW_BATTLE_EVENTS "show battle events" + +Setup_Chat::Setup_Chat() +{ + setName(_("Chat")); + + mRemoveColors = config.getBoolValue("removeColors"); + mRemoveColorsCheckBox = new CheckBox( + _("Remove colors from received chat messages"), + mRemoveColors, this, ACTION_REMOVE_COLORS); + + mMagicInDebug = config.getBoolValue("showMagicInDebug"); + mMagicInDebugCheckBox = new CheckBox(_("Log magic messages in debug tab"), + mMagicInDebug, this, ACTION_MAGIC_IN_DEBUG); + + mAllowCommandsInChatTabs = config.getBoolValue( + "allowCommandsInChatTabs"); + + mAllowCommandsInChatTabsCheckBox = new CheckBox( + _("Allow magic and GM commands in all chat tabs"), + mAllowCommandsInChatTabs, this, ACTION_ALLOW_COMMANDS_IN_CHATTABS); + + mServerMsgInDebug = config.getBoolValue("serverMsgInDebug"); + mServerMsgInDebugCheckBox = new CheckBox( + _("Show server messages in debug tab"), + mServerMsgInDebug, this, ACTION_SERVER_MSG_IN_DEBUG); + + mEnableChatLogger = config.getBoolValue("enableChatLog"); + mEnableChatLoggerCheckBox = new CheckBox(_("Enable chat Log"), + mEnableChatLogger, this, ACTION_CHAT_LOGGER); + + mEnableTradeTab = config.getBoolValue("enableTradeTab"); + mEnableTradeTabCheckBox = new CheckBox(_("Enable trade tab"), + mEnableTradeTab, this, ACTION_TRADE_TAB); + + mHideShopMessages = config.getBoolValue("hideShopMessages"); + mHideShopMessagesCheckBox = new CheckBox(_("Hide shop messages"), + mHideShopMessages, this, ACTION_HIDE_SHOP_MESSAGES); + + mShowChatHistory = config.getBoolValue("showChatHistory"); + mShowChatHistoryCheckBox = new CheckBox(_("Show chat history"), + mShowChatHistory, this, ACTION_SHOW_CHAT_HISTORY); + + mEnableBattleTab = config.getBoolValue("enableBattleTab"); + mEnableBattleTabCheckBox = new CheckBox(_("Enable battle tab"), + mEnableBattleTab, this, ACTION_ENABLE_BATTLE_TAB); + + mShowBattleEvents = config.getBoolValue("showBattleEvents"); + mShowBattleEventsCheckBox = new CheckBox(_("Show battle events"), + mShowBattleEvents, this, ACTION_SHOW_BATTLE_EVENTS); + + mShowChatColors = config.getBoolValue("showChatColorsList"); + mShowChatColorsCheckBox = new CheckBox(_("Show chat colors list"), + mShowChatColors, this, ACTION_SHOW_CHAT_COLORS); + + mMaxCharButton = new Button(_("Edit"), ACTION_EDIT_CHAR_LIMIT, this); + int maxCharLimit = config.getIntValue("chatMaxCharLimit"); + mMaxChar = (maxCharLimit != 0); + mMaxCharCheckBox = new CheckBox(_("Limit max chars in chat line"), + mMaxChar, this, ACTION_MAX_CHAR_LIMIT); + + mMaxCharField = new IntTextField(maxCharLimit, 0, 500, mMaxChar, 20); + + mMaxLinesButton = new Button(_("Edit"), ACTION_EDIT_LINES_LIMIT, this); + int maxLinesLimit = config.getIntValue("chatMaxLinesLimit"); + mMaxLines = (maxLinesLimit != 0); + + mMaxLinesCheckBox = new CheckBox(_("Limit max lines in chat"), + mMaxLines, + this, ACTION_MAX_LINES_LIMIT); + + mMaxLinesField = new IntTextField(maxLinesLimit, 0, 500, mMaxLines, 20); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mRemoveColorsCheckBox, 10); + place(0, 1, mMagicInDebugCheckBox, 10); + place(0, 2, mAllowCommandsInChatTabsCheckBox, 10); + place(0, 3, mServerMsgInDebugCheckBox, 10); + place(0, 4, mShowChatColorsCheckBox, 10); + place(0, 5, mMaxCharCheckBox, 6); + place(6, 5, mMaxCharField, 2); + place(8, 5, mMaxCharButton, 2); + place(0, 6, mEnableChatLoggerCheckBox, 10); + place(0, 7, mMaxLinesCheckBox, 6); + place(6, 7, mMaxLinesField, 2); + place(8, 7, mMaxLinesButton, 2); + place(0, 8, mEnableTradeTabCheckBox, 10); + place(0, 9, mHideShopMessagesCheckBox, 10); + place(0, 10, mShowChatHistoryCheckBox, 10); + place(0, 11, mEnableBattleTabCheckBox, 10); + place(0, 12, mShowBattleEventsCheckBox, 10); + + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + + setDimension(gcn::Rectangle(0, 0, 500, 500)); +} + +void Setup_Chat::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_REMOVE_COLORS) + { + mRemoveColors = mRemoveColorsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_MAGIC_IN_DEBUG) + { + mMagicInDebug = mMagicInDebugCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ALLOW_COMMANDS_IN_CHATTABS) + { + mAllowCommandsInChatTabs + = mAllowCommandsInChatTabsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SERVER_MSG_IN_DEBUG) + { + mServerMsgInDebug = mServerMsgInDebugCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_CHAT_COLORS) + { + mShowChatColors = mShowChatColorsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_MAX_CHAR_LIMIT) + { + mMaxChar = mMaxCharCheckBox->isSelected(); + } + else if (event.getId() == ACTION_EDIT_CHAR_LIMIT) + { + mEditDialog = new EditDialog("Limit max chars in chat line", + toString(mMaxCharField->getValue()), + ACTION_EDIT_CHAR_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_CHAR_OK) + { + mMaxCharField->setValue(atoi(mEditDialog->getMsg().c_str())); + } + else if (event.getId() == ACTION_MAX_LINES_LIMIT) + { + mMaxLines = mMaxLinesCheckBox->isSelected(); + } + else if (event.getId() == ACTION_EDIT_LINES_LIMIT) + { + mEditDialog = new EditDialog("Limit max lines in chat", + toString(mMaxLinesField->getValue()), + ACTION_EDIT_LINES_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_LINES_OK) + { + mMaxLinesField->setValue(atoi(mEditDialog->getMsg().c_str())); + } + else if (event.getId() == ACTION_CHAT_LOGGER) + { + mEnableChatLogger = mEnableChatLoggerCheckBox->isSelected(); + } + else if (event.getId() == ACTION_TRADE_TAB) + { + mEnableTradeTab = mEnableTradeTabCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIDE_SHOP_MESSAGES) + { + mHideShopMessages = mHideShopMessagesCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_CHAT_HISTORY) + { + mShowChatHistory = mShowChatHistoryCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ENABLE_BATTLE_TAB) + { + mEnableBattleTab = mEnableBattleTabCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_BATTLE_EVENTS) + { + mShowBattleEvents = mShowBattleEventsCheckBox->isSelected(); + } +} + +void Setup_Chat::cancel() +{ + mRemoveColors = config.getBoolValue("removeColors"); + mRemoveColorsCheckBox->setSelected(mRemoveColors); + + mMagicInDebug = config.getBoolValue("showMagicInDebug"); + mMagicInDebugCheckBox->setSelected(mMagicInDebug); + + mAllowCommandsInChatTabs + = config.getBoolValue("allowCommandsInChatTabs"); + mAllowCommandsInChatTabsCheckBox->setSelected(mAllowCommandsInChatTabs); + + mServerMsgInDebug = config.getBoolValue("serverMsgInDebug"); + mServerMsgInDebugCheckBox->setSelected(mServerMsgInDebug); + + mShowChatColors = config.getBoolValue("showChatColorsList"); + mShowChatColorsCheckBox->setSelected(mShowChatColors); + + int maxCharLimit = config.getIntValue("chatMaxCharLimit"); + mMaxChar = (maxCharLimit != 0); + mMaxCharCheckBox->setSelected(mMaxChar); + mMaxCharField->setValue(maxCharLimit); + mMaxCharField->setEnabled(mMaxChar); + + int maxLinesLimit = config.getIntValue("chatMaxLinesLimit"); + mMaxLines = (maxLinesLimit != 0); + mMaxLinesCheckBox->setSelected(mMaxLines); + mMaxLinesField->setValue(maxLinesLimit); + mMaxLinesField->setEnabled(mMaxLines); + + mEnableChatLogger = config.getBoolValue("enableChatLog"); + mEnableChatLoggerCheckBox->setSelected(mEnableChatLogger); + + mEnableTradeTab = config.getBoolValue("enableTradeTab"); + mEnableTradeTabCheckBox->setSelected(mEnableTradeTab); + + mHideShopMessages = config.getBoolValue("hideShopMessages"); + mHideShopMessagesCheckBox->setSelected(mHideShopMessages); + + mShowChatHistory = config.getBoolValue("showChatHistory"); + mShowChatHistoryCheckBox->setSelected(mShowChatHistory); + + mEnableBattleTab = config.getBoolValue("enableBattleTab"); + mEnableBattleTabCheckBox->setSelected(mEnableBattleTab); + + mShowBattleEvents = config.getBoolValue("showBattleEvents"); + mShowBattleEventsCheckBox->setSelected(mShowBattleEvents); +} + +void Setup_Chat::apply() +{ + config.setValue("removeColors", mRemoveColors); + config.setValue("showMagicInDebug", mMagicInDebug); + config.setValue("allowCommandsInChatTabs", mAllowCommandsInChatTabs); + config.setValue("serverMsgInDebug", mServerMsgInDebug); + config.setValue("showChatColorsList", mShowChatColors); + if (mMaxChar) + config.setValue("chatMaxCharLimit", mMaxCharField->getValue()); + else + config.setValue("chatMaxCharLimit", 0); + if (mMaxLines) + config.setValue("chatMaxLinesLimit", mMaxLinesField->getValue()); + else + config.setValue("chatMaxLinesLimit", 0); + config.setValue("enableChatLog", mEnableChatLogger); + config.setValue("enableTradeTab", mEnableTradeTab); + config.setValue("hideShopMessages", mHideShopMessages); + config.setValue("showChatHistory", mShowChatHistory); + config.setValue("enableBattleTab", mEnableBattleTab); + config.setValue("showBattleEvents", mShowBattleEvents); +} diff --git a/src/gui/setup_chat.h b/src/gui/setup_chat.h new file mode 100644 index 000000000..30a59895a --- /dev/null +++ b/src/gui/setup_chat.h @@ -0,0 +1,92 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef GUI_SETUP_CHAT_H +#define GUI_SETUP_CHAT_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> + +class IntTextField; +class EditDialog; + +class Setup_Chat : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Chat(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + gcn::CheckBox *mRemoveColorsCheckBox; + bool mRemoveColors; + + gcn::CheckBox *mMagicInDebugCheckBox; + bool mMagicInDebug; + + gcn::CheckBox *mAllowCommandsInChatTabsCheckBox; + bool mAllowCommandsInChatTabs; + + gcn::CheckBox *mServerMsgInDebugCheckBox; + bool mServerMsgInDebug; + + gcn::CheckBox *mShowChatColorsCheckBox; + bool mShowChatColors; + + gcn::CheckBox *mMaxCharCheckBox; + IntTextField *mMaxCharField; + gcn::Button *mMaxCharButton; + bool mMaxChar; + + gcn::CheckBox *mMaxLinesCheckBox; + IntTextField *mMaxLinesField; + gcn::Button *mMaxLinesButton; + bool mMaxLines; + + gcn::CheckBox *mEnableChatLoggerCheckBox; + bool mEnableChatLogger; + + gcn::CheckBox *mEnableTradeTabCheckBox; + bool mEnableTradeTab; + + gcn::CheckBox *mHideShopMessagesCheckBox; + bool mHideShopMessages; + + gcn::CheckBox *mShowChatHistoryCheckBox; + bool mShowChatHistory; + + gcn::CheckBox *mEnableBattleTabCheckBox; + bool mEnableBattleTab; + + gcn::CheckBox *mShowBattleEventsCheckBox; + bool mShowBattleEvents; + + EditDialog *mEditDialog; +}; + +#endif diff --git a/src/gui/setup_colors.cpp b/src/gui/setup_colors.cpp new file mode 100644 index 000000000..76510a283 --- /dev/null +++ b/src/gui/setup_colors.cpp @@ -0,0 +1,443 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net> + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/setup_colors.h" + +#include "configuration.h" + +#include "gui/gui.h" +#include "gui/theme.h" +#include "gui/userpalette.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/itemlinkhandler.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/textpreview.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <string> +#include <cmath> + +const std::string Setup_Colors::rawmsg = + _("This is what the color looks like"); + +Setup_Colors::Setup_Colors() : + mSelected(-1) +{ + setName(_("Colors")); + + mColorBox = new ListBox(userPalette); + mColorBox->addSelectionListener(this); + + mScroll = new ScrollArea(mColorBox); + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mTextPreview = new TextPreview(rawmsg); + + mPreview = new BrowserBox(BrowserBox::AUTO_WRAP); + mPreview->setOpaque(false); + + // don't do anything with links + mPreview->setLinkHandler(NULL); + + mPreviewBox = new ScrollArea(mPreview); + mPreviewBox->setHeight(20); + mPreviewBox->setScrollPolicy(gcn::ScrollArea::SHOW_NEVER, + gcn::ScrollArea::SHOW_NEVER); + + mGradTypeLabel = new Label(_("Type:")); + + mGradTypeSlider = new Slider(0, 3); + mGradTypeSlider->setWidth(180); + mGradTypeSlider->setActionEventId("slider_grad"); + mGradTypeSlider->setValue(0); + mGradTypeSlider->addActionListener(this); + mGradTypeSlider->setEnabled(false); + + mGradTypeText = new Label; + + std::string longText = _("Static"); + + if (getFont()->getWidth(_("Pulse")) > getFont()->getWidth(longText)) + longText = _("Pulse"); + if (getFont()->getWidth(_("Rainbow")) > getFont()->getWidth(longText)) + longText = _("Rainbow"); + if (getFont()->getWidth(_("Spectrum")) > getFont()->getWidth(longText)) + longText = _("Spectrum"); + + mGradTypeText->setCaption(longText); + + mGradDelayLabel = new Label(_("Delay:")); + + mGradDelayText = new TextField(); + mGradDelayText->setWidth(40); + mGradDelayText->setRange(20, 100); + mGradDelayText->setNumeric(true); + mGradDelayText->setEnabled(false); + + mGradDelaySlider = new Slider(20, 100); + mGradDelaySlider->setWidth(180); + mGradDelaySlider->setValue(mGradDelayText->getValue()); + mGradDelaySlider->setActionEventId("slider_graddelay"); + mGradDelaySlider->addActionListener(this); + mGradDelaySlider->setEnabled(false); + + mRedLabel = new Label(_("Red:")); + + mRedText = new TextField; + mRedText->setWidth(40); + mRedText->setRange(0, 255); + mRedText->setNumeric(true); + mRedText->setEnabled(false); + + mRedSlider = new Slider(0, 255); + mRedSlider->setWidth(180); + mRedSlider->setValue(mRedText->getValue()); + mRedSlider->setActionEventId("slider_red"); + mRedSlider->addActionListener(this); + mRedSlider->setEnabled(false); + + mGreenLabel = new Label(_("Green:")); + + mGreenText = new TextField; + mGreenText->setWidth(40); + mGreenText->setRange(0, 255); + mGreenText->setNumeric(true); + mGreenText->setEnabled(false); + + mGreenSlider = new Slider(0, 255); + mGreenSlider->setWidth(180); + mGreenSlider->setValue(mGreenText->getValue()); + mGreenSlider->setActionEventId("slider_green"); + mGreenSlider->addActionListener(this); + mGreenSlider->setEnabled(false); + + mBlueLabel = new Label(_("Blue:")); + + mBlueText = new TextField; + mBlueText->setWidth(40); + mBlueText->setRange(0, 255); + mBlueText->setNumeric(true); + mBlueText->setEnabled(false); + + mBlueSlider = new Slider(0, 255); + mBlueSlider->setWidth(180); + mBlueSlider->setValue(mBlueText->getValue()); + mBlueSlider->setActionEventId("slider_blue"); + mBlueSlider->addActionListener(this); + mBlueSlider->setEnabled(false); + + setOpaque(false); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mScroll, 6, 6).setPadding(2); + place(0, 6, mPreviewBox, 6).setPadding(2); + place(0, 7, mGradTypeLabel, 3); + place(3, 7, mGradTypeSlider); + place(4, 7, mGradTypeText, 2).setPadding(1); + place(0, 8, mRedLabel, 3); + place(3, 8, mRedSlider); + place(5, 8, mRedText).setPadding(1); + place(0, 9, mGreenLabel, 3); + place(3, 9, mGreenSlider); + place(5, 9, mGreenText).setPadding(1); + place(0, 10, mBlueLabel, 3); + place(3, 10, mBlueSlider); + place(5, 10, mBlueText).setPadding(1); + place(0, 11, mGradDelayLabel, 3); + place(3, 11, mGradDelaySlider); + place(5, 11, mGradDelayText).setPadding(1); + + mGradTypeText->setCaption(""); + + setDimension(gcn::Rectangle(0, 0, 365, 350)); +} + +Setup_Colors::~Setup_Colors() +{ + if (mPreviewBox && mPreviewBox->getContent() == mPreview) + { + delete mTextPreview; + mTextPreview = 0; + } + else + { + delete mPreview; + mPreview = 0; + } +} + +void Setup_Colors::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "slider_grad") + { + updateGradType(); + updateColor(); + return; + } + + if (event.getId() == "slider_graddelay") + { + mGradDelayText->setText(toString( + std::floor(mGradDelaySlider->getValue()))); + updateColor(); + return; + } + + if (event.getId() == "slider_red") + { + mRedText->setText(toString(std::floor(mRedSlider->getValue()))); + updateColor(); + return; + } + + if (event.getId() == "slider_green") + { + mGreenText->setText(toString(std::floor(mGreenSlider->getValue()))); + updateColor(); + return; + } + + if (event.getId() == "slider_blue") + { + mBlueText->setText(toString(std::floor(mBlueSlider->getValue()))); + updateColor(); + return; + } +} + +void Setup_Colors::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + if (!userPalette) + return; + + mSelected = mColorBox->getSelected(); + int type = userPalette->getColorTypeAt(mSelected); + const gcn::Color *col = &userPalette->getColor(type); + Palette::GradientType grad = userPalette->getGradientType(type); + const int delay = userPalette->getGradientDelay(type); + + mPreview->clearRows(); + mPreviewBox->setContent(mTextPreview); + mTextPreview->setFont(boldFont); + mTextPreview->setTextColor(col); + mTextPreview->setTextBGColor(NULL); + mTextPreview->setOpaque(false); + mTextPreview->setShadow(true); + mTextPreview->setOutline(true); + mTextPreview->useTextAlpha(false); + + switch (type) + { + case UserPalette::COLLISION_HIGHLIGHT: + case UserPalette::PORTAL_HIGHLIGHT: + case UserPalette::HOME_PLACE: + case UserPalette::ROAD_POINT: + mTextPreview->setBGColor(col); + mTextPreview->setOpaque(true); + mTextPreview->setOutline(false); + mTextPreview->setShadow(false); + break; + case UserPalette::ATTACK_RANGE_BORDER: + case UserPalette::HOME_PLACE_BORDER: + mTextPreview->setFont(gui->getFont()); + mTextPreview->setTextColor(col); + mTextPreview->setOutline(false); + mTextPreview->setShadow(false); + break; + case UserPalette::PARTICLE: + case UserPalette::EXP_INFO: + case UserPalette::PICKUP_INFO: + case UserPalette::HIT_PLAYER_MONSTER: + case UserPalette::HIT_MONSTER_PLAYER: + case UserPalette::HIT_CRITICAL: + case UserPalette::MISS: + case UserPalette::HIT_LOCAL_PLAYER_MONSTER: + case UserPalette::HIT_LOCAL_PLAYER_CRITICAL: + case UserPalette::HIT_LOCAL_PLAYER_MISS: + case UserPalette::ATTACK_RANGE: + case UserPalette::MONSTER_ATTACK_RANGE: + mTextPreview->setShadow(false); + default: + break; + } + + switch (type) + { + case UserPalette::PORTAL_HIGHLIGHT: + case UserPalette::ATTACK_RANGE: + case UserPalette::ATTACK_RANGE_BORDER: + case UserPalette::MONSTER_ATTACK_RANGE: + case UserPalette::HOME_PLACE: + case UserPalette::HOME_PLACE_BORDER: + case UserPalette::COLLISION_HIGHLIGHT: + case UserPalette::WALKABLE_HIGHLIGHT: + case UserPalette::ROAD_POINT: + case UserPalette::MONSTER_HP: + case UserPalette::MONSTER_HP2: + mGradDelayLabel->setCaption(_("Alpha:")); + mGradDelayText->setRange(0, 255); + mGradDelaySlider->setScale(0, 255); + break; + default: + mGradDelayLabel->setCaption(_("Delay:")); + mGradDelayText->setRange(20, 100); + mGradDelaySlider->setScale(20, 100); + break; + } + if (grad != Palette::STATIC && grad != Palette::PULSE) + { // If nonstatic color, don't display the current, but the committed + // color at the sliders + col = &userPalette->getCommittedColor(type); + } + else if (grad == Palette::PULSE) + { + col = &userPalette->getTestColor(type); + } + + setEntry(mGradDelaySlider, mGradDelayText, delay); + setEntry(mRedSlider, mRedText, col->r); + setEntry(mGreenSlider, mGreenText, col->g); + setEntry(mBlueSlider, mBlueText, col->b); + + mGradTypeSlider->setValue(grad); + updateGradType(); + mGradTypeSlider->setEnabled(true); +} + +void Setup_Colors::setEntry(gcn::Slider *s, TextField *t, int value) +{ + if (s) + s->setValue(value); + if (t) + { + char buffer[100]; + sprintf(buffer, "%d", value); + t->setText(buffer); + } +} + +void Setup_Colors::apply() +{ + if (userPalette) + userPalette->commit(); +} + +void Setup_Colors::cancel() +{ + if (!userPalette) + return; + + userPalette->rollback(); + int type = userPalette->getColorTypeAt(mSelected); + const gcn::Color *col = &userPalette->getColor(type); + mGradTypeSlider->setValue(userPalette->getGradientType(type)); + const int delay = userPalette->getGradientDelay(type); + setEntry(mGradDelaySlider, mGradDelayText, delay); + setEntry(mRedSlider, mRedText, col->r); + setEntry(mGreenSlider, mGreenText, col->g); + setEntry(mBlueSlider, mBlueText, col->b); +} + +#if 0 +void Setup_Colors::listen(const TextField *tf) +{ + if (!tf) + return; + + if (tf == mGradDelayText) + mGradDelaySlider->setValue(tf->getValue()); + else if (tf == mRedText) + mRedSlider->setValue(tf->getValue()); + else if (tf == mGreenText) + mGreenSlider->setValue(tf->getValue()); + else if (tf == mBlueText) + mBlueSlider->setValue(tf->getValue()); + + updateColor(); +} +#endif + +void Setup_Colors::updateGradType() +{ + if (mSelected == -1 || !userPalette) + return; + + mSelected = mColorBox->getSelected(); + int type = userPalette->getColorTypeAt(mSelected); + Palette::GradientType grad = userPalette->getGradientType(type); + + mGradTypeText->setCaption( + (grad == Palette::STATIC) ? _("Static") : + (grad == Palette::PULSE) ? _("Pulse") : + (grad == Palette::RAINBOW) ? _("Rainbow") : _("Spectrum")); + + const bool enable = (grad == Palette::STATIC || grad == Palette::PULSE); +// const bool delayEnable = (grad != Palette::STATIC); + const bool delayEnable = true; + + mGradDelayText->setEnabled(delayEnable); + mGradDelaySlider->setEnabled(delayEnable); + + mRedText->setEnabled(enable); + mRedSlider->setEnabled(enable); + mGreenText->setEnabled(enable); + mGreenSlider->setEnabled(enable); + mBlueText->setEnabled(enable); + mBlueSlider->setEnabled(enable); +} + +void Setup_Colors::updateColor() +{ + if (mSelected == -1 || !userPalette) + return; + + int type = userPalette->getColorTypeAt(mSelected); + Palette::GradientType grad = static_cast<Palette::GradientType>( + static_cast<int>(mGradTypeSlider->getValue())); + int delay = static_cast<int>(mGradDelaySlider->getValue()); + userPalette->setGradient(type, grad); + userPalette->setGradientDelay(type, delay); + + if (grad == Palette::STATIC) + { + userPalette->setColor(type, + static_cast<int>(mRedSlider->getValue()), + static_cast<int>(mGreenSlider->getValue()), + static_cast<int>(mBlueSlider->getValue())); + } + else if (grad == Palette::PULSE) + { + userPalette->setTestColor(type, gcn::Color( + static_cast<int>(mRedSlider->getValue()), + static_cast<int>(mGreenSlider->getValue()), + static_cast<int>(mBlueSlider->getValue()))); + } +} diff --git a/src/gui/setup_colors.h b/src/gui/setup_colors.h new file mode 100644 index 000000000..1b3985850 --- /dev/null +++ b/src/gui/setup_colors.h @@ -0,0 +1,96 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net> + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SETUP_COLORS_H +#define SETUP_COLORS_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/selectionlistener.hpp> + +#include <string> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class BrowserBox; +class TextField; +class TextPreview; + +class Setup_Colors : public SetupTab, + public gcn::ActionListener, + public gcn::SelectionListener +{ + public: + Setup_Colors(); + ~Setup_Colors(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + void valueChanged(const gcn::SelectionEvent &event); + + private: + static const std::string rawmsg; + + gcn::ListBox *mColorBox; + gcn::ScrollArea *mScroll; + BrowserBox *mPreview; + TextPreview *mTextPreview; + gcn::ScrollArea *mPreviewBox; + int mSelected; + + gcn::Label *mGradTypeLabel; + gcn::Slider *mGradTypeSlider; + gcn::Label *mGradTypeText; + + gcn::Label *mGradDelayLabel; + gcn::Slider *mGradDelaySlider; + TextField *mGradDelayText; + + gcn::Label *mRedLabel; + gcn::Slider *mRedSlider; + TextField *mRedText; + int mRedValue; + + gcn::Label *mGreenLabel; + gcn::Slider *mGreenSlider; + TextField *mGreenText; + int mGreenValue; + + gcn::Label *mBlueLabel; + gcn::Slider *mBlueSlider; + TextField *mBlueText; + int mBlueValue; + + void setEntry(gcn::Slider *s, TextField *t, int value); + void updateColor(); + void updateGradType(); +}; + +#endif // SETUP_COLORS_H diff --git a/src/gui/setup_joystick.cpp b/src/gui/setup_joystick.cpp new file mode 100644 index 000000000..5f8f11993 --- /dev/null +++ b/src/gui/setup_joystick.cpp @@ -0,0 +1,101 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/setup_joystick.h" + +#include "configuration.h" +#include "joystick.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" + +#include "utils/gettext.h" + +extern Joystick *joystick; + +Setup_Joystick::Setup_Joystick(): + mCalibrateLabel(new Label(_("Press the button to start calibration"))), + mCalibrateButton(new Button(_("Calibrate"), "calibrate", this)), + mJoystickEnabled(new CheckBox(_("Enable joystick"))) +{ + setName(_("Joystick")); + + mOriginalJoystickEnabled = !config.getBoolValue("joystickEnabled"); + mJoystickEnabled->setSelected(mOriginalJoystickEnabled); + + mJoystickEnabled->addActionListener(this); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mJoystickEnabled); + place(0, 1, mCalibrateLabel); + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + place(0, 0, mCalibrateButton); + + setDimension(gcn::Rectangle(0, 0, 365, 75)); +} + +void Setup_Joystick::action(const gcn::ActionEvent &event) +{ + if (!joystick) + return; + + if (event.getSource() == mJoystickEnabled) + { + joystick->setEnabled(mJoystickEnabled->isSelected()); + } + else + { + if (joystick->isCalibrating()) + { + mCalibrateButton->setCaption(_("Calibrate")); + mCalibrateLabel->setCaption + (_("Press the button to start calibration")); + joystick->finishCalibration(); + } + else + { + mCalibrateButton->setCaption(_("Stop")); + mCalibrateLabel->setCaption(_("Rotate the stick")); + joystick->startCalibration(); + } + } +} + +void Setup_Joystick::cancel() +{ + if (joystick) + joystick->setEnabled(mOriginalJoystickEnabled); + + mJoystickEnabled->setSelected(mOriginalJoystickEnabled); +} + +void Setup_Joystick::apply() +{ + config.setValue("joystickEnabled", + joystick ? joystick->isEnabled() : false); +} + diff --git a/src/gui/setup_joystick.h b/src/gui/setup_joystick.h new file mode 100644 index 000000000..f848f45c1 --- /dev/null +++ b/src/gui/setup_joystick.h @@ -0,0 +1,48 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_SETUP_JOYSTICK_H +#define GUI_SETUP_JOYSTICK_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> + +class Setup_Joystick : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Joystick(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + gcn::Label *mCalibrateLabel; + gcn::Button *mCalibrateButton; + bool mOriginalJoystickEnabled; + gcn::CheckBox *mJoystickEnabled; +}; + +#endif diff --git a/src/gui/setup_keyboard.cpp b/src/gui/setup_keyboard.cpp new file mode 100644 index 000000000..4e55ecb35 --- /dev/null +++ b/src/gui/setup_keyboard.cpp @@ -0,0 +1,210 @@ +/* + * Custom keyboard shortcuts configuration + * Copyright (C) 2007 Joshua Langley <joshlangley@optusnet.com.au> + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/setup_keyboard.h" + +#include "keyboardconfig.h" + +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/listmodel.hpp> + +#include <SDL_keyboard.h> + +/** + * The list model for key function list. + * + * \ingroup Interface + */ +class KeyListModel : public gcn::ListModel +{ + public: + /** + * Returns the number of elements in container. + */ + int getNumberOfElements() + { return keyboard.KEY_TOTAL; } + + /** + * Returns element from container. + */ + std::string getElementAt(int i) + { return mKeyFunctions[i]; } + + /** + * Sets element from container. + */ + void setElementAt(int i, const std::string &caption) + { mKeyFunctions[i] = caption; } + + private: + std::string mKeyFunctions[KeyboardConfig::KEY_TOTAL]; +}; + +Setup_Keyboard::Setup_Keyboard(): + mKeyListModel(new KeyListModel), + mKeyList(new ListBox(mKeyListModel)), + mKeySetting(false) +{ + keyboard.setSetupKeyboard(this); + setName(_("Keyboard")); + + refreshKeys(); + + mKeyList->addActionListener(this); + + ScrollArea *scrollArea = new ScrollArea(mKeyList); + scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mAssignKeyButton = new Button(_("Assign"), "assign", this); + mAssignKeyButton->addActionListener(this); + mAssignKeyButton->setEnabled(false); + + mUnassignKeyButton = new Button(_("Unassign"), "unassign", this); + mUnassignKeyButton->addActionListener(this); + mUnassignKeyButton->setEnabled(false); + + mMakeDefaultButton = new Button(_("Default"), "makeDefault", this); + mMakeDefaultButton->addActionListener(this); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, scrollArea, 4, 6).setPadding(2); + place(0, 6, mMakeDefaultButton); + place(2, 6, mAssignKeyButton); + place(3, 6, mUnassignKeyButton); + + setDimension(gcn::Rectangle(0, 0, 500, 350)); +} + +Setup_Keyboard::~Setup_Keyboard() +{ + delete mKeyList; + mKeyList = 0; + delete mKeyListModel; + mKeyListModel = 0; + + delete mAssignKeyButton; + mAssignKeyButton = 0; + delete mUnassignKeyButton; + mUnassignKeyButton = 0; + delete mMakeDefaultButton; + mMakeDefaultButton = 0; +} + +void Setup_Keyboard::apply() +{ + keyUnresolved(); + + if (keyboard.hasConflicts()) + { + new OkDialog(_("Key Conflict(s) Detected."), + keyboard.getBindError()); + } + keyboard.setEnabled(true); + keyboard.store(); +} + +void Setup_Keyboard::cancel() +{ + keyUnresolved(); + + keyboard.retrieve(); + keyboard.setEnabled(true); + + refreshKeys(); +} + +void Setup_Keyboard::action(const gcn::ActionEvent &event) +{ + if (event.getSource() == mKeyList) + { + if (!mKeySetting) + { + mAssignKeyButton->setEnabled(true); + mUnassignKeyButton->setEnabled(true); + } + } + else if (event.getId() == "assign") + { + mKeySetting = true; + mAssignKeyButton->setEnabled(false); + keyboard.setEnabled(false); + int i(mKeyList->getSelected()); + keyboard.setNewKeyIndex(i); + mKeyListModel->setElementAt(i, keyboard.getKeyCaption(i) + ": ?"); + } + else if (event.getId() == "unassign") + { + int i(mKeyList->getSelected()); + keyboard.setNewKeyIndex(i); + refreshAssignedKey(mKeyList->getSelected()); + keyboard.setNewKey(keyboard.KEY_NO_VALUE); + mAssignKeyButton->setEnabled(true); + } + else if (event.getId() == "makeDefault") + { + keyboard.makeDefault(); + refreshKeys(); + } +} + +void Setup_Keyboard::refreshAssignedKey(int index) +{ + std::string caption; + char *temp = SDL_GetKeyName( + static_cast<SDLKey>(keyboard.getKeyValue(index))); + caption = keyboard.getKeyCaption(index) + ": " + toString(temp); + mKeyListModel->setElementAt(index, caption); +} + +void Setup_Keyboard::newKeyCallback(int index) +{ + mKeySetting = false; + refreshAssignedKey(index); + mAssignKeyButton->setEnabled(true); +} + +void Setup_Keyboard::refreshKeys() +{ + for (int i = 0; i < keyboard.KEY_TOTAL; i++) + refreshAssignedKey(i); +} + +void Setup_Keyboard::keyUnresolved() +{ + if (mKeySetting) + { + newKeyCallback(keyboard.getNewKeyIndex()); + keyboard.setNewKeyIndex(keyboard.KEY_NO_VALUE); + } +} diff --git a/src/gui/setup_keyboard.h b/src/gui/setup_keyboard.h new file mode 100644 index 000000000..4c916705c --- /dev/null +++ b/src/gui/setup_keyboard.h @@ -0,0 +1,81 @@ +/* + * Custom keyboard shortcuts configuration + * Copyright (C) 2007 Joshua Langley <joshlangley@optusnet.com.au> + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_SETUP_KEYBOARD_H +#define GUI_SETUP_KEYBOARD_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> + +#include <string> + +class Setup_Keyboard : public SetupTab, public gcn::ActionListener +{ + public: + /** + * Constructor + */ + Setup_Keyboard(); + + /** + * Destructor + */ + ~Setup_Keyboard(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + /** + * Get an update on the assigned key. + */ + void refreshAssignedKey(int index); + + /** + * The callback function when a new key has been pressed. + */ + void newKeyCallback(int index); + + /** + * Shorthand method to update all the keys. + */ + void refreshKeys(); + + /** + * If a key function is unresolved, then this reverts it. + */ + void keyUnresolved(); + + private: + class KeyListModel *mKeyListModel; + gcn::ListBox *mKeyList; + + gcn::Button *mAssignKeyButton; + gcn::Button *mUnassignKeyButton; + gcn::Button *mMakeDefaultButton; + + bool mKeySetting; /**< flag to check if key being set. */ +}; + +#endif diff --git a/src/gui/setup_other.cpp b/src/gui/setup_other.cpp new file mode 100644 index 000000000..54ec1c65d --- /dev/null +++ b/src/gui/setup_other.cpp @@ -0,0 +1,426 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/setup_other.h" +#include "gui/editdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/textfield.h" + +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "utils/gettext.h" + +#define ACTION_SHOW_TAKEDDAMAGE "taked damage" +#define ACTION_NO_RAIN "no rain" +#define ACTION_ONLY_REACHABLE "only reachable" +#define ACTION_ERRORS_IN_DEBUG "errors in debug" +#define ACTION_HIGHLIGHT_PORTALS "highlight portals" +#define ACTION_HIGHLIGHT_ATTACK_RANGE "highlight attack" +#define ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE "highlight monster attack" +#define ACTION_CYCLE_PLAYERS "cycle players" +#define ACTION_CYCLE_MONSTERS "cycle monsters" +#define ACTION_ENABLE_BOTCHECKER "bot checker" +#define ACTION_FLOORITEMS_HIGHLIGHT "floor items" +#define ACTION_MOVE_PROGRAM "move program" +#define ACTION_EDIT_PROGRAM "edit program" +#define ACTION_EDIT_PROGRAM_OK "edit program ok" +#define ACTION_AFK "move afk" +#define ACTION_EDIT_AFK "edit afk" +#define ACTION_EDIT_AFK_OK "edit afk ok" +#define ACTION_ENABLE_AFK "enable afk" +#define ACTION_TRADEBOT "trade bot" +#define ACTION_BUGGY_SERVERS "buggy servers" +#define ACTION_DEBUG_LOG "debug log" +#define ACTION_SERVER_ATTACK "server attack" +#define ACTION_FIX_POS "fix pos" +#define ACTION_ATTACK_MOVING "attack moving" +#define ACTION_QUICK_STATS "quick stats" +#define ACTION_WARP_PARTICLE "warp particle" +#define ACTION_AUTO_SHOP "auto shop" +#define ACTION_SHOW_MOB_HP "show mob hp" + +Setup_Other::Setup_Other(): + mShowMonstersTakedDamage(config.getBoolValue("showMonstersTakedDamage")), + mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")), + mErrorsInDebug(config.getBoolValue("errorsInDebug")), + mHighlightPortals(config.getBoolValue("highlightMapPortals")), + mHighlightAttackRange(config.getBoolValue("highlightAttackRange")), + mHighlightMonsterAttackRange( + config.getBoolValue("highlightMonsterAttackRange")), + mCyclePlayers(config.getBoolValue("cyclePlayers")), + mCycleMonsters(config.getBoolValue("cycleMonsters")), + mEnableBotChecker(config.getBoolValue("enableBotCheker")), + mFloorItemsHighlight(config.getBoolValue("floorItemsHighlight")), + mMoveProgram(config.getStringValue("crazyMoveProgram")), + mAfk(config.getStringValue("afkMessage")), + mTradeBot(config.getBoolValue("tradebot")), + mBuggyServers(serverConfig.getValueBool("enableBuggyServers", true)), + mDebugLog(config.getBoolValue("debugLog")), + mServerAttack(config.getBoolValue("serverAttack")), + mAutofixPos(config.getBoolValue("autofixPos")), + mAttackMoving(config.getBoolValue("attackMoving")), + mQuickStats(config.getBoolValue("quickStats")), + mWarpParticle(config.getBoolValue("warpParticle")), + mAutoShop(config.getBoolValue("autoShop")), + mShowMobHP(config.getBoolValue("showMobHP")) +{ + setName(_("Misc")); + + mShowMonstersTakedDamageCheckBox = new CheckBox( + _("Show damage inflicted to monsters"), + mShowMonstersTakedDamage, + this, ACTION_SHOW_TAKEDDAMAGE); + + mTargetOnlyReachableCheckBox = new CheckBox( + _("Auto target only reachable monsters"), + mTargetOnlyReachable, + this, ACTION_ONLY_REACHABLE); + + mHighlightPortalsCheckBox = new CheckBox(_("Highlight map portals"), + mHighlightPortals, + this, ACTION_HIGHLIGHT_PORTALS); + + mHighlightAttackRangeCheckBox = new CheckBox( + _("Highlight player attack range"), + mHighlightAttackRange, + this, ACTION_HIGHLIGHT_ATTACK_RANGE); + + mHighlightMonsterAttackRangeCheckBox = new CheckBox( + _("Highlight monster attack range"), + mHighlightMonsterAttackRange, + this, ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE); + + mCyclePlayersCheckBox = new CheckBox(_("Cycle player targets"), + mCyclePlayers, this, ACTION_CYCLE_PLAYERS); + + mCycleMonstersCheckBox = new CheckBox(_("Cycle monster targets"), + mCycleMonsters, this, ACTION_CYCLE_MONSTERS); + + mEnableBotCheckerCheckBox = new CheckBox(_("Enable bot checker"), + mEnableBotChecker, this, ACTION_ENABLE_BOTCHECKER); + + mFloorItemsHighlightCheckBox = new CheckBox(_("Highlight floor items"), + mFloorItemsHighlight, this, ACTION_FLOORITEMS_HIGHLIGHT); + + mMoveProgramLabel = new Label(_("Crazy move A program")); + + mMoveProgramField = new TextField(mMoveProgram, true, + this, ACTION_MOVE_PROGRAM); + + mMoveProgramButton = new Button(_("Edit"), ACTION_EDIT_PROGRAM, this); + + mAfkField = new TextField(mAfk, true, this, ACTION_AFK); + + mAfkButton = new Button(_("Edit"), ACTION_EDIT_AFK, this); + + mTradeBotCheckBox = new CheckBox(_("Enable shop mode"), + mTradeBot, + this, ACTION_TRADEBOT); + + mBuggyServersCheckBox = new CheckBox(_("Enable buggy servers protection"), + mBuggyServers, + this, ACTION_BUGGY_SERVERS); + + mDebugLogCheckBox = new CheckBox(_("Enable debug log"), + mDebugLog, + this, ACTION_DEBUG_LOG); + + mServerAttackCheckBox = new CheckBox(_("Enable server side attack"), + mServerAttack, + this, ACTION_SERVER_ATTACK); + + mAutofixPosCheckBox = new CheckBox(_("Auto fix position"), + mAutofixPos, + this, ACTION_FIX_POS); + + mAttackMovingCheckBox = new CheckBox(_("Attack while moving"), + mAttackMoving, + this, ACTION_ATTACK_MOVING); + + mQuickStatsCheckBox = new CheckBox(_("Enable quick stats"), + mQuickStats, + this, ACTION_QUICK_STATS); + + mWarpParticleCheckBox = new CheckBox(_("Show warps particles"), + mWarpParticle, + this, ACTION_WARP_PARTICLE); + + mAutoShopCheckBox = new CheckBox(_("Accept sell/buy requests"), + mAutoShop, + this, ACTION_AUTO_SHOP); + + mShowMobHPCheckBox = new CheckBox(_("Show monster hp bar"), + mShowMobHP, + this, ACTION_SHOW_MOB_HP); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mShowMonstersTakedDamageCheckBox, 12); + place(12, 0, mServerAttackCheckBox, 10); + place(0, 1, mTargetOnlyReachableCheckBox, 12); + place(12, 1, mAutofixPosCheckBox, 10); + place(0, 2, mHighlightPortalsCheckBox, 12); + place(12, 2, mAttackMovingCheckBox, 10); + place(12, 3, mQuickStatsCheckBox, 10); + place(12, 4, mWarpParticleCheckBox, 10); + place(12, 5, mAutoShopCheckBox, 10); + place(12, 6, mShowMobHPCheckBox, 10); + place(0, 3, mFloorItemsHighlightCheckBox, 12); + place(0, 4, mHighlightAttackRangeCheckBox, 12); + place(0, 5, mHighlightMonsterAttackRangeCheckBox, 12); + place(0, 6, mCyclePlayersCheckBox, 12); + place(0, 7, mCycleMonstersCheckBox, 12); + place(0, 8, mEnableBotCheckerCheckBox, 12); + place(0, 9, mMoveProgramLabel, 12); + place(0, 10, mMoveProgramField, 9); + place(9, 10, mMoveProgramButton, 2); + place(0, 11, mAfkField, 9); + place(9, 11, mAfkButton, 2); + place(0, 12, mTradeBotCheckBox, 12); + place(0, 13, mBuggyServersCheckBox, 12); + place(0, 14, mDebugLogCheckBox, 12); + + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + + setDimension(gcn::Rectangle(0, 0, 550, 500)); +} + +void Setup_Other::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_SHOW_TAKEDDAMAGE) + { + mShowMonstersTakedDamage = mShowMonstersTakedDamageCheckBox + ->isSelected(); + } + else if (event.getId() == ACTION_ONLY_REACHABLE) + { + mTargetOnlyReachable = mTargetOnlyReachableCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIGHLIGHT_PORTALS) + { + mHighlightPortals = mHighlightPortalsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIGHLIGHT_ATTACK_RANGE) + { + mHighlightAttackRange = mHighlightAttackRangeCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE) + { + mHighlightMonsterAttackRange = mHighlightMonsterAttackRangeCheckBox + ->isSelected(); + } + else if (event.getId() == ACTION_CYCLE_PLAYERS) + { + mCyclePlayers = mCyclePlayersCheckBox->isSelected(); + } + else if (event.getId() == ACTION_CYCLE_MONSTERS) + { + mCycleMonsters = mCycleMonstersCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ENABLE_BOTCHECKER) + { + mEnableBotChecker = mEnableBotCheckerCheckBox->isSelected(); + } + else if (event.getId() == ACTION_FLOORITEMS_HIGHLIGHT) + { + mFloorItemsHighlight = mFloorItemsHighlightCheckBox->isSelected(); + } + else if (event.getId() == ACTION_MOVE_PROGRAM) + { + mMoveProgram = mMoveProgramField->getText(); + } + else if (event.getId() == ACTION_EDIT_PROGRAM) + { + mEditDialog = new EditDialog("Crazy Move A", + mMoveProgramField->getText(), ACTION_EDIT_PROGRAM_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_PROGRAM_OK) + { + mMoveProgramField->setText(mEditDialog->getMsg()); + } + + else if (event.getId() == ACTION_AFK) + mAfk = mAfkField->getText(); + else if (event.getId() == ACTION_EDIT_AFK) + { + mEditDialog = new EditDialog("Afk message", mAfkField->getText(), + ACTION_EDIT_AFK_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_AFK_OK) + { + mAfkField->setText(mEditDialog->getMsg()); + } + else if (event.getId() == ACTION_TRADEBOT) + { + mTradeBot = mTradeBotCheckBox->isSelected(); + } + else if (event.getId() == ACTION_BUGGY_SERVERS) + { + mBuggyServers = mBuggyServersCheckBox->isSelected(); + } + else if (event.getId() == ACTION_DEBUG_LOG) + { + mDebugLog = mDebugLogCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SERVER_ATTACK) + { + mServerAttack = mServerAttackCheckBox->isSelected(); + } + else if (event.getId() == ACTION_FIX_POS) + { + mAutofixPos = mAutofixPosCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ATTACK_MOVING) + { + mAttackMoving = mAttackMovingCheckBox->isSelected(); + } + else if (event.getId() == ACTION_QUICK_STATS) + { + mQuickStats = mQuickStatsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_WARP_PARTICLE) + { + mWarpParticle = mWarpParticleCheckBox->isSelected(); + } + else if (event.getId() == ACTION_AUTO_SHOP) + { + mAutoShop = mAutoShopCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_MOB_HP) + { + mShowMobHP = mShowMobHPCheckBox->isSelected(); + } +} + +void Setup_Other::cancel() +{ + mShowMonstersTakedDamage = config.getBoolValue( + "showMonstersTakedDamage"); + mShowMonstersTakedDamageCheckBox->setSelected(mShowMonstersTakedDamage); + + mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable"); + mTargetOnlyReachableCheckBox->setSelected(mTargetOnlyReachable); + + mHighlightPortals = config.getBoolValue("highlightMapPortals"); + mHighlightPortalsCheckBox->setSelected(mHighlightPortals); + + mHighlightAttackRange = config.getBoolValue("highlightAttackRange"); + mHighlightAttackRangeCheckBox->setSelected(mHighlightAttackRange); + + mHighlightMonsterAttackRange = config.getBoolValue( + "highlightMonsterAttackRange"); + mHighlightMonsterAttackRangeCheckBox->setSelected( + mHighlightMonsterAttackRange); + + mCyclePlayers = config.getBoolValue("cyclePlayers"); + mCyclePlayersCheckBox->setSelected(mCyclePlayers); + + mCycleMonsters = config.getBoolValue("cycleMonsters"); + mCycleMonstersCheckBox->setSelected(mCycleMonsters); + + mEnableBotChecker = config.getBoolValue("enableBotCheker"); + mEnableBotCheckerCheckBox->setSelected(mEnableBotChecker); + + mFloorItemsHighlight = config.getBoolValue("floorItemsHighlight"); + mFloorItemsHighlightCheckBox->setSelected(mFloorItemsHighlight); + + mMoveProgram = config.getStringValue("crazyMoveProgram"); + mMoveProgramField->setText(mMoveProgram); + + mAfk = config.getStringValue("afkMessage"); + mAfkField->setText(mAfk); + + mTradeBot = config.getBoolValue("tradebot"); + mTradeBotCheckBox->setSelected(mTradeBot); + + mBuggyServers = serverConfig.getValueBool("enableBuggyServers", true); + mBuggyServersCheckBox->setSelected(mBuggyServers); + + mDebugLog = config.getBoolValue("debugLog"); + mDebugLogCheckBox->setSelected(mDebugLog); + + mServerAttack = config.getBoolValue("serverAttack"); + mServerAttackCheckBox->setSelected(mServerAttack); + + mAutofixPos = config.getBoolValue("autofixPos"); + mAutofixPosCheckBox->setSelected(mAutofixPos); + + mAttackMoving = config.getBoolValue("attackMoving"); + mAttackMovingCheckBox->setSelected(mAttackMoving); + + mQuickStats = config.getBoolValue("quickStats"); + mQuickStatsCheckBox->setSelected(mQuickStats); + + mWarpParticle = config.getBoolValue("warpParticle"); + mWarpParticleCheckBox->setSelected(mWarpParticle); + + mAutoShop = config.getBoolValue("autoShop"); + mAutoShopCheckBox->setSelected(mAutoShop); + + mShowMobHP = config.getBoolValue("showMobHP"); + mShowMobHPCheckBox->setSelected(mShowMobHP); +} + +void Setup_Other::apply() +{ + config.setValue("showMonstersTakedDamage", mShowMonstersTakedDamage); + config.setValue("targetOnlyReachable", mTargetOnlyReachable); + config.setValue("errorsInDebug", mErrorsInDebug); + config.setValue("highlightMapPortals", mHighlightPortals); + config.setValue("highlightAttackRange", mHighlightAttackRange); + config.setValue("highlightMonsterAttackRange", + mHighlightMonsterAttackRange); + config.setValue("cyclePlayers", mCyclePlayers); + config.setValue("cycleMonsters", mCycleMonsters); + config.setValue("enableBotCheker", mEnableBotChecker); + config.setValue("floorItemsHighlight", mFloorItemsHighlight); + config.setValue("crazyMoveProgram", mMoveProgramField->getText()); + config.setValue("afkMessage", mAfkField->getText()); + config.setValue("tradebot", mTradeBot); + serverConfig.setValue("enableBuggyServers", mBuggyServers); + config.setValue("debugLog", mDebugLog); + config.setValue("serverAttack", mServerAttack); + config.setValue("autofixPos", mAutofixPos); + config.setValue("attackMoving", mAttackMoving); + config.setValue("quickStats", mQuickStats); + config.setValue("warpParticle", mWarpParticle); + config.setValue("autoShop", mAutoShop); + config.setValue("showMobHP", mShowMobHP); + logger->setDebugLog(mDebugLog); +} + +void Setup_Other::externalUpdated() +{ + mBuggyServers = serverConfig.getValueBool("enableBuggyServers", true); + mBuggyServersCheckBox->setSelected(mBuggyServers); +}
\ No newline at end of file diff --git a/src/gui/setup_other.h b/src/gui/setup_other.h new file mode 100644 index 000000000..b20401be5 --- /dev/null +++ b/src/gui/setup_other.h @@ -0,0 +1,125 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef GUI_Setup_Other_H +#define GUI_Setup_Other_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> + +class EditDialog; + +class Setup_Other : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Other(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + virtual void externalUpdated(); + + private: + gcn::CheckBox *mShowMonstersTakedDamageCheckBox; + bool mShowMonstersTakedDamage; + + gcn::CheckBox *mNoRainCheckBox; + bool mNoRain; + + gcn::CheckBox *mTargetOnlyReachableCheckBox; + bool mTargetOnlyReachable; + + int mOverlayDetail; + gcn::DropDown *mFontSizeDropDown; + + gcn::CheckBox *mErrorsInDebugCheckBox; + bool mErrorsInDebug; + + gcn::CheckBox *mHighlightPortalsCheckBox; + bool mHighlightPortals; + + gcn::CheckBox *mHighlightAttackRangeCheckBox; + bool mHighlightAttackRange; + + gcn::CheckBox *mHighlightMonsterAttackRangeCheckBox; + bool mHighlightMonsterAttackRange; + + gcn::CheckBox *mCyclePlayersCheckBox; + bool mCyclePlayers; + + gcn::CheckBox *mCycleMonstersCheckBox; + bool mCycleMonsters; + + gcn::CheckBox *mEnableBotCheckerCheckBox; + bool mEnableBotChecker; + + gcn::CheckBox *mFloorItemsHighlightCheckBox; + bool mFloorItemsHighlight; + + gcn::Label *mMoveProgramLabel; + gcn::TextField *mMoveProgramField; + gcn::Button *mMoveProgramButton; + std::string mMoveProgram; + + gcn::TextField *mAfkField; + gcn::Button *mAfkButton; + std::string mAfk; + + gcn::CheckBox *mTradeBotCheckBox; + bool mTradeBot; + + gcn::CheckBox *mBuggyServersCheckBox; + bool mBuggyServers; + + gcn::CheckBox *mDebugLogCheckBox; + bool mDebugLog; + + gcn::CheckBox *mServerAttackCheckBox; + bool mServerAttack; + + gcn::CheckBox *mAutofixPosCheckBox; + bool mAutofixPos; + + gcn::CheckBox *mAttackMovingCheckBox; + bool mAttackMoving; + + gcn::CheckBox *mQuickStatsCheckBox; + bool mQuickStats; + + gcn::CheckBox *mWarpParticleCheckBox; + bool mWarpParticle; + + gcn::CheckBox *mAutoShopCheckBox; + bool mAutoShop; + + gcn::CheckBox *mShowMobHPCheckBox; + bool mShowMobHP; + + EditDialog *mEditDialog; +}; + +#endif diff --git a/src/gui/setup_players.cpp b/src/gui/setup_players.cpp new file mode 100644 index 000000000..6f4d50888 --- /dev/null +++ b/src/gui/setup_players.cpp @@ -0,0 +1,505 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/setup_players.h" + +#include "actorspritemanager.h" +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/table.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +#include <string> +#include <vector> + +#define COLUMNS_NR 2 // name plus listbox +#define NAME_COLUMN 0 +#define RELATION_CHOICE_COLUMN 1 + +#define ROW_HEIGHT 12 +// The following column widths really shouldn't be hardcoded but should scale with the size of the widget... except +// that, right now, the widget doesn't exactly scale either. +#define NAME_COLUMN_WIDTH 230 +#define RELATION_CHOICE_COLUMN_WIDTH 80 + +#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column) + +static const char *table_titles[COLUMNS_NR] = +{ + N_("Name"), + N_("Relation") +}; + +static const char *RELATION_NAMES[PlayerRelation::RELATIONS_NR] = +{ + N_("Neutral"), + N_("Friend"), + N_("Disregarded"), + N_("Ignored"), + N_("Erased") +}; + +class PlayerRelationListModel : public gcn::ListModel +{ +public: + virtual ~PlayerRelationListModel() { } + + virtual int getNumberOfElements() + { + return PlayerRelation::RELATIONS_NR; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return ""; + return gettext(RELATION_NAMES[i]); + } +}; + +class PlayerTableModel : public TableModel +{ +public: + PlayerTableModel() : + mPlayers(NULL), + mListModel(new PlayerRelationListModel) + { + playerRelationsUpdated(); + } + + virtual ~PlayerTableModel() + { + freeWidgets(); + delete mListModel; + mListModel = 0; + delete mPlayers; + mPlayers = 0; + } + + virtual int getRows() const + { + if (mPlayers) + return static_cast<int>(mPlayers->size()); + else + return 0; + } + + virtual int getColumns() const + { + return COLUMNS_NR; + } + + virtual int getRowHeight() const + { + return ROW_HEIGHT; + } + + virtual int getColumnWidth(int index) const + { + if (index == NAME_COLUMN) + return NAME_COLUMN_WIDTH; + else + return RELATION_CHOICE_COLUMN_WIDTH; + } + + virtual void playerRelationsUpdated() + { + signalBeforeUpdate(); + + freeWidgets(); + std::vector<std::string> *player_names = player_relations.getPlayers(); + + if (!player_names) + return; + + delete mPlayers; + mPlayers = player_names; + + // set up widgets + for (unsigned int r = 0; r < player_names->size(); ++r) + { + std::string name = (*player_names)[r]; + gcn::Widget *widget = new Label(name); + mWidgets.push_back(widget); + + gcn::DropDown *choicebox = new DropDown(mListModel); + choicebox->setSelected(player_relations.getRelation(name)); + mWidgets.push_back(choicebox); + } + + signalAfterUpdate(); + } + + virtual void updateModelInRow(int row) + { + gcn::DropDown *choicebox = static_cast<gcn::DropDown *>( + getElementAt(row, RELATION_CHOICE_COLUMN)); + player_relations.setRelation(getPlayerAt(row), + static_cast<PlayerRelation::Relation>( + choicebox->getSelected())); + } + + + virtual gcn::Widget *getElementAt(int row, int column) const + { + return mWidgets[WIDGET_AT(row, column)]; + } + + virtual void freeWidgets() + { + delete mPlayers; + mPlayers = 0; + + delete_all(mWidgets); + mWidgets.clear(); + } + + std::string getPlayerAt(int index) const + { + return (*mPlayers)[index]; + } + +protected: + std::vector<std::string> *mPlayers; + std::vector<gcn::Widget *> mWidgets; + PlayerRelationListModel *mListModel; +}; + +/** + * Class for choosing one of the various `what to do when ignoring a player' options + */ +class IgnoreChoicesListModel : public gcn::ListModel +{ +public: + virtual ~IgnoreChoicesListModel() { } + + virtual int getNumberOfElements() + { + return static_cast<int>(player_relations.getPlayerIgnoreStrategies() + ->size()); + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return (*player_relations.getPlayerIgnoreStrategies()) + [i]->mDescription; + } +}; + +#define ACTION_DELETE "delete" +#define ACTION_OLD "old" +#define ACTION_TABLE "table" +#define ACTION_STRATEGY "strategy" +#define ACTION_WHISPER_TAB "whisper tab" +#define ACTION_SHOW_GENDER "show gender" +#define ACTION_SHOW_LEVEL "show level" +#define ACTION_TARGET_DEAD "target dead" +#define ACTION_SHOW_OWN_NAME "show own name" + +Setup_Players::Setup_Players(): + mPlayerTableTitleModel(new StaticTableModel(1, COLUMNS_NR)), + mPlayerTableModel(new PlayerTableModel), + mPlayerTable(new GuiTable(mPlayerTableModel)), + mPlayerTitleTable(new GuiTable(mPlayerTableTitleModel)), + mPlayerScrollArea(new ScrollArea(mPlayerTable)), + mDefaultTrading(new CheckBox(_("Allow trading"), + player_relations.getDefault() & PlayerRelation::TRADE)), + mDefaultWhisper(new CheckBox(_("Allow whispers"), + player_relations.getDefault() & PlayerRelation::WHISPER)), + mDeleteButton(new Button(_("Delete"), ACTION_DELETE, this)), + mOldButton(new Button(_("Old"), ACTION_OLD, this)), + mWhisperTab(config.getBoolValue("whispertab")), + mWhisperTabCheckBox(new CheckBox(_("Put all whispers in tabs"), + mWhisperTab)), + mShowGender(config.getBoolValue("showgender")), + mShowGenderCheckBox(new CheckBox(_("Show gender"), mShowGender)), + mShowLevel(config.getBoolValue("showlevel")), + mShowOwnName(config.getBoolValue("showownname")), + mTargetDead(config.getBoolValue("targetDeadPlayers")) +{ + setName(_("Players")); + + mPlayerTable->setOpaque(false); + + mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH); + mPlayerTableTitleModel->fixColumnWidth(RELATION_CHOICE_COLUMN, + RELATION_CHOICE_COLUMN_WIDTH); + mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf)); + + mIgnoreActionChoicesModel = new IgnoreChoicesListModel; + mIgnoreActionChoicesBox = new DropDown(mIgnoreActionChoicesModel); + + for (int i = 0; i < COLUMNS_NR; i++) + { + mPlayerTableTitleModel->set(0, i, + new Label(gettext(table_titles[i]))); + } + + mPlayerTitleTable->setLinewiseSelection(true); + + mPlayerScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mPlayerTable->setActionEventId(ACTION_TABLE); + mPlayerTable->setLinewiseSelection(true); + mPlayerTable->addActionListener(this); + + gcn::Label *ignore_action_label = new Label(_("When ignoring:")); + + mIgnoreActionChoicesBox->setActionEventId(ACTION_STRATEGY); + mIgnoreActionChoicesBox->addActionListener(this); + + int ignore_strategy_index = 0; // safe default + + if (player_relations.getPlayerIgnoreStrategy()) + { + ignore_strategy_index = player_relations.getPlayerIgnoreStrategyIndex( + player_relations.getPlayerIgnoreStrategy()->mShortName); + if (ignore_strategy_index < 0) + ignore_strategy_index = 0; + } + mIgnoreActionChoicesBox->setSelected(ignore_strategy_index); + mIgnoreActionChoicesBox->adjustHeight(); + + mWhisperTabCheckBox->setActionEventId(ACTION_WHISPER_TAB); + mWhisperTabCheckBox->addActionListener(this); + + mShowGenderCheckBox->setActionEventId(ACTION_SHOW_GENDER); + mShowGenderCheckBox->addActionListener(this); + + mShowLevelCheckBox = new CheckBox(_("Show level"), mShowLevel); + mShowLevelCheckBox->setActionEventId(ACTION_SHOW_LEVEL); + mShowLevelCheckBox->addActionListener(this); + + mShowOwnNameCheckBox = new CheckBox(_("Show own name"), mShowOwnName); + mShowOwnNameCheckBox->setActionEventId(ACTION_SHOW_OWN_NAME); + mShowOwnNameCheckBox->addActionListener(this); + + mTargetDeadCheckBox = new CheckBox(_("Target dead players"), mTargetDead); + mTargetDeadCheckBox->setActionEventId(ACTION_TARGET_DEAD); + mTargetDeadCheckBox->addActionListener(this); + + reset(); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mPlayerTitleTable, 5); + place(0, 1, mPlayerScrollArea, 5, 4).setPadding(2); + place(0, 5, mDeleteButton); + place(0, 6, mShowGenderCheckBox, 3).setPadding(2); + place(0, 7, mShowLevelCheckBox, 3).setPadding(2); + place(0, 8, mShowOwnNameCheckBox, 3).setPadding(2); + place(1, 5, mOldButton, 1); + place(3, 5, ignore_action_label); + place(3, 6, mIgnoreActionChoicesBox, 2).setPadding(2); + place(3, 7, mDefaultTrading); + place(3, 8, mDefaultWhisper); + place(0, 9, mWhisperTabCheckBox, 4).setPadding(4); + place(0, 10, mTargetDeadCheckBox, 4).setPadding(4); + + player_relations.addListener(this); + + setDimension(gcn::Rectangle(0, 0, 500, 350)); +} + +Setup_Players::~Setup_Players() +{ + player_relations.removeListener(this); + delete mIgnoreActionChoicesModel; + mIgnoreActionChoicesModel = 0; +} + + +void Setup_Players::reset() +{ + // We now have to search through the list of ignore choices to find the + // current selection. We could use an index into the table of config + // options in player_relations instead of strategies to sidestep this. + int selection = 0; + for (unsigned int i = 0; + i < player_relations.getPlayerIgnoreStrategies()->size(); + ++i) + if ((*player_relations.getPlayerIgnoreStrategies())[i] == + player_relations.getPlayerIgnoreStrategy()) + { + + selection = i; + break; + } + + mIgnoreActionChoicesBox->setSelected(selection); +} + +void Setup_Players::apply() +{ + player_relations.store(); + + unsigned int old_default_relations = player_relations.getDefault() & + ~(PlayerRelation::TRADE | + PlayerRelation::WHISPER); + player_relations.setDefault(old_default_relations + | (mDefaultTrading->isSelected() ? + PlayerRelation::TRADE : 0) + | (mDefaultWhisper->isSelected() ? + PlayerRelation::WHISPER : 0)); + config.setValue("whispertab", mWhisperTab); + config.setValue("showlevel", mShowLevel); + config.setValue("showownname", mShowOwnName); + config.setValue("targetDeadPlayers", mTargetDead); + config.setValue("showgender", mShowGender); + + if (actorSpriteManager) + actorSpriteManager->updatePlayerNames(); + + if (player_node) + player_node->setCheckNameSetting(true); +} + +void Setup_Players::cancel() +{ + mWhisperTab = config.getBoolValue("whispertab"); + mWhisperTabCheckBox->setSelected(mWhisperTab); + mShowGender = config.getBoolValue("showgender"); + mShowGenderCheckBox->setSelected(mShowGender); + mShowLevel = config.getBoolValue("showlevel"); + mShowLevelCheckBox->setSelected(mShowLevel); + mShowOwnName = config.getBoolValue("showownname"); + mShowOwnNameCheckBox->setSelected(mShowOwnName); + mTargetDead = config.getBoolValue("targetDeadPlayers"); + mTargetDeadCheckBox->setSelected(mTargetDead); +} + +void Setup_Players::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_TABLE) + { + // temporarily eliminate ourselves: we are fully aware of this change, + // so there is no need for asynchronous updates. (In fact, thouse + // might destroy the widet that triggered them, which would be rather + // embarrassing.) + player_relations.removeListener(this); + + int row = mPlayerTable->getSelectedRow(); + if (row >= 0) + mPlayerTableModel->updateModelInRow(row); + + player_relations.addListener(this); + + } + else if (event.getId() == ACTION_DELETE) + { + int player_index = mPlayerTable->getSelectedRow(); + + if (player_index < 0) + return; + + std::string name = mPlayerTableModel->getPlayerAt(player_index); + + player_relations.removePlayer(name); + } + else if (event.getId() == ACTION_OLD) + { + player_relations.load(true); + updateAll(); + } + else if (event.getId() == ACTION_STRATEGY) + { + PlayerIgnoreStrategy *s = + (*player_relations.getPlayerIgnoreStrategies())[ + mIgnoreActionChoicesBox->getSelected()]; + + player_relations.setPlayerIgnoreStrategy(s); + } + else if (event.getId() == ACTION_WHISPER_TAB) + { + mWhisperTab = mWhisperTabCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_GENDER) + { + mShowGender = mShowGenderCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_LEVEL) + { + mShowLevel = mShowLevelCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_OWN_NAME) + { + mShowOwnName = mShowOwnNameCheckBox->isSelected(); + } + else if (event.getId() == ACTION_TARGET_DEAD) + { + mTargetDead = mTargetDeadCheckBox->isSelected(); + } +} + + +void Setup_Players::updatedPlayer(const std::string &name _UNUSED_) +{ + mPlayerTableModel->playerRelationsUpdated(); + mDefaultTrading->setSelected( + player_relations.getDefault() & PlayerRelation::TRADE); + mDefaultWhisper->setSelected( + player_relations.getDefault() & PlayerRelation::WHISPER); + if (player_node) + player_node->updateName(); +} + +void Setup_Players::updateAll() +{ + PlayerTableModel *model = new PlayerTableModel(); + mPlayerTable->setModel(model); + delete mPlayerTableModel; + mPlayerTableModel = model; + int ignore_strategy_index = 0; // safe default + + if (player_relations.getPlayerIgnoreStrategy()) + { + ignore_strategy_index = player_relations.getPlayerIgnoreStrategyIndex( + player_relations.getPlayerIgnoreStrategy()->mShortName); + if (ignore_strategy_index < 0) + ignore_strategy_index = 0; + } + mIgnoreActionChoicesBox->setSelected(ignore_strategy_index); + mIgnoreActionChoicesBox->adjustHeight(); + reset(); +} +void Setup_Players::externalUpdated() +{ + mDefaultTrading->setSelected( + player_relations.getDefault() & PlayerRelation::TRADE); + mDefaultWhisper->setSelected( + player_relations.getDefault() & PlayerRelation::WHISPER); +}
\ No newline at end of file diff --git a/src/gui/setup_players.h b/src/gui/setup_players.h new file mode 100644 index 000000000..21d3bb3e8 --- /dev/null +++ b/src/gui/setup_players.h @@ -0,0 +1,95 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_SETUP_PLAYERS_H +#define GUI_SETUP_PLAYERS_H + +#include "guichanfwd.h" +#include "playerrelations.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class GuiTable; +class PlayerTableModel; +class StaticTableModel; + +class Setup_Players : public SetupTab, + public gcn::ActionListener, + public PlayerRelationsListener +{ +public: + Setup_Players(); + virtual ~Setup_Players(); + + void apply(); + void cancel(); + + void reset(); + + void action(const gcn::ActionEvent &event); + + virtual void updatedPlayer(const std::string &name); + + virtual void updateAll(); + + virtual void externalUpdated(); + +private: + StaticTableModel *mPlayerTableTitleModel; + PlayerTableModel *mPlayerTableModel; + GuiTable *mPlayerTable; + GuiTable *mPlayerTitleTable; + gcn::ScrollArea *mPlayerScrollArea; + + gcn::CheckBox *mDefaultTrading; + gcn::CheckBox *mDefaultWhisper; + + gcn::Button *mDeleteButton; + gcn::Button *mOldButton; + + gcn::ListModel *mIgnoreActionChoicesModel; + gcn::DropDown *mIgnoreActionChoicesBox; + + bool mWhisperTab; + gcn::CheckBox *mWhisperTabCheckBox; + + bool mShowGender; + gcn::CheckBox *mShowGenderCheckBox; + + bool mShowLevel; + gcn::CheckBox *mShowLevelCheckBox; + + bool mShowOwnName; + gcn::CheckBox *mShowOwnNameCheckBox; + + bool mTargetDead; + gcn::CheckBox *mTargetDeadCheckBox; +}; + +#endif diff --git a/src/gui/setup_theme.cpp b/src/gui/setup_theme.cpp new file mode 100644 index 000000000..faaeeb00f --- /dev/null +++ b/src/gui/setup_theme.cpp @@ -0,0 +1,239 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/setup_theme.h" + +#include "gui/gui.h" +#include "gui/editdialog.h" +#include "gui/okdialog.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/dropdown.h" + +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "utils/gettext.h" + +#include "resources/resourcemanager.h" + +const char* ACTION_THEME = "theme"; +const char* ACTION_FONT = "font"; +const char* ACTION_BOLD_FONT = "bold font"; +const char* ACTION_PARTICLE_FONT = "particle font"; +const char* ACTION_HELP_FONT = "help font"; + +class NamesModel : public gcn::ListModel +{ +public: + NamesModel() + { + } + + virtual ~NamesModel() { } + + virtual int getNumberOfElements() + { + return static_cast<int>(mNames.size()); + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return mNames[i]; + } + +protected: + std::vector<std::string> mNames; +}; + +class ThemesModel : public NamesModel +{ +public: + ThemesModel() + { + mNames.push_back("(default)"); + Theme::fillSkinsList(mNames); + } + + virtual ~ThemesModel() + { } +}; + +class FontsModel : public NamesModel +{ +public: + FontsModel() + { Theme::fillFontsList(mNames); } + + virtual ~FontsModel() + { } +}; + +Setup_Theme::Setup_Theme(): + mTheme(config.getValue("theme", config.getValue("selectedSkin", ""))), + mFont(config.getStringValue("font")), + mBoldFont(config.getStringValue("boldFont")), + mParticleFont(config.getStringValue("particleFont")), + mHelpFont(config.getStringValue("helpFont")) +{ + setName(_("Theme")); + + mThemeLabel = new Label(_("Gui theme")); + mFontLabel = new Label(_("Main Font")); + mBoldFontLabel = new Label(_("Bold font")); + mParticleFontLabel = new Label(_("Particle font")); + mHelpFontLabel = new Label(_("Help font")); + mThemesModel = new ThemesModel(); + mFontsModel = new FontsModel(); + + mThemeDropDown = new DropDown(mThemesModel); + mThemeDropDown->setActionEventId(ACTION_THEME); + mThemeDropDown->addActionListener(this); + + mFontDropDown = new DropDown(mFontsModel); + mFontDropDown->setActionEventId(ACTION_FONT); + mFontDropDown->addActionListener(this); + + mBoldFontDropDown = new DropDown(mFontsModel); + mBoldFontDropDown->setActionEventId(ACTION_BOLD_FONT); + mBoldFontDropDown->addActionListener(this); + + mParticleFontDropDown = new DropDown(mFontsModel); + mParticleFontDropDown->setActionEventId(ACTION_PARTICLE_FONT); + mParticleFontDropDown->addActionListener(this); + + mHelpFontDropDown = new DropDown(mFontsModel); + mHelpFontDropDown->setActionEventId(ACTION_HELP_FONT); + mHelpFontDropDown->addActionListener(this); + + std::string skin = Theme::getThemeName(); + if (!skin.empty()) + mThemeDropDown->setSelectedString(skin); + else + mThemeDropDown->setSelected(0); + + mFontDropDown->setSelectedString(getFileName( + config.getStringValue("font"))); + mBoldFontDropDown->setSelectedString(getFileName( + config.getStringValue("boldFont"))); + mParticleFontDropDown->setSelectedString(getFileName( + config.getStringValue("particleFont"))); + mHelpFontDropDown->setSelectedString(getFileName( + config.getStringValue("helpFont"))); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mThemeLabel, 10); + place(0, 1, mThemeDropDown, 6); + place(0, 2, mFontLabel, 10); + place(0, 3, mFontDropDown, 6); + place(0, 4, mBoldFontLabel, 10); + place(0, 5, mBoldFontDropDown, 6); + place(0, 6, mParticleFontLabel, 10); + place(0, 7, mParticleFontDropDown, 6); + place(0, 8, mHelpFontLabel, 10); + place(0, 9, mHelpFontDropDown, 6); + + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + + setDimension(gcn::Rectangle(0, 0, 365, 500)); +} + +Setup_Theme::~Setup_Theme() +{ + delete mThemesModel; + mThemesModel = 0; + + delete mFontsModel; + mFontsModel = 0; +} + +void Setup_Theme::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_THEME) + { + if (mThemeDropDown->getSelected() == 0) + mTheme = ""; + else + mTheme = mThemeDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_FONT) + { + mFont = mFontDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_BOLD_FONT) + { + mBoldFont = mBoldFontDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_PARTICLE_FONT) + { + mParticleFont = mParticleFontDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_HELP_FONT) + { + mHelpFont = mHelpFontDropDown->getSelectedString(); + } +} + +void Setup_Theme::cancel() +{ + mTheme = config.getValue("theme", config.getValue("selectedSkin", "")); + mFont = getFileName(config.getStringValue("font")); + mBoldFont = getFileName(config.getStringValue("boldFont")); + mParticleFont = getFileName(config.getStringValue("particleFont")); + mHelpFont = getFileName(config.getStringValue("helpFont")); +} + +void Setup_Theme::apply() +{ + if (config.getValue("theme", + config.getValue("selectedSkin", "")) != mTheme) + { + new OkDialog(_("Theme Changed"), + _("Restart your client for the change to take effect.")); + } + config.setValue("selectedSkin", ""); + config.setValue("theme", mTheme); + if (config.getValue("font", "dejavusans.ttf") != mFont + || config.getValue("boldFont", "dejavusans-bold.ttf") != mBoldFont + || config.getValue("particleFont", "dejavusans.ttf") != mParticleFont + || config.getValue("helpFont", "dejavusansmono.ttf") != mHelpFont) + { + config.setValue("font", "fonts/" + getFileName(mFont)); + config.setValue("boldFont", "fonts/" + getFileName(mBoldFont)); + config.setValue("particleFont", "fonts/" + getFileName(mParticleFont)); + config.setValue("helpFont", "fonts/" + getFileName(mHelpFont)); + gui->updateFonts(); + } +} diff --git a/src/gui/setup_theme.h b/src/gui/setup_theme.h new file mode 100644 index 000000000..6b2c99999 --- /dev/null +++ b/src/gui/setup_theme.h @@ -0,0 +1,74 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef GUI_Setup_Theme_H +#define GUI_Setup_Theme_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> + +class FontsModel; +class EditDialog; +class DropDown; +class ThemesModel; + +class Setup_Theme : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Theme(); + ~Setup_Theme(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + gcn::Label *mThemeLabel; + DropDown *mThemeDropDown; + std::string mTheme; + ThemesModel *mThemesModel; + FontsModel *mFontsModel; + + gcn::Label *mFontLabel; + DropDown *mFontDropDown; + std::string mFont; + + gcn::Label *mBoldFontLabel; + DropDown *mBoldFontDropDown; + std::string mBoldFont; + + gcn::Label *mParticleFontLabel; + DropDown *mParticleFontDropDown; + std::string mParticleFont; + + gcn::Label *mHelpFontLabel; + DropDown *mHelpFontDropDown; + std::string mHelpFont; + + EditDialog *mEditDialog; +}; + +#endif diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp new file mode 100644 index 000000000..9399756aa --- /dev/null +++ b/src/gui/setup_video.cpp @@ -0,0 +1,819 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/setup_video.h" + +#include "configuration.h" +#include "game.h" +#include "graphics.h" +#include "localplayer.h" +#include "log.h" +#include "main.h" +#include "particle.h" + +#include "gui/gui.h" +#include "gui/okdialog.h" +#include "gui/textdialog.h" + +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/dropdown.h" + +#include "resources/image.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/key.hpp> +#include <guichan/listmodel.hpp> + +#include <SDL.h> + +#include <string> +#include <vector> + +extern Graphics *graphics; + +/** + * The list model for mode list. + * + * \ingroup Interface + */ +class ModeListModel : public gcn::ListModel +{ + public: + /** + * Constructor. + */ + ModeListModel(); + + /** + * Destructor. + */ + virtual ~ModeListModel() + { } + + /** + * Returns the number of elements in container. + */ + int getNumberOfElements() + { return static_cast<int>(mVideoModes.size()); } + + /** + * Returns element from container. + */ + std::string getElementAt(int i) + { return mVideoModes[i]; } + + /** + * Returns the index corresponding to the given video mode. + * E.g.: "800x600". + * or -1 if not found. + */ + int getIndexOf(const std::string &widthXHeightMode); + + private: + std::vector<std::string> mVideoModes; +}; + +ModeListModel::ModeListModel() +{ + /* Get available fullscreen/hardware modes */ + SDL_Rect **modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); + + /* Check which modes are available */ + if (modes == static_cast<SDL_Rect **>(0)) + { + logger->log1("No modes available"); + } + else if (modes == (SDL_Rect **)-1) + { + logger->log1("All resolutions available"); + } + else + { + for (int i = 0; modes[i]; ++i) + { + const std::string modeString = + toString(static_cast<int>(modes[i]->w)) + "x" + + toString(static_cast<int>(modes[i]->h)); + mVideoModes.push_back(modeString); + } + } + mVideoModes.push_back("custom"); +} + +int ModeListModel::getIndexOf(const std::string &widthXHeightMode) +{ + std::string currentMode = ""; + for (int i = 0; i < getNumberOfElements(); i++) + { + currentMode = getElementAt(i); + if (currentMode == widthXHeightMode) + return i; + } + return -1; +} + +const char *SIZE_NAME[6] = +{ + N_("Tiny (10)"), + N_("Small (11)"), + N_("Medium (12)"), + N_("Large (13)"), + N_("Big (14)"), + N_("Huge (15)"), +}; + +class FontSizeChoiceListModel : public gcn::ListModel +{ +public: + virtual ~FontSizeChoiceListModel() + { } + + virtual int getNumberOfElements() + { return 6; } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return SIZE_NAME[i]; + } +}; + +const char *OPENGL_NAME[3] = +{ + N_("Software"), + N_("Fast OpenGL"), + N_("Safe OpenGL"), +}; + +class OpenGLListModel : public gcn::ListModel +{ +public: + virtual ~OpenGLListModel() + { } + + virtual int getNumberOfElements() + { return 3; } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return OPENGL_NAME[i]; + } +}; + +static const char *speechModeToString(Being::Speech mode) +{ + switch (mode) + { + case Being::NO_SPEECH: + default: + return _("No text"); + case Being::TEXT_OVERHEAD: + return _("Text"); + case Being::NO_NAME_IN_BUBBLE: + return _("Bubbles, no names"); + case Being::NAME_IN_BUBBLE: + return _("Bubbles with names"); + } + return ""; +} + +const char *Setup_Video::overlayDetailToString(int detail) +{ + if (detail == -1) + detail = config.getIntValue("OverlayDetail"); + + switch (detail) + { + case 0: + return _("off"); + case 1: + return _("low"); + case 2: + return _("high"); + default: + return ""; + } + return ""; +} + +const char *Setup_Video::particleDetailToString(int detail) +{ + if (detail == -1) + detail = 3 - config.getIntValue("particleEmitterSkip"); + + switch (detail) + { + case 0: + return _("low"); + case 1: + return _("medium"); + case 2: + return _("high"); + case 3: + return _("max"); + default: + return ""; + } + return ""; +} + +Setup_Video::Setup_Video(): + mFullScreenEnabled(config.getBoolValue("screen")), + mOpenGLEnabled(config.getIntValue("opengl")), + mHwAccelEnabled(config.getBoolValue("hwaccel")), + mCustomCursorEnabled(config.getBoolValue("customcursor")), + mVisibleNamesEnabled(config.getBoolValue("visiblenames")), + mParticleEffectsEnabled(config.getBoolValue("particleeffects")), + mNPCLogEnabled(config.getBoolValue("logNpcInGui")), + mPickupChatEnabled(config.getBoolValue("showpickupchat")), + mPickupParticleEnabled(config.getBoolValue("showpickupparticle")), + mOpacity(config.getFloatValue("guialpha")), + mFps(config.getIntValue("fpslimit")), + mAltFps(config.getIntValue("altfpslimit")), + mHideShieldSprite(config.getBoolValue("hideShield")), + mLowTraffic(config.getBoolValue("lowTraffic")), + mSyncPlayerMove(config.getBoolValue("syncPlayerMove")), + mDrawHotKeys(config.getBoolValue("drawHotKeys")), + mDrawPath(config.getBoolValue("drawPath")), + mShowJob(serverConfig.getBoolValue("showJob")), + mAlphaCache(config.getBoolValue("alphaCache")), + mShowBackground(config.getBoolValue("showBackground")), + mSpeechMode(static_cast<Being::Speech>( + config.getIntValue("speech"))), + mModeListModel(new ModeListModel), + mModeList(new ListBox(mModeListModel)), + mFsCheckBox(new CheckBox(_("Full screen"), mFullScreenEnabled)), + mHwAccelCheckBox(new CheckBox(_("Hw acceleration"), mHwAccelEnabled)), + mCustomCursorCheckBox(new CheckBox(_("Custom cursor"), + mCustomCursorEnabled)), + mVisibleNamesCheckBox(new CheckBox(_("Visible names"), + mVisibleNamesEnabled)), + mParticleEffectsCheckBox(new CheckBox(_("Particle effects"), + mParticleEffectsEnabled)), + mNPCLogCheckBox(new CheckBox(_("Log NPC dialogue"), mNPCLogEnabled)), + mPickupNotifyLabel(new Label(_("Show pickup notification"))), + // TRANSLATORS: Refers to "Show own name" + mPickupChatCheckBox(new CheckBox(_("in chat"), mPickupChatEnabled)), + // TRANSLATORS: Refers to "Show own name" + mPickupParticleCheckBox(new CheckBox(_("as particle"), + mPickupParticleEnabled)), + mHideShieldSpriteCheckBox(new CheckBox(_("Hide shield sprite"), + mHideShieldSprite)), + mLowTrafficCheckBox(new CheckBox(_("Low traffic mode"), + mLowTraffic)), + mSyncPlayerMoveCheckBox(new CheckBox(_("Sync player move"), + mSyncPlayerMove)), + mDrawHotKeysCheckBox(new CheckBox(_("Draw hotkeys on map"), + mDrawHotKeys)), + mDrawPathCheckBox(new CheckBox(_("Draw path"), mDrawPath)), + mShowJobCheckBox(new CheckBox(_("Show job"), mShowJob)), + mAlphaCacheCheckBox(new CheckBox(_("Enable opacity cache"), mAlphaCache)), + mShowBackgroundCheckBox(new CheckBox(_("Show background"), + mShowBackground)), + mSpeechSlider(new Slider(0, 3)), + mSpeechLabel(new Label("")), + mAlphaSlider(new Slider(0.1, 1.0)), + mFpsCheckBox(new CheckBox(_("FPS limit:"))), + mFpsSlider(new Slider(2, 160)), + mFpsLabel(new Label), + mAltFpsSlider(new Slider(2, 160)), + mAltFpsLabel(new Label(_("Alt FPS limit: "))), + mOverlayDetail(config.getIntValue("OverlayDetail")), + mOverlayDetailSlider(new Slider(0, 2)), + mOverlayDetailField(new Label), + mParticleDetail(3 - config.getIntValue("particleEmitterSkip")), + mParticleDetailSlider(new Slider(0, 3)), + mParticleDetailField(new Label), + mFontSize(config.getIntValue("fontSize")), + mDialog(0) +{ + setName(_("Video")); + + ScrollArea *scrollArea = new ScrollArea(mModeList); + scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + speechLabel = new Label(_("Overhead text")); + alphaLabel = new Label(_("Gui opacity")); + overlayDetailLabel = new Label(_("Ambient FX")); + particleDetailLabel = new Label(_("Particle detail")); + fontSizeLabel = new Label(_("Font size")); + + mOpenGLListModel = new OpenGLListModel; + mOpenGLDropDown = new DropDown(mOpenGLListModel), + mOpenGLDropDown->setSelected(mOpenGLEnabled); + + mFontSizeListModel = new FontSizeChoiceListModel; + mFontSizeDropDown = new DropDown(mFontSizeListModel); + + mModeList->setEnabled(true); + +#ifndef USE_OPENGL + mOpenGLDropDown->setSelected(0); +#endif + + mAlphaSlider->setValue(mOpacity); + mAlphaSlider->setWidth(90); + + mFpsLabel->setCaption(mFps > 0 ? toString(mFps) : _("None")); + mFpsLabel->setWidth(60); + mAltFpsLabel->setCaption(_("Alt FPS limit: ") + (mAltFps > 0 + ? toString(mAltFps) : _("None"))); + mAltFpsLabel->setWidth(150); + mFpsSlider->setValue(mFps); + mFpsSlider->setEnabled(mFps > 0); + mAltFpsSlider->setValue(mAltFps); + mAltFpsSlider->setEnabled(mAltFps > 0); + mFpsCheckBox->setSelected(mFps > 0); + + // Pre-select the current video mode. + std::string videoMode = toString(graphics->getWidth()) + "x" + + toString(graphics->getHeight()); + mModeList->setSelected(mModeListModel->getIndexOf(videoMode)); + + mModeList->setActionEventId("videomode"); + mCustomCursorCheckBox->setActionEventId("customcursor"); + mVisibleNamesCheckBox->setActionEventId("visiblenames"); + mParticleEffectsCheckBox->setActionEventId("particleeffects"); + mPickupChatCheckBox->setActionEventId("pickupchat"); + mPickupParticleCheckBox->setActionEventId("pickupparticle"); + mNPCLogCheckBox->setActionEventId("lognpc"); + mAlphaSlider->setActionEventId("guialpha"); + mFpsCheckBox->setActionEventId("fpslimitcheckbox"); + mSpeechSlider->setActionEventId("speech"); + mFpsSlider->setActionEventId("fpslimitslider"); + mAltFpsSlider->setActionEventId("altfpslimitslider"); + mOverlayDetailSlider->setActionEventId("overlaydetailslider"); + mOverlayDetailField->setActionEventId("overlaydetailfield"); + mParticleDetailSlider->setActionEventId("particledetailslider"); + mParticleDetailField->setActionEventId("particledetailfield"); + mHideShieldSpriteCheckBox->setActionEventId("hideshieldsprite1"); + mLowTrafficCheckBox->setActionEventId("lowTraffic1"); + mSyncPlayerMoveCheckBox->setActionEventId("syncPlayerMove1"); + mDrawHotKeysCheckBox->setActionEventId("drawHotKeys"); + mDrawPathCheckBox->setActionEventId("drawPath1"); + mShowJobCheckBox->setActionEventId("showJob"); + mAlphaCacheCheckBox->setActionEventId("alphaCache"); + + mModeList->addActionListener(this); + mCustomCursorCheckBox->addActionListener(this); + mVisibleNamesCheckBox->addActionListener(this); + mParticleEffectsCheckBox->addActionListener(this); + mPickupChatCheckBox->addActionListener(this); + mPickupParticleCheckBox->addActionListener(this); + mNPCLogCheckBox->addActionListener(this); + mAlphaSlider->addActionListener(this); + mFpsCheckBox->addActionListener(this); + mSpeechSlider->addActionListener(this); + mFpsSlider->addActionListener(this); + mAltFpsSlider->addActionListener(this); + mOverlayDetailSlider->addActionListener(this); + mOverlayDetailField->addKeyListener(this); + mParticleDetailSlider->addActionListener(this); + mParticleDetailField->addKeyListener(this); + mHideShieldSpriteCheckBox->addKeyListener(this); + mLowTrafficCheckBox->addKeyListener(this); + mSyncPlayerMoveCheckBox->addKeyListener(this); + mDrawHotKeysCheckBox->addKeyListener(this); + mDrawPathCheckBox->addKeyListener(this); + mShowJobCheckBox->addKeyListener(this); + mAlphaCacheCheckBox->addKeyListener(this); + + mSpeechLabel->setCaption(speechModeToString(mSpeechMode)); + mSpeechSlider->setValue(mSpeechMode); + + mOverlayDetailField->setCaption(overlayDetailToString(mOverlayDetail)); + mOverlayDetailSlider->setValue(mOverlayDetail); + + mParticleDetailField->setCaption(particleDetailToString(mParticleDetail)); + mParticleDetailSlider->setValue(mParticleDetail); + + mFontSizeDropDown->setSelected(mFontSize - 10); + mFontSizeDropDown->adjustHeight(); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, scrollArea, 1, 5).setPadding(2); + place(1, 0, mFsCheckBox, 2); + place(3, 0, mOpenGLDropDown, 1); + + place(1, 1, mCustomCursorCheckBox, 3); + place(3, 1, mHwAccelCheckBox, 1); + + place(1, 2, mVisibleNamesCheckBox, 3); + + place(3, 2, mAlphaCacheCheckBox, 4); + + place(1, 3, mParticleEffectsCheckBox, 2); + + place(1, 4, mPickupNotifyLabel, 4); + + place(1, 5, mPickupChatCheckBox, 1); + place(2, 5, mPickupParticleCheckBox, 2); + + place(0, 6, fontSizeLabel, 3); + place(1, 6, mFontSizeDropDown, 1); + + place(0, 7, mAlphaSlider); + place(1, 7, alphaLabel, 3); + + place(0, 8, mFpsSlider); + place(1, 8, mFpsCheckBox).setPadding(3); + place(2, 8, mFpsLabel).setPadding(1); + + place(0, 9, mAltFpsSlider); + place(1, 9, mAltFpsLabel).setPadding(3); + + place(0, 10, mSpeechSlider); + place(1, 10, speechLabel); + place(2, 10, mSpeechLabel, 3).setPadding(2); + + place(0, 11, mOverlayDetailSlider); + place(1, 11, overlayDetailLabel); + place(2, 11, mOverlayDetailField, 3).setPadding(2); + + place(0, 12, mParticleDetailSlider); + place(1, 12, particleDetailLabel); + place(2, 12, mParticleDetailField, 3).setPadding(2); + + place(3, 7, mHideShieldSpriteCheckBox); + place(3, 8, mLowTrafficCheckBox); + place(0, 13, mShowBackgroundCheckBox); + place(0, 14, mSyncPlayerMoveCheckBox); + place(0, 15, mDrawHotKeysCheckBox); + place(2, 13, mDrawPathCheckBox, 2); + place(2, 14, mShowJobCheckBox, 2); + place(2, 15, mNPCLogCheckBox, 2); + + int width = 600; + + if (config.getIntValue("screenwidth") >= 730) + width += 100; + + setDimension(gcn::Rectangle(0, 0, width, 300)); +} + +Setup_Video::~Setup_Video() +{ + delete mModeListModel; + mModeListModel = 0; + delete mModeList; + mModeList = 0; + delete mFontSizeListModel; + mFontSizeListModel = 0; + delete mOpenGLListModel; + mOpenGLListModel = 0; + delete mDialog; + mDialog = 0; +} + +void Setup_Video::apply() +{ + // Full screen changes + bool fullscreen = mFsCheckBox->isSelected(); + if (fullscreen != config.getBoolValue("screen")) + { + /* The OpenGL test is only necessary on Windows, since switching + * to/from full screen works fine on Linux. On Windows we'd have to + * reinitialize the OpenGL state and reload all textures. + * + * See http://libsdl.org/cgi/docwiki.cgi/SDL_SetVideoMode + */ + +#if defined(WIN32) || defined(__APPLE__) + // checks for opengl usage + if (!config.getIntValue("opengl")) + { +#endif + if (!graphics->setFullscreen(fullscreen)) + { + fullscreen = !fullscreen; + if (!graphics->setFullscreen(fullscreen)) + { + std::stringstream errorMessage; + if (fullscreen) + { + errorMessage << _("Failed to switch to windowed mode " + "and restoration of old mode also " + "failed!") << std::endl; + } + else + { + errorMessage << _("Failed to switch to fullscreen mode" + " and restoration of old mode also " + "failed!") << std::endl; + } + logger->error(errorMessage.str()); + } + } +#if defined(WIN32) || defined(__APPLE__) + } + else + { + new OkDialog(_("Switching to Full Screen"), + _("Restart needed for changes to take effect.")); + } +#endif + config.setValue("screen", fullscreen); + } + + // OpenGL change + if (mOpenGLDropDown->getSelected() != mOpenGLEnabled) + { + config.setValue("opengl", mOpenGLDropDown->getSelected()); + + // OpenGL can currently only be changed by restarting, notify user. + new OkDialog(_("Changing to OpenGL"), + _("Applying change to OpenGL requires restart.")); + } + + mFps = mFpsCheckBox->isSelected() ? + static_cast<int>(mFpsSlider->getValue()) : 0; + + mAltFps = static_cast<int>(mAltFpsSlider->getValue()); + + mFpsSlider->setEnabled(mFps > 0); + + mAltFpsSlider->setEnabled(mAltFps > 0); + + config.setValue("hwaccel", mHwAccelCheckBox->isSelected()); + + // FPS change + config.setValue("fpslimit", mFps); + config.setValue("altfpslimit", mAltFps); + + if (config.getIntValue("fontSize") + != static_cast<int>(mFontSizeDropDown->getSelected()) + 10) + { + config.setValue("fontSize", mFontSizeDropDown->getSelected() + 10); + gui->updateFonts(); + } + + config.setValue("hideShield", mHideShieldSpriteCheckBox->isSelected()); + config.setValue("lowTraffic", mLowTrafficCheckBox->isSelected()); + config.setValue("syncPlayerMove", mSyncPlayerMoveCheckBox->isSelected()); + config.setValue("drawHotKeys", mDrawHotKeysCheckBox->isSelected()); + config.setValue("drawPath", mDrawPathCheckBox->isSelected()); + serverConfig.setValue("showJob", mShowJobCheckBox->isSelected()); + config.setValue("alphaCache", mAlphaCacheCheckBox->isSelected()); + config.setValue("showBackground", mShowBackgroundCheckBox->isSelected()); + + // We sync old and new values at apply time + mFullScreenEnabled = config.getBoolValue("screen"); + mCustomCursorEnabled = config.getBoolValue("customcursor"); + mVisibleNamesEnabled = config.getBoolValue("visiblenames"); + mParticleEffectsEnabled = config.getBoolValue("particleeffects"); + mNPCLogEnabled = config.getBoolValue("logNpcInGui"); + mHideShieldSprite = config.getBoolValue("hideShield"); + mLowTraffic = config.getBoolValue("lowTraffic"); + mSyncPlayerMove = config.getBoolValue("syncPlayerMove"); + mDrawHotKeys = config.getBoolValue("drawHotKeys"); + mDrawPath = config.getBoolValue("drawPath"); + mShowJob = serverConfig.getBoolValue("showJob"); + mAlphaCache = config.getBoolValue("alphaCache"); + mShowBackground = config.getBoolValue("showBackground"); + + mSpeechMode = static_cast<Being::Speech>( + config.getIntValue("speech")); + mOpacity = config.getFloatValue("guialpha"); + mOverlayDetail = config.getIntValue("OverlayDetail"); + mOpenGLEnabled = config.getIntValue("opengl"); + mHwAccelEnabled = config.getBoolValue("hwaccel"); + mPickupChatEnabled = config.getBoolValue("showpickupchat"); + mPickupParticleEnabled = config.getBoolValue("showpickupparticle"); +} + +void Setup_Video::cancel() +{ + mFpsCheckBox->setSelected(mFps > 0); + mFsCheckBox->setSelected(mFullScreenEnabled); + mOpenGLDropDown->setSelected(mOpenGLEnabled); + mHwAccelCheckBox->setSelected(mHwAccelEnabled); + mCustomCursorCheckBox->setSelected(mCustomCursorEnabled); + mVisibleNamesCheckBox->setSelected(mVisibleNamesEnabled); + mParticleEffectsCheckBox->setSelected(mParticleEffectsEnabled); + mFpsSlider->setValue(mFps); + mFpsSlider->setEnabled(mFps > 0); + mAltFpsSlider->setValue(mAltFps); + mAltFpsSlider->setEnabled(mAltFps > 0); + mSpeechSlider->setValue(mSpeechMode); + mNPCLogCheckBox->setSelected(mNPCLogEnabled); + mHideShieldSpriteCheckBox->setSelected(mHideShieldSprite); + mLowTrafficCheckBox->setSelected(mLowTraffic); + mSyncPlayerMoveCheckBox->setSelected(mSyncPlayerMove); + mDrawHotKeysCheckBox->setSelected(mDrawHotKeys); + mDrawPathCheckBox->setSelected(mDrawPath); + mShowJobCheckBox->setSelected(mShowJob); + mAlphaCacheCheckBox->setSelected(mAlphaCache); + mShowBackgroundCheckBox->setSelected(mShowBackground); + mAlphaSlider->setValue(mOpacity); + mOverlayDetailSlider->setValue(mOverlayDetail); + mParticleDetailSlider->setValue(mParticleDetail); + mFpsLabel->setCaption(mFpsCheckBox->isSelected() + ? toString(mFps) : _("None")); + mAltFpsLabel->setCaption(_("Alt FPS limit: ") + toString(mAltFps)); + + config.setValue("screen", mFullScreenEnabled); + + // Set back to the current video mode. + std::string videoMode = toString(graphics->getWidth()) + "x" + + toString(graphics->getHeight()); + mModeList->setSelected(mModeListModel->getIndexOf(videoMode)); + config.setValue("screenwidth", graphics->getWidth()); + config.setValue("screenheight", graphics->getHeight()); + + config.setValue("customcursor", mCustomCursorEnabled); + config.setValue("visiblenames", mVisibleNamesEnabled); + config.setValue("particleeffects", mParticleEffectsEnabled); + config.setValue("speech", static_cast<int>(mSpeechMode)); + config.setValue("logNpcInGui", mNPCLogEnabled); + config.setValue("hideShield", mHideShieldSprite); + config.setValue("lowTraffic", mLowTraffic); + config.setValue("syncPlayerMove", mSyncPlayerMove); + config.setValue("drawHotKeys", mDrawHotKeys); + config.setValue("drawPath", mDrawPath); + serverConfig.setValue("showJob", mShowJob); + config.setValue("alphaCache", mAlphaCache); + config.setValue("showBackground", mShowBackground); + config.setValue("guialpha", mOpacity); + Image::setEnableAlpha(mOpacity != 1.0f); + config.setValue("opengl", mOpenGLEnabled); + config.setValue("hwaccel", mHwAccelEnabled); + config.setValue("showpickupchat", mPickupChatEnabled); + config.setValue("showpickupparticle", mPickupParticleEnabled); +} + +void Setup_Video::action(const gcn::ActionEvent &event) +{ + const std::string &id = event.getId(); + + if (id == "videomode") + { + std::string mode = mModeListModel->getElementAt( + mModeList->getSelected()); + + if (mode == "custom") + { + if (mDialog) + { + mode = mDialog->getText(); + mDialog = 0; + } + else + { + mDialog = new TextDialog( + _("Custom resolution (example: 1024x768)"), + _("Enter new resolution: ")); + mDialog->setActionEventId("videomode"); + mDialog->addActionListener(this); + return; + } + } + const int width = atoi(mode.substr(0, mode.find("x")).c_str()); + const int height = atoi(mode.substr(mode.find("x") + 1).c_str()); + if (!width || !height) + return; + + // TODO: Find out why the drawing area doesn't resize without a restart. + if (width != graphics->getWidth() || height != graphics->getHeight()) + { + if (width < graphics->getWidth() || height < graphics->getHeight()) + new OkDialog(_("Screen Resolution Changed"), + _("Restart your client for the change to take effect.") + + std::string("\n") + + _("Some windows may be moved to fit the lowered resolution.")); + else + new OkDialog(_("Screen Resolution Changed"), + _("Restart your client for the change to take effect.")); + } + + config.setValue("screenwidth", width); + config.setValue("screenheight", height); + } + if (id == "~videomode") + { + mDialog = 0; + } + else if (id == "guialpha") + { + config.setValue("guialpha", mAlphaSlider->getValue()); + Image::setEnableAlpha(config.getFloatValue("guialpha") != 1.0f); + } + else if (id == "customcursor") + { + config.setValue("customcursor", mCustomCursorCheckBox->isSelected()); + } + else if (id == "visiblenames") + { + config.setValue("visiblenames", mVisibleNamesCheckBox->isSelected()); + } + else if (id == "particleeffects") + { + config.setValue("particleeffects", + mParticleEffectsCheckBox->isSelected()); + Particle::enabled = mParticleEffectsCheckBox->isSelected(); + + if (Game::instance()) + { + new OkDialog(_("Particle Effect Settings Changed."), + _("Changes will take effect on map change.")); + } + } + else if (id == "pickupchat") + { + config.setValue("showpickupchat", mPickupChatCheckBox->isSelected()); + } + else if (id == "pickupparticle") + { + config.setValue("showpickupparticle", + mPickupParticleCheckBox->isSelected()); + } + else if (id == "speech") + { + Being::Speech val = static_cast<Being::Speech>( + mSpeechSlider->getValue()); + mSpeechLabel->setCaption(speechModeToString(val)); + mSpeechSlider->setValue(val); + config.setValue("speech", (int)val); + } + else if (id == "lognpc") + { + config.setValue("logNpcInGui", mNPCLogCheckBox->isSelected()); + } + else if (id == "overlaydetailslider") + { + int val = static_cast<int>(mOverlayDetailSlider->getValue()); + mOverlayDetailField->setCaption(overlayDetailToString(val)); + config.setValue("OverlayDetail", val); + } + else if (id == "particledetailslider") + { + int val = static_cast<int>(mParticleDetailSlider->getValue()); + mParticleDetailField->setCaption(particleDetailToString(val)); + config.setValue("particleEmitterSkip", 3 - val); + Particle::emitterSkip = 4 - val; + } + else if (id == "fpslimitcheckbox" || id == "fpslimitslider") + { + int fps = static_cast<int>(mFpsSlider->getValue()); + if (id == "fpslimitcheckbox" && !mFpsSlider->isEnabled()) + fps = 60; + else + fps = fps > 0 ? fps : 60; + mFps = mFpsCheckBox->isSelected() ? fps : 0; + const std::string text = mFps > 0 ? toString(mFps) : _("None"); + + mFpsLabel->setCaption(text); + mFpsSlider->setValue(mFps); + mFpsSlider->setEnabled(mFps > 0); + } + else if (id == "altfpslimitslider") + { + int fps = static_cast<int>(mAltFpsSlider->getValue()); + fps = fps > 0 ? fps : static_cast<int>(mAltFpsSlider->getScaleStart()); + mAltFps = fps; + const std::string text = mAltFps > 0 ? toString(mAltFps) : _("None"); + + mAltFpsLabel->setCaption(_("Alt FPS limit: ") + text); + mAltFpsSlider->setValue(mAltFps); + mAltFpsSlider->setEnabled(mAltFps > 0); + } +} + +void Setup_Video::externalUpdated() +{ + mShowJob = serverConfig.getBoolValue("showJob"); + mShowJobCheckBox->setSelected(mShowJob); +}
\ No newline at end of file diff --git a/src/gui/setup_video.h b/src/gui/setup_video.h new file mode 100644 index 000000000..c9a72666a --- /dev/null +++ b/src/gui/setup_video.h @@ -0,0 +1,136 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_SETUP_VIDEO_H +#define GUI_SETUP_VIDEO_H + +#include "being.h" +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> + +class FontSizeChoiceListModel; +class ModeListModel; +class OpenGLListModel; +class TextDialog; + +class Setup_Video : public SetupTab, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + Setup_Video(); + ~Setup_Video(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + static const char *overlayDetailToString(int detail = -1); + + static const char *particleDetailToString(int detail = -1); + + virtual void externalUpdated(); + + private: + bool mFullScreenEnabled; + int mOpenGLEnabled; + bool mHwAccelEnabled; + bool mCustomCursorEnabled; + bool mVisibleNamesEnabled; + bool mParticleEffectsEnabled; + bool mNPCLogEnabled; + bool mPickupChatEnabled; + bool mPickupParticleEnabled; + double mOpacity; + int mFps; + int mAltFps; + bool mHideShieldSprite; + bool mLowTraffic; + bool mSyncPlayerMove; + bool mDrawHotKeys; + bool mDrawPath; + bool mShowJob; + bool mAlphaCache; + bool mShowBackground; + Being::Speech mSpeechMode; + + ModeListModel *mModeListModel; + FontSizeChoiceListModel *mFontSizeListModel; + + OpenGLListModel *mOpenGLListModel; + + gcn::Label *speechLabel; + gcn::Label *alphaLabel; + gcn::Label *scrollRadiusLabel; + gcn::Label *scrollLazinessLabel; + gcn::Label *overlayDetailLabel; + gcn::Label *particleDetailLabel; + gcn::Label *fontSizeLabel; + + gcn::ListBox *mModeList; + gcn::CheckBox *mFsCheckBox; + gcn::DropDown *mOpenGLDropDown; + gcn::CheckBox *mHwAccelCheckBox; + gcn::CheckBox *mCustomCursorCheckBox; + gcn::CheckBox *mVisibleNamesCheckBox; + gcn::CheckBox *mParticleEffectsCheckBox; + gcn::CheckBox *mNPCLogCheckBox; + + gcn::Label *mPickupNotifyLabel; + gcn::CheckBox *mPickupChatCheckBox; + gcn::CheckBox *mPickupParticleCheckBox; + + gcn::CheckBox *mHideShieldSpriteCheckBox; + gcn::CheckBox *mLowTrafficCheckBox; + gcn::CheckBox *mSyncPlayerMoveCheckBox; + gcn::CheckBox *mDrawHotKeysCheckBox; + gcn::CheckBox *mDrawPathCheckBox; + gcn::CheckBox *mShowJobCheckBox; + gcn::CheckBox *mAlphaCacheCheckBox; + gcn::CheckBox *mShowBackgroundCheckBox; + gcn::Slider *mSpeechSlider; + gcn::Label *mSpeechLabel; + gcn::Slider *mAlphaSlider; + gcn::CheckBox *mFpsCheckBox; + gcn::Slider *mFpsSlider; + gcn::Label *mFpsLabel; +// gcn::CheckBox *mAltFpsCheckBox; + gcn::Slider *mAltFpsSlider; + gcn::Label *mAltFpsLabel; + + int mOverlayDetail; + gcn::Slider *mOverlayDetailSlider; + gcn::Label *mOverlayDetailField; + + int mParticleDetail; + gcn::Slider *mParticleDetailSlider; + gcn::Label *mParticleDetailField; + + int mFontSize; + gcn::DropDown *mFontSizeDropDown; + TextDialog *mDialog; +}; + +#endif diff --git a/src/gui/shopwindow.cpp b/src/gui/shopwindow.cpp new file mode 100644 index 000000000..410e79dbb --- /dev/null +++ b/src/gui/shopwindow.cpp @@ -0,0 +1,788 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/shopwindow.h" + +#include "gui/buy.h" +#include "gui/itemamount.h" +#include "gui/sell.h" +#include "gui/setup.h" +#include "gui/trade.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shopitems.h" +#include "gui/widgets/shoplistbox.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/tradetab.h" + +#include "actorspritemanager.h" +#include "configuration.h" +#include "confirmdialog.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "playerrelations.h" +#include "shopitem.h" +#include "sound.h" +#include "units.h" + +#include "net/net.h" +#include "net/chathandler.h" +#include "net/npchandler.h" +#include "net/tradehandler.h" + +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <sstream> + +#include <sys/stat.h> + +extern std::string tradePartnerName; +ShopWindow::DialogList ShopWindow::instances; + +ShopWindow::ShopWindow(): + Window(_("Personal Shop")), + mSelectedItem(-1), + mAnnonceTime(0), + mLastRequestTimeList(0), + mLastRequestTimeItem(0), + mRandCounter(0), + mAcceptPlayer(""), + mTradeItem(0), + mTradeNick(""), + mTradeMoney(0) +{ + setWindowName("Personal Shop"); + setResizable(true); + setCloseButton(true); + setMinWidth(260); + setMinHeight(230); + setDefaultSize(380, 300, ImageRect::CENTER); + + mBuyShopItems = new ShopItems; + mSellShopItems = new ShopItems; + + mAnnounceCounter[BUY] = 0; + mAnnounceCounter[SELL] = 0; + + loadList(); + + mBuyShopItemList = new ShopListBox(mBuyShopItems, mBuyShopItems); + mSellShopItemList = new ShopListBox(mSellShopItems, mSellShopItems); + + mBuyShopItemList->setPriceCheck(false); + mSellShopItemList->setPriceCheck(false); + + mBuyScrollArea = new ScrollArea(mBuyShopItemList); + mBuyScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mSellScrollArea = new ScrollArea(mSellShopItemList); + mSellScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mCloseButton = new Button(_("Close"), "close", this); + + mBuyShopItemList->addSelectionListener(this); + mSellShopItemList->addSelectionListener(this); + + mBuyLabel = new Label(_("Buy items")); + mSellLabel = new Label(_("Sell items")); + + mBuyAddButton = new Button(_("Add"), "add buy", this); + mBuyDeleteButton = new Button(_("Delete"), "delete buy", this); + mBuyAnnounceButton = new Button(_("Announce"), "announce buy", this); + mSellAddButton = new Button(_("Add"), "add sell", this); + mSellDeleteButton = new Button(_("Delete"), "delete sell", this); + mSellAnnounceButton = new Button(_("Announce"), "announce sell", this); + mAnnounceLinks = new CheckBox(_("Show links in announce"), false, + this, "link announce"); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mBuyLabel, 8).setPadding(3); + place(8, 0, mSellLabel, 8).setPadding(3); + place(0, 1, mBuyScrollArea, 8, 5).setPadding(3); + place(8, 1, mSellScrollArea, 8, 5).setPadding(3); + place(0, 6, mBuyAddButton); + place(1, 6, mBuyDeleteButton); + place(3, 6, mBuyAnnounceButton); + place(8, 6, mSellAddButton); + place(9, 6, mSellDeleteButton); + place(11, 6, mSellAnnounceButton); + place(0, 7, mAnnounceLinks, 8); + place(15, 7, mCloseButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(false); + + updateButtonsAndLabels(); +} + +ShopWindow::~ShopWindow() +{ + saveList(); + + delete mBuyShopItems; + mBuyShopItems = 0; + + delete mSellShopItems; + mSellShopItems = 0; + + instances.remove(this); +} + +void ShopWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "close") + { + close(); + return; + } + + if (event.getId() == "yes") + { + startTrade(); + } + else if (event.getId() == "no") + { + mTradeNick = ""; + } + else if (event.getId() == "ignore") + { + player_relations.ignoreTrade(mTradeNick); + mTradeNick = ""; + } + else if (event.getId() == "delete buy" && mBuyShopItemList + && mBuyShopItemList->getSelected() >= 0) + { + mBuyShopItems->erase(mBuyShopItemList->getSelected()); + } + else if (event.getId() == "delete sell" && mSellShopItemList + && mSellShopItemList->getSelected() >= 0) + { + mSellShopItems->erase(mSellShopItemList->getSelected()); + } + else if (event.getId() == "announce buy" && mBuyShopItems + && mBuyShopItems->getNumberOfElements() > 0) + { + announce(mBuyShopItems, BUY); + } + else if (event.getId() == "announce sell" && mSellShopItems + && mSellShopItems->getNumberOfElements() > 0) + { + announce(mSellShopItems, SELL); + } + + if (mSelectedItem < 1) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(mSelectedItem); + if (item) + { + if (event.getId() == "add buy") + { + ItemAmountWindow::showWindow(ItemAmountWindow::ShopBuyAdd, + this, item, sumAmount(item)); + } + else if (event.getId() == "add sell") + { + ItemAmountWindow::showWindow(ItemAmountWindow::ShopSellAdd, + this, item, sumAmount(item)); + } + } + +} + +void ShopWindow::startTrade() +{ + if (!actorSpriteManager || !tradeWindow) + return; + + Being *being = actorSpriteManager->findBeingByName( + mTradeNick, Being::PLAYER); + tradeWindow->clear(); + if (mTradeMoney) + { + tradeWindow->addAutoMoney(mTradeNick, mTradeMoney); + } + else + { + tradeWindow->addAutoItem(mTradeNick, mTradeItem, + mTradeItem->getQuantity()); + } + Net::getTradeHandler()->request(being); + tradePartnerName = mTradeNick; + mTradeNick = ""; +} + +void ShopWindow::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + updateButtonsAndLabels(); +} + +void ShopWindow::updateButtonsAndLabels() +{ + mBuyAddButton->setEnabled(mSelectedItem != -1); + mSellAddButton->setEnabled(mSelectedItem != -1); + mBuyDeleteButton->setEnabled( + mBuyShopItemList->getSelected() != -1 + && mBuyShopItems->getNumberOfElements() > 0); + mSellDeleteButton->setEnabled( + mSellShopItemList->getSelected() != -1 + && mSellShopItems->getNumberOfElements() > 0); +} + +void ShopWindow::setVisible(bool visible) +{ + Window::setVisible(visible); +} + +void ShopWindow::addBuyItem(Item *item, int amount, int price) +{ + if (!mBuyShopItems || !item) + return; + mBuyShopItems->addItemNoDup(item->getId(), amount, price); + updateButtonsAndLabels(); +} + +void ShopWindow::addSellItem(Item *item, int amount, int price) +{ + if (!mBuyShopItems || !item) + return; + mSellShopItems->addItemNoDup(item->getId(), amount, price); + updateButtonsAndLabels(); +} + +void ShopWindow::loadList() +{ + if (!mBuyShopItems || !mSellShopItems) + return; + + std::ifstream shopFile; + struct stat statbuf; + + mBuyShopItems->clear(); + mSellShopItems->clear(); + + std::string shopListName = Client::getServerConfigDirectory() + + "/shoplist.txt"; + + if (!stat(shopListName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) + { + shopFile.open(shopListName.c_str(), std::ios::in); + char line[101]; + while (shopFile.getline(line, 100)) + { + std::string buf; + std::string str = line; + if (!str.empty()) + { + std::vector<int> tokens; + + std::stringstream ss(str); + + while (ss >> buf) + tokens.push_back(atoi(buf.c_str())); + + if (tokens.size() == 5 && tokens[0]) + { + if (tokens[1] && tokens[2] && mBuyShopItems) + { + mBuyShopItems->addItem( + tokens[0], tokens[1], tokens[2]); + } + if (tokens[3] && tokens[4] && mSellShopItems) + { + mSellShopItems->addItem( + tokens[0], tokens[3], tokens[4]); + } + } + } + } + shopFile.close(); + } +} + +void ShopWindow::saveList() +{ + if (!mBuyShopItems || !mSellShopItems) + return; + + std::ofstream shopFile; + std::string shopListName = Client::getServerConfigDirectory() + + "/shoplist.txt"; + std::list<int> procesList; + std::map<int, ShopItem*> mapItems; + + shopFile.open(shopListName.c_str(), std::ios::binary); + if (!shopFile.is_open()) + { + logger->log1("Unable to open shoplist.txt for writing"); + return; + } + + std::vector<ShopItem*> items = mBuyShopItems->items(); + std::vector<ShopItem*>::iterator it; + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (item) + mapItems[item->getId()] = item; + } + + items = mSellShopItems->items(); + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *sellItem = *(it); + ShopItem *buyItem = mapItems[sellItem->getId()]; + + shopFile << sellItem->getId(); + if (buyItem) + { + shopFile << strprintf(" %d %d ", buyItem->getQuantity(), + buyItem->getPrice()); + mapItems.erase(sellItem->getId()); + } + else + { + shopFile << " 0 0 "; + } + + if (sellItem) + { + shopFile << strprintf("%d %d", sellItem->getQuantity(), + sellItem->getPrice()) << std::endl; + } + } + + std::map<int, ShopItem*>::iterator mapIt; + for (mapIt = mapItems.begin(); mapIt != mapItems.end(); mapIt++) + { + ShopItem *buyItem = (*mapIt).second; + if (buyItem) + { + shopFile << buyItem->getId(); + shopFile << strprintf(" %d %d ", buyItem->getQuantity(), + buyItem->getPrice()); + shopFile << "0 0" << std::endl; + } + } + + shopFile.close(); +} + +void ShopWindow::announce(ShopItems *list, int mode) +{ + if (!list) + return; + + std::string data = "\302\202"; + if (mode == BUY) + data += "Buy "; + else + data += "Sell "; + + if (mAnnonceTime && (mAnnonceTime + (2 * 60) > cur_time + || mAnnonceTime > cur_time)) + { + return; + } + + mAnnonceTime = cur_time; + if (mBuyAnnounceButton) + mBuyAnnounceButton->setEnabled(false); + if (mSellAnnounceButton) + mSellAnnounceButton->setEnabled(false); + + std::vector<ShopItem*> items = list->items(); + std::vector<ShopItem*>::iterator it; + + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (item->getQuantity() > 1) + { + if (mAnnounceLinks->isSelected()) + { + data += strprintf("[@@%d|%s@@] (%dGP) %d, ", item->getId(), + item->getInfo().getName().c_str(), + item->getPrice(), item->getQuantity()); + } + else + { + data += strprintf("%s (%dGP) %d, ", + item->getInfo().getName().c_str(), + item->getPrice(), item->getQuantity()); + } + } + else + { + if (mAnnounceLinks->isSelected()) + { + data += strprintf("[@@%d|%s@@] (%dGP), ", item->getId(), + item->getInfo().getName().c_str(), + item->getPrice()); + } + else + { + data += strprintf("%s (%dGP), ", + item->getInfo().getName().c_str(), + item->getPrice()); + } + } + } + + Net::getChatHandler()->talk(data); +} + +void ShopWindow::giveList(const std::string &nick, int mode) +{ + if (!checkFloodCounter(mLastRequestTimeList)) + return; + + std::string data = "\302\202"; + + ShopItems *list; + if (mode == BUY) + { + list = mBuyShopItems; + data += "S1"; + } + else + { + list = mSellShopItems; + data += "B1"; + } + if (!list) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + std::vector<ShopItem*> items = list->items(); + std::vector<ShopItem*>::iterator it; + + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (!item) + continue; + + if (mode == SELL) + { + Item *item2 = inv->findItem(item->getId()); + if (item2) + { + int amount = item->getQuantity(); + if (item2->getQuantity() < amount) + amount = item2->getQuantity(); + + if (amount) + { + data += strprintf("%s%s%s", + encodeStr(item->getId(), 2).c_str(), + encodeStr(item->getPrice(), 4).c_str(), + encodeStr(amount, 3).c_str()); + } + } + } + else + { + int amount = item->getQuantity(); + if (item->getPrice() * amount > PlayerInfo::getAttribute(MONEY)) + amount = PlayerInfo::getAttribute(MONEY) / item->getPrice(); + + if (amount > 0) + { + data += strprintf("%s%s%s", + encodeStr(item->getId(), 2).c_str(), + encodeStr(item->getPrice(), 4).c_str(), + encodeStr(amount, 3).c_str()); + } + } + } + sendMessage(nick, data, true); +} + +void ShopWindow::sendMessage(const std::string &nick, + std::string data, bool random) +{ + if (!chatWindow) + return; + + if (random) + { + mRandCounter ++; + if (mRandCounter > 200) + mRandCounter = 0; + data += encodeStr(mRandCounter, 2); + } + + if (config.getBoolValue("hideShopMessages")) + Net::getChatHandler()->privateMessage(nick, data); + else if (chatWindow) + chatWindow->whisper(nick, data, BY_PLAYER); +//here was true +} + +void ShopWindow::showList(const std::string &nick, std::string data) +{ + BuyDialog *buyDialog = 0; + SellDialog *sellDialog = 0; + if (data.find("B1") == 0) + { + data = data.substr(2); + buyDialog = new BuyDialog(nick); + } + else if (data.find("S1") == 0) + { + data = data.substr(2); + sellDialog = new SellDialog(nick); + } + else + { + return; + } + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + if (buyDialog) + buyDialog->setMoney(PlayerInfo::getAttribute(MONEY)); + if (sellDialog) + sellDialog->setMoney(PlayerInfo::getAttribute(MONEY)); + + for (unsigned f = 0; f < data.length(); f += 9) + { + if (f + 9 > data.length()) + break; + + int id = decodeStr(data.substr(f, 2)); + int price = decodeStr(data.substr(f + 2, 4)); + int amount = decodeStr(data.substr(f + 6, 3)); + if (buyDialog && amount > 0) + buyDialog->addItem(id, amount, price); + if (sellDialog) + { + Item *item = inv->findItem(id); + if (item) + { + if (item->getQuantity() < amount) + amount = item->getQuantity(); + if (amount > 0) + sellDialog->addItem(id, amount, price); + else + sellDialog->addItem(id, -1, price); + } + } + } +} + +void ShopWindow::processRequest(std::string nick, std::string data, int mode) +{ + if (!player_node || !mTradeNick.empty() || PlayerInfo::isTrading() + || !actorSpriteManager + || !actorSpriteManager->findBeingByName(nick, Being::PLAYER)) + { + return; + } + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + unsigned long idx = 0; + + idx = data.find(" "); + if (idx == std::string::npos) + return; + + if (!checkFloodCounter(mLastRequestTimeItem)) + return; + + if (!mTradeNick.empty()) + { + sendMessage(nick, "error: player busy ", true); + return; + } + + data = data.substr(idx + 1); + + std::string part1; + std::string part2; + std::string part3; + std::stringstream ss(data); + std::string msg; + int id; + int price; + int amount; + + if (!(ss >> part1)) + return; + + if (!(ss >> part2)) + return; + + if (!(ss >> part3)) + return; + + id = atoi(part1.c_str()); + price = atoi(part2.c_str()); + amount = atoi(part3.c_str()); + + delete mTradeItem; + mTradeItem = new ShopItem(-1, id, amount, price); + + if (mode == BUY) + { + Item *item2 = inv->findItem(mTradeItem->getId()); + if (!item2 || item2->getQuantity() < amount + || !findShopItem(mTradeItem, SELL)) + { + sendMessage(nick, "error: Cant sell this item ", true); + return; + } + msg = "buy"; + mTradeMoney = 0; + } + else + { + if (!findShopItem(mTradeItem, BUY)) + { + sendMessage(nick, "error: Cant buy this item ", true); + return; + } + msg = "sell"; + mTradeMoney = mTradeItem->getPrice() * mTradeItem->getQuantity(); + } + + mTradeNick = nick; + + if (config.getBoolValue("autoShop")) + { + sound.playGuiSfx("system/newmessage.ogg"); + startTrade(); + } + else + { + ConfirmDialog *confirmDlg = new ConfirmDialog(_("Request for Trade"), + strprintf(_("%s wants to %s %s do you " + "accept?"), nick.c_str(), msg.c_str(), + mTradeItem->getInfo().getName().c_str()), true); + confirmDlg->addActionListener(this); + } +} + +void ShopWindow::updateTimes() +{ + if (mAnnonceTime + (2 * 60) < cur_time + || mAnnonceTime > cur_time) + { + mBuyAnnounceButton->setEnabled(true); + mSellAnnounceButton->setEnabled(true); + } +} + +bool ShopWindow::checkFloodCounter(int &counterTime) +{ + if (!counterTime || counterTime > cur_time) + counterTime = cur_time; + else if (counterTime + 10 > cur_time) + return false; + + counterTime = cur_time; + return true; +} + +bool ShopWindow::findShopItem(ShopItem *shopItem, int mode) +{ + if (!shopItem) + return false; + + std::vector<ShopItem*> items; + std::vector<ShopItem*>::iterator it; + if (mode == SELL) + { + if (!mSellShopItems) + return false; + items = mSellShopItems->items(); + } + else + { + if (!mBuyShopItems) + return false; + items = mBuyShopItems->items(); + } + + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (!item) + continue; + + if (item && item->getId() == shopItem->getId() + && item->getPrice() == shopItem->getPrice() + && item->getQuantity() >= shopItem->getQuantity()) + { + return true; + } + } + return false; +} + +int ShopWindow::sumAmount(Item *shopItem) +{ + if (!player_node || !shopItem) + return 0; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return 0; + int sum = 0; + + for (unsigned f = 0; f < inv->getSize(); f ++) + { + Item *item = inv->getItem(f); + if (item && item->getId() == shopItem->getId()) + sum += item->getQuantity(); + } + return sum; +} diff --git a/src/gui/shopwindow.h b/src/gui/shopwindow.h new file mode 100644 index 000000000..6b31b67a5 --- /dev/null +++ b/src/gui/shopwindow.h @@ -0,0 +1,173 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SHOP_H +#define SHOP_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/selectionlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class CheckBox; +class Item; +class ListBox; +class ShopItem; +class ShopItems; +class ShopListBox; + +/** + * The buy dialog. + * + * \ingroup Interface + */ +class ShopWindow : public Window, public gcn::ActionListener, + public gcn::SelectionListener +{ + public: + + enum ShopMode + { + BUY = 0, + SELL = 1 + }; + + /** + * Constructor. + * + * @see Window::Window + */ + ShopWindow(); + + /** + * Destructor + */ + ~ShopWindow(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Updates the labels according to the selected item. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Updates the state of buttons and labels. + */ + void updateButtonsAndLabels(); + + /** + * Sets the visibility of this window. + */ + void setVisible(bool visible); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return instances.size() > 0; } + + void setItemSelected(int id) + { mSelectedItem = id; updateButtonsAndLabels(); } + + void addBuyItem(Item *item, int amount, int price); + + void addSellItem(Item *item, int amount, int price); + + void loadList(); + + void saveList(); + + void announce(ShopItems *list, int mode); + + void giveList(const std::string &nick, int mode); + + void setAcceptPlayer(std::string name) + { mAcceptPlayer = name; } + + const std::string &getAcceptPlayer() + { return mAcceptPlayer; } + + void sendMessage(const std::string &nick, std::string data, + bool random = false); + + void showList(const std::string &nick, std::string data); + + void processRequest(std::string nick, std::string data, int mode); + + bool findShopItem(ShopItem *shopItem, int mode); + + int sumAmount(Item *shopItem); + + void updateTimes(); + + bool checkFloodCounter(int &counterTime); + + private: + void startTrade(); + + typedef std::list<ShopWindow*> DialogList; + static DialogList instances; + + gcn::Button *mCloseButton; + ShopListBox *mBuyShopItemList; + ShopListBox *mSellShopItemList; + gcn::ScrollArea *mBuyScrollArea; + gcn::ScrollArea *mSellScrollArea; + gcn::Label *mBuyLabel; + gcn::Label *mSellLabel; + gcn::Button *mBuyAddButton; + gcn::Button *mBuyDeleteButton; + gcn::Button *mBuyAnnounceButton; + gcn::Button *mSellAddButton; + gcn::Button *mSellDeleteButton; + gcn::Button *mSellAnnounceButton; + gcn::CheckBox *mAnnounceLinks; + + ShopItems *mBuyShopItems; + ShopItems *mSellShopItems; + + int mSelectedItem; + int mAnnonceTime; + int mLastRequestTimeList; + int mLastRequestTimeItem; + int mRandCounter; + std::string mAcceptPlayer; + ShopItem *mTradeItem; + std::string mTradeNick; + int mTradeMoney; + int mAnnounceCounter[2]; +}; + +extern ShopWindow *shopWindow; + +#endif diff --git a/src/gui/shortcutwindow.cpp b/src/gui/shortcutwindow.cpp new file mode 100644 index 000000000..c3aa8454f --- /dev/null +++ b/src/gui/shortcutwindow.cpp @@ -0,0 +1,152 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/shortcutwindow.h" + +#include "configuration.h" + +#include "gui/setup.h" + +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shortcutcontainer.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" + +static const int SCROLL_PADDING = 0; + +int ShortcutWindow::mBoxesWidth = 0; + +class ShortcutTab : public Tab +{ + public: + ShortcutTab(std::string name, ShortcutContainer* content) + { + setCaption(name); + mContent = content; + } + + ShortcutContainer* mContent; +}; + +ShortcutWindow::ShortcutWindow(const std::string &title, + ShortcutContainer *content, + int width, int height) +{ + setWindowName(title); + // no title presented, title bar is padding so window can be moved. + gcn::Window::setTitleBarHeight(gcn::Window::getPadding()); + setShowTitle(false); + setResizable(true); + setDefaultVisible(false); + setSaveVisible(true); + + setupWindow->registerWindowForReset(this); + + mTabs = 0; + mItems = content; + + const int border = SCROLL_PADDING * 2 + getPadding() * 2; + setMinWidth(mItems->getBoxWidth() + border); + setMinHeight(mItems->getBoxHeight() + border); + setMaxWidth(mItems->getBoxWidth() * mItems->getMaxItems() + border); + setMaxHeight(mItems->getBoxHeight() * mItems->getMaxItems() + border); + + if (width == 0) + width = mItems->getBoxWidth() + border; + if (height == 0) + height = (mItems->getBoxHeight() * mItems->getMaxItems()) + border; + + setDefaultSize(width, height, ImageRect::LOWER_RIGHT); + + mBoxesWidth += mItems->getBoxWidth() + border; + + mScrollArea = new ScrollArea(mItems); + mScrollArea->setPosition(SCROLL_PADDING, SCROLL_PADDING); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mScrollArea->setOpaque(false); + + place(0, 0, mScrollArea, 5, 5).setPadding(0); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + layout.setMargin(0); + + loadWindowState(); +} + +ShortcutWindow::ShortcutWindow(const std::string &title, int width, int height) +{ + setWindowName(title); + // no title presented, title bar is padding so window can be moved. + gcn::Window::setTitleBarHeight(gcn::Window::getPadding()); + setShowTitle(false); + setResizable(true); + setDefaultVisible(false); + setSaveVisible(true); + + setupWindow->registerWindowForReset(this); + + mTabs = new TabbedArea; + + mItems = 0; + + const int border = SCROLL_PADDING * 2 + getPadding() * 2; + + if (width && height) + setDefaultSize(width, height, ImageRect::LOWER_RIGHT); + + setMinWidth(32 + border); + setMinHeight(32 + border); + + place(0, 0, mTabs, 5, 5); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + layout.setMargin(0); + + loadWindowState(); +} + +ShortcutWindow::~ShortcutWindow() +{ + delete mTabs; + mTabs = 0; + delete mItems; + mItems = 0; +} + +void ShortcutWindow::addTab(std::string name, ShortcutContainer *content) +{ + ScrollArea *scroll = new ScrollArea(content); + scroll->setPosition(SCROLL_PADDING, SCROLL_PADDING); + scroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + scroll->setOpaque(false); + Tab *tab = new ShortcutTab(name, content); + mTabs->addTab(tab, scroll); +} + +int ShortcutWindow::getTabIndex() +{ + if (!mTabs) + return 0; + return mTabs->getSelectedTabIndex(); +} diff --git a/src/gui/shortcutwindow.h b/src/gui/shortcutwindow.h new file mode 100644 index 000000000..bd3dedb61 --- /dev/null +++ b/src/gui/shortcutwindow.h @@ -0,0 +1,71 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SHORTCUTWINDOW_H +#define SHORTCUTWINDOW_H + +#include "gui/widgets/window.h" + +class ScrollArea; +class ShortcutContainer; +class TabbedArea; + +/** + * A window around a ShortcutContainer. + * + * \ingroup Interface + */ +class ShortcutWindow : public Window +{ + public: + /** + * Constructor. + */ + ShortcutWindow(const std::string &title, ShortcutContainer *content, + int width = 0, int height = 0); + + ShortcutWindow(const std::string &title, + int width = 0, int height = 0); + + /** + * Destructor. + */ + ~ShortcutWindow(); + + void addTab(std::string name, ShortcutContainer *content); + + int getTabIndex(); + + private: + ShortcutWindow(); + ShortcutContainer *mItems; + + ScrollArea *mScrollArea; + TabbedArea *mTabs; + + static int mBoxesWidth; +}; + +extern ShortcutWindow *itemShortcutWindow; +extern ShortcutWindow *emoteShortcutWindow; +extern ShortcutWindow *dropShortcutWindow; + +#endif diff --git a/src/gui/skilldialog.cpp b/src/gui/skilldialog.cpp new file mode 100644 index 000000000..04222d6f5 --- /dev/null +++ b/src/gui/skilldialog.cpp @@ -0,0 +1,523 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/skilldialog.h" + +#include "log.h" +#include "playerinfo.h" +#include "configuration.h" + +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/container.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/playerhandler.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include <guichan/font.hpp> + +#include <set> +#include <string> + +class SkillModel; +class SkillEntry; + + +struct SkillInfo +{ + unsigned short id; + std::string name; + Image *icon; + bool modifiable; + bool visible; + SkillModel *model; + + std::string skillLevel; + int skillLevelWidth; + + std::string skillExp; + float progress; + gcn::Color color; + + SkillInfo() : + id(0), name(""), icon(0), modifiable(false), visible(false), + model(0), skillLevel(""), skillLevelWidth(0), skillExp(""), + progress(0.0f) + { + } + + ~SkillInfo() + { + if (icon) + icon->decRef(); + } + + void setIcon(const std::string &iconPath) + { + ResourceManager *res = ResourceManager::getInstance(); + if (!iconPath.empty()) + { + icon = res->getImage(iconPath); + } + + if (!icon) + { + icon = Theme::getImageFromTheme( + paths.getStringValue("unknownItemFile")); + } + } + + void update(); + + void draw(Graphics *graphics, int y, int width); +}; + + +typedef std::vector<SkillInfo*> SkillList; + +class SkillModel : public gcn::ListModel +{ +public: + int getNumberOfElements() + { return static_cast<int>(mVisibleSkills.size()); } + + SkillInfo *getSkillAt(int i) const + { return mVisibleSkills.at(i); } + + std::string getElementAt(int i) + { + if (getSkillAt(i)) + return getSkillAt(i)->name; + else + return ""; + } + + void updateVisibilities(); + + void addSkill(SkillInfo *info) + { mSkills.push_back(info); } + +private: + SkillList mSkills; + SkillList mVisibleSkills; +}; + +class SkillListBox : public ListBox +{ +public: + SkillListBox(SkillModel *model): + ListBox(model) + {} + + SkillInfo *getSelectedInfo() + { + const int selected = getSelected(); + if (!mListModel || selected < 0 + || selected > mListModel->getNumberOfElements()) + { + return 0; + } + + return static_cast<SkillModel*>(mListModel)->getSkillAt(selected); + } + + void draw(gcn::Graphics *gcnGraphics) + { + if (!mListModel) + return; + + SkillModel* model = static_cast<SkillModel*>(mListModel); + + updateAlpha(); + + Graphics *graphics = static_cast<Graphics*>(gcnGraphics); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast<int>(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + // Draw filled rectangle around the selected list element + if (mSelected >= 0) + { + graphics->fillRectangle(gcn::Rectangle(0, getRowHeight() + * mSelected, getWidth(), getRowHeight())); + } + + // Draw the list elements + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + for (int i = 0, y = 1; + i < model->getNumberOfElements(); + ++i, y += getRowHeight()) + { + SkillInfo *e = model->getSkillAt(i); + + if (e) + e->draw(graphics, y, getWidth()); + } + } + + unsigned int getRowHeight() const + { return 34; } +}; + +class SkillTab : public Tab +{ +public: + SkillTab(const std::string &name, SkillListBox *listBox): + mListBox(listBox) + { + setCaption(name); + } + + ~SkillTab() + { + delete mListBox; + mListBox = 0; + } + + SkillInfo *getSelectedInfo() + { + if (mListBox) + return mListBox->getSelectedInfo(); + else + return 0; + } + +private: + SkillListBox *mListBox; +}; + +SkillDialog::SkillDialog(): + Window(_("Skills")) +{ + setWindowName("Skills"); + setCloseButton(true); + setResizable(true); + setSaveVisible(true); + setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425); + setupWindow->registerWindowForReset(this); + + mTabs = new TabbedArea(); + mPointsLabel = new Label("0"); + mIncreaseButton = new Button(_("Up"), "inc", this); + + place(0, 0, mTabs, 5, 5); + place(0, 5, mPointsLabel, 4); + place(4, 5, mIncreaseButton); + + setLocationRelativeTo(getParent()); + loadWindowState(); +} + +SkillDialog::~SkillDialog() +{ + // Clear gui + loadSkills(""); +} + +void SkillDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "inc") + { + SkillTab *tab = static_cast<SkillTab*>(mTabs->getSelectedTab()); + if (tab) + { + if (SkillInfo *info = tab->getSelectedInfo()) + Net::getPlayerHandler()->increaseSkill(info->id); + } + } + else if (event.getId() == "close") + { + setVisible(false); + } +} + +std::string SkillDialog::update(int id) +{ + SkillMap::iterator i = mSkills.find(id); + + if (i != mSkills.end()) + { + SkillInfo *info = i->second; + if (info) + { + info->update(); + return info->name; + } + } + + return std::string(); +} + +void SkillDialog::update() +{ + mPointsLabel->setCaption(strprintf(_("Skill points available: %d"), + PlayerInfo::getAttribute(SKILL_POINTS))); + mPointsLabel->adjustSize(); + + for (SkillMap::iterator it = mSkills.begin(); it != mSkills.end(); it++) + { + if ((*it).second && (*it).second->modifiable) + (*it).second->update(); + } +} + +void SkillDialog::loadSkills(const std::string &file) +{ + // Fixes issues with removing tabs + if (mTabs->getSelectedTabIndex() != -1) + { + mTabs->setSelectedTab(static_cast<unsigned int>(0)); + + while (mTabs->getSelectedTabIndex() != -1) + { + gcn::Tab *tab = mTabs->getSelectedTab(); + if (tab) + mTabs->removeTabWithIndex(mTabs->getSelectedTabIndex()); + delete tab; + } + } + + delete_all(mSkills); + mSkills.clear(); + + if (file.length() == 0) + return; + + XML::Document doc(file); + xmlNodePtr root = doc.rootNode(); + + int setCount = 0; + std::string setName; + ScrollArea *scroll; + SkillListBox *listbox; + SkillTab *tab; + + if (!root || !xmlStrEqual(root->name, BAD_CAST "skills")) + { + logger->log("Error loading skills file: %s", file.c_str()); + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + SkillModel *model = new SkillModel(); + SkillInfo *skill = new SkillInfo; + skill->id = 1; + skill->name = "basic"; + skill->setIcon(""); + skill->modifiable = true; + skill->visible = true; + skill->model = model; + skill->update(); + + model->addSkill(skill); + mSkills[1] = skill; + + model->updateVisibilities(); + + listbox = new SkillListBox(model); + scroll = new ScrollArea(listbox); + scroll->setOpaque(false); + scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS); + + tab = new SkillTab("Skills", listbox); + + mTabs->addTab(tab, scroll); + + update(); + } + return; + } + + for_each_xml_child_node(set, root) + { + if (xmlStrEqual(set->name, BAD_CAST "set")) + { + setCount++; + setName = XML::getProperty(set, "name", + strprintf(_("Skill Set %d"), setCount)); + + SkillModel *model = new SkillModel(); + + for_each_xml_child_node(node, set) + { + if (xmlStrEqual(node->name, BAD_CAST "skill")) + { + int id = atoi(XML::getProperty(node, "id", "-1").c_str()); + std::string name = XML::getProperty(node, "name", + strprintf(_("Skill %d"), id)); + std::string icon = XML::getProperty(node, "icon", ""); + + SkillInfo *skill = new SkillInfo; + skill->id = static_cast<short unsigned>(id); + skill->name = name; + skill->setIcon(icon); + skill->modifiable = false; + skill->visible = false; + skill->model = model; + skill->update(); + + model->addSkill(skill); + + mSkills[id] = skill; + } + } + + model->updateVisibilities(); + + // possible leak listbox, scroll + listbox = new SkillListBox(model); + scroll = new ScrollArea(listbox); + scroll->setOpaque(false); + scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS); + + tab = new SkillTab(setName, listbox); + + mTabs->addTab(tab, scroll); + } + } + update(); +} + +void SkillDialog::setModifiable(int id, bool modifiable) +{ + SkillMap::iterator it = mSkills.find(id); + + if (it != mSkills.end()) + { + SkillInfo *info = it->second; + if (info) + { + info->modifiable = modifiable; + info->update(); + } + } +} + +void SkillModel::updateVisibilities() +{ + mVisibleSkills.clear(); + + for (SkillList::iterator it = mSkills.begin(); it != mSkills.end(); it++) + { + if ((*it)->visible) + mVisibleSkills.push_back((*it)); + } +} + +void SkillInfo::update() +{ + int baseLevel = PlayerInfo::getStatBase(id); + int effLevel = PlayerInfo::getStatEffective(id); + + std::pair<int, int> exp = PlayerInfo::getStatExperience(id); + + if (!modifiable && baseLevel == 0 && effLevel == 0 && exp.second == 0) + { + if (visible) + { + visible = false; + if (model) + model->updateVisibilities(); + } + + return; + } + + bool updateVisibility = !visible; + visible = true; + + if (effLevel != baseLevel) + { + skillLevel = strprintf(_("Lvl: %d (%+d)"), baseLevel, + effLevel - baseLevel); + } + else + { + if (baseLevel == 0) + skillLevel.clear(); + else + skillLevel = strprintf(_("Lvl: %d"), baseLevel); + } + skillLevelWidth = -1; + + if (exp.second) + { + skillExp = strprintf("%d / %d", exp.first, exp.second); + progress = static_cast<float>(exp.first) + / static_cast<float>(exp.second); + } + else + { + skillExp.clear(); + progress = 0.0f; + } + + color = Theme::getProgressColor(Theme::PROG_EXP, progress); + + if (updateVisibility && model) + model->updateVisibilities(); +} + +void SkillInfo::draw(Graphics *graphics, int y, int width) +{ + graphics->drawImage(icon, 1, y); + graphics->drawText(name, 34, y); + + if (skillLevelWidth < 0) + { + // Add one for padding + skillLevelWidth = graphics->getFont()->getWidth(skillLevel) + 1; + } + + graphics->drawText(skillLevel, width - skillLevelWidth, y); + + if (!skillExp.empty()) + { + gcn::Rectangle rect(33, y + 15, width - 33, 17); + + ProgressBar::render(graphics, rect, color, progress, skillExp); + } +} + +SkillInfo* SkillDialog::getSkill(int id) +{ + return mSkills[id]; +} diff --git a/src/gui/skilldialog.h b/src/gui/skilldialog.h new file mode 100644 index 000000000..4ba4afe8e --- /dev/null +++ b/src/gui/skilldialog.h @@ -0,0 +1,91 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SKILLDIALOG_H +#define SKILLDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +//include "resources/image.h" +//include "resources/resourcemanager.h" + +#include <guichan/actionlistener.hpp> + +#include <map> + +class Button; +//class Image; +class Label; +class ScrollArea; +class Tab; +class TabbedArea; + +struct SkillInfo; + +/** + * The skill dialog. + * + * \ingroup Interface + */ +class SkillDialog : public Window, public gcn::ActionListener +{ + public: + SkillDialog(); + + ~SkillDialog(); + + /** + * Called when receiving actions from widget. + */ + void action(const gcn::ActionEvent &event); + + /** + * Update the given skill's display + */ + std::string update(int id); + + /** + * Update other parts of the display + */ + void update(); + + void loadSkills(const std::string &file); + + void setModifiable(int id, bool modifiable); + + SkillInfo* getSkill(int id); + + bool hasSkills() + { return !mSkills.empty(); } + + private: + typedef std::map<int, SkillInfo*> SkillMap; + SkillMap mSkills; + TabbedArea *mTabs; + Label *mPointsLabel; + Button *mIncreaseButton; +}; + +extern SkillDialog *skillDialog; + +#endif diff --git a/src/gui/socialwindow.cpp b/src/gui/socialwindow.cpp new file mode 100644 index 000000000..7e86e4d9c --- /dev/null +++ b/src/gui/socialwindow.cpp @@ -0,0 +1,1306 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/socialwindow.h" + +#include "actorspritemanager.h" +#include "guild.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" +#include "party.h" + +#include "gui/confirmdialog.h" +#include "gui/okdialog.h" +#include "gui/outfitwindow.h" +#include "gui/setup.h" +#include "gui/textdialog.h" +#include "gui/theme.h" + +#include "gui/widgets/avatarlistbox.h" +#include "gui/widgets/browserbox.h" +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/container.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/popup.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" + +#include "net/net.h" +#include "net/guildhandler.h" +#include "net/partyhandler.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +class SocialTab : public Tab +{ +protected: + friend class SocialWindow; + + SocialTab(): + mInviteDialog(0), + mConfirmDialog(0), + mScroll(0), + mList(0) + {} + + virtual ~SocialTab() + { + // Cleanup dialogs + if (mInviteDialog) + { + mInviteDialog->close(); + mInviteDialog->scheduleDelete(); + mInviteDialog = NULL; + } + + if (mConfirmDialog) + { + mConfirmDialog->close(); + mConfirmDialog->scheduleDelete(); + mConfirmDialog = NULL; + } + } + + virtual void invite() = 0; + + virtual void leave() = 0; + + virtual void updateList() = 0; + + virtual void updateAvatar(std::string name) = 0; + + virtual void resetDamage(std::string name) = 0; + + virtual void selectIndex(unsigned num _UNUSED_) + { } + + TextDialog *mInviteDialog; + ConfirmDialog *mConfirmDialog; + ScrollArea *mScroll; + AvatarListBox *mList; +}; + +class GuildTab : public SocialTab, public gcn::ActionListener +{ +public: + GuildTab(Guild *guild): + mGuild(guild) + { + setCaption(_("Guild")); + + setTabColor(&Theme::getThemeColor(Theme::GUILD_SOCIAL_TAB)); + + mList = new AvatarListBox(guild); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + } + + ~GuildTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + } + + void action(const gcn::ActionEvent &event) + { + if (event.getId() == "do invite") + { + std::string name = mInviteDialog->getText(); + Net::getGuildHandler()->invite(mGuild->getId(), name); + + if (localChatTab) + { + localChatTab->chatLog(strprintf( + _("Invited user %s to guild %s."), + name.c_str(), mGuild->getName().c_str()), BY_SERVER); + } + mInviteDialog = 0; + } + else if (event.getId() == "~do invite") + { + mInviteDialog = 0; + } + else if (event.getId() == "yes") + { + Net::getGuildHandler()->leave(mGuild->getId()); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Guild %s quit requested."), + mGuild->getName().c_str()), BY_SERVER); + } + mConfirmDialog = 0; + } + else if (event.getId() == "~yes") + { + mConfirmDialog = 0; + } + } + + void updateList() + { + } + + void updateAvatar(std::string name _UNUSED_) + { + } + + void resetDamage(std::string name _UNUSED_) + { + } + +protected: + void invite() + { + // TODO - Give feedback on whether the invite succeeded + mInviteDialog = new TextDialog(_("Member Invite to Guild"), + strprintf(_("Who would you like to invite to guild %s?"), + mGuild->getName().c_str()), + socialWindow); + mInviteDialog->setActionEventId("do invite"); + mInviteDialog->addActionListener(this); + } + + void leave() + { + mConfirmDialog = new ConfirmDialog(_("Leave Guild?"), + strprintf(_("Are you sure you want to leave guild %s?"), + mGuild->getName().c_str()), + socialWindow); + + mConfirmDialog->addActionListener(this); + } + +private: + Guild *mGuild; +}; + +class PartyTab : public SocialTab, public gcn::ActionListener +{ +public: + PartyTab(Party *party): + mParty(party) + { + setCaption(_("Party")); + + setTabColor(&Theme::getThemeColor(Theme::PARTY_SOCIAL_TAB)); + + mList = new AvatarListBox(party); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + } + + ~PartyTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + } + + void action(const gcn::ActionEvent &event) + { + if (event.getId() == "do invite") + { + std::string name = mInviteDialog->getText(); + Net::getPartyHandler()->invite(name); + + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Invited user %s to party."), + name.c_str()), BY_SERVER); + } + mInviteDialog = NULL; + } + else if (event.getId() == "~do invite") + { + mInviteDialog = NULL; + } + else if (event.getId() == "yes") + { + Net::getPartyHandler()->leave(); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Party %s quit requested."), + mParty->getName().c_str()), BY_SERVER); + } + mConfirmDialog = NULL; + } + else if (event.getId() == "~yes") + { + mConfirmDialog = NULL; + } + } + + void updateList() + { + } + + void updateAvatar(std::string name _UNUSED_) + { + } + + void resetDamage(std::string name _UNUSED_) + { + } + +protected: + void invite() + { + // TODO - Give feedback on whether the invite succeeded + mInviteDialog = new TextDialog(_("Member Invite to Party"), + strprintf(_("Who would you like to invite to party %s?"), + mParty->getName().c_str()), + socialWindow); + mInviteDialog->setActionEventId("do invite"); + mInviteDialog->addActionListener(this); + } + + void leave() + { + mConfirmDialog = new ConfirmDialog(_("Leave Party?"), + strprintf(_("Are you sure you want to leave party %s?"), + mParty->getName().c_str()), + socialWindow); + + mConfirmDialog->addActionListener(this); + } + +private: + Party *mParty; +}; + +/*class BuddyTab : public SocialTab +{ + // TODO? +};*/ + + +class BeingsListModal : public AvatarListModel +{ +public: + BeingsListModal() + { + } + + ~BeingsListModal() + { + delete_all(mMembers); + mMembers.clear(); + } + + std::vector<Avatar*> *getMembers() + { + return &mMembers; + } + + virtual Avatar *getAvatarAt(int index) + { + return mMembers[index]; + } + + int getNumberOfElements() + { + return static_cast<int>(mMembers.size()); + } + + std::vector<Avatar*> mMembers; +}; + +class PlayersTab : public SocialTab +{ +public: + PlayersTab(std::string name) + { + mBeings = new BeingsListModal(); + + mList = new AvatarListBox(mBeings); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + +// mBeings->getMembers().push_back(new Avatar("test")); + updateList(); + setCaption(name); + } + + ~PlayersTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + delete mBeings; + mBeings = 0; + } + + void updateList() + { + getPlayersAvatars(); + } + + void updateAvatar(std::string name) + { + if (!actorSpriteManager) + return; + + Avatar *avatar = findAvatarbyName(name); + if (!avatar) + return; + if (Party::getParty(1)) + { + PartyMember *pm = Party::getParty(1)->getMember(name); + if (pm && pm->getMaxHp() > 0) + { + avatar->setMaxHp(pm->getMaxHp()); + avatar->setHp(pm->getHp()); + } + } + Being* being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + if (being) + { + avatar->setDamageHp(being->getDamageTaken()); + avatar->setLevel(being->getLevel()); + avatar->setGender(being->getGender()); + avatar->setIp(being->getIp()); + } + } + + void resetDamage(std::string name) + { + if (!actorSpriteManager) + return; + + Avatar *avatar = findAvatarbyName(name); + if (!avatar) + return; + avatar->setDamageHp(0); + Being* being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + + if (being) + being->setDamageTaken(0); + } + + Avatar* findAvatarbyName(std::string name) + { + std::vector<Avatar*> *avatars = mBeings->getMembers(); + if (!avatars) + return 0; + + Avatar *ava = 0; + std::vector<Avatar*>::iterator i = avatars->begin(); + while (i != avatars->end()) + { + ava = (*i); + if (ava && ava->getName() == name) + return ava; + ++i; + } + ava = new Avatar(name); + ava->setOnline(true); + avatars->push_back(ava); + return ava; + } + + void getPlayersAvatars() + { + std::vector<Avatar*> *avatars = mBeings->getMembers(); + if (!avatars) + return; + + if (actorSpriteManager) + { +// std::list<Being*> beings = actorSpriteManager->getAll(); + std::vector<std::string> names; + actorSpriteManager->getPlayerNames(names, false); + + std::vector<Avatar*>::iterator ai = avatars->begin(); + while (ai != avatars->end()) + { + bool finded = false; + Avatar *ava = (*ai); + if (!ava) + break; + + std::vector<std::string>::iterator i = names.begin(); + while (i != names.end()) + { + if (ava->getName() == (*i) && (*i) != "") + { + finded = true; + break; + } + ++i; + } + + if (!finded) + avatars->erase(ai); + else + ++ai; + } + + std::vector<std::string>::iterator i = names.begin(); + + while (i != names.end()) + { + if ((*i) != "") + updateAvatar(*i); + ++i; + } + } + } + +protected: + void invite() + { + } + + void leave() + { + } + +private: + BeingsListModal *mBeings; +}; + + +class NavigationTab : public SocialTab +{ + +public: + NavigationTab() + { + mBeings = new BeingsListModal(); + + mList = new AvatarListBox(mBeings); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + + setCaption(_("Nav")); + + } + + ~NavigationTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + delete mBeings; + mBeings = 0; + } + + void invite() + { + } + + void leave() + { + } + + void updateList() + { + if (!socialWindow || !player_node) + return; + + Map* map = socialWindow->getMap(); + if (!map) + return; + + if (socialWindow->getProcessedPortals()) + return; + + std::vector<Avatar*> *avatars = mBeings->getMembers(); + std::list<MapItem*> portals = map->getPortals(); + + std::list<MapItem*>::iterator i = portals.begin(); + SpecialLayer *specialLayer = map->getSpecialLayer(); + + avatars->clear(); + + int idx = 0; + while (i != portals.end()) + { + MapItem *portal = *i; + if (!portal) + continue; + + int x = portal->getX(); + int y = portal->getY(); + + std::string name = strprintf("%s [%d %d]", + portal->getComment().c_str(), x, y); + + Avatar *ava = new Avatar(name); + if (player_node) + ava->setOnline(player_node->isReachable(x, y, 0)); + else + ava->setOnline(false); + ava->setLevel(-1); + ava->setType(portal->getType()); + ava->setX(x); + ava->setY(y); + avatars->push_back(ava); + + if (config.getBoolValue("drawHotKeys") && idx < 80 && outfitWindow) + { + Being *being = actorSpriteManager->findPortalByTile(x, y); + if (being) + { + being->setName(keyboard.getKeyShortString( + outfitWindow->keyName(idx))); + } + + if (specialLayer) + { + portal = specialLayer->getTile(ava->getX(), ava->getY()); + if (portal) + { + portal->setName(keyboard.getKeyShortString( + outfitWindow->keyName(idx))); + } + } + } + + i++; + idx ++; + } + if (socialWindow) + socialWindow->setProcessedPortals(true); + } + + + virtual void selectIndex(unsigned num) + { + if (!player_node) + return; + + std::vector<Avatar*> *avatars = mBeings->getMembers(); + if (!avatars || avatars->size() <= num) + return; + + Avatar *ava = avatars->at(num); + if (ava && player_node) + player_node->navigateTo(ava->getX(), ava->getY()); + } + + void updateNames() + { + if (!socialWindow) + return; + + std::vector<Avatar*> *avatars = mBeings->getMembers(); + if (!avatars) + return; + + Map *map = socialWindow->getMap(); + if (!map) + return; + + Avatar *ava = 0; + std::vector<Avatar*>::iterator i = avatars->begin(); + while (i != avatars->end()) + { + ava = (*i); + if (!ava) + break; + + MapItem *item = map->findPortalXY(ava->getX(), ava->getY()); + if (item) + { + std::string name = strprintf("%s [%d %d]", + item->getComment().c_str(), item->getX(), item->getY()); + ava->setName(name); + ava->setOriginalName(name); + } + + ++i; + } + } + + int getPortalIndex(int x, int y) + { + if (!socialWindow) + return -1; + + std::vector<Avatar*> *avatars = mBeings->getMembers(); + if (!avatars) + return -1; + + Map *map = socialWindow->getMap(); + if (!map) + return 01; + + Avatar *ava = 0; + std::vector<Avatar*>::iterator i = avatars->begin(); + unsigned num = 0; + while (i != avatars->end()) + { + ava = (*i); + + if (!ava) + break; + + if (ava->getX() == x && ava->getY() == y) + return num; + + ++i; + num ++; + } + return -1; + } + + void addPortal(int x, int y) + { + if (!socialWindow || !player_node) + return; + + Map* map = socialWindow->getMap(); + if (!map) + return; + + std::vector<Avatar*> *avatars = mBeings->getMembers(); + + if (!avatars) + return; + + MapItem *portal = map->findPortalXY(x, y); + if (!portal) + return; + + std::string name = strprintf("%s [%d %d]", + portal->getComment().c_str(), x, y); + + Avatar *ava = new Avatar(name); + if (player_node) + ava->setOnline(player_node->isReachable(x, y, 0)); + else + ava->setOnline(false); + ava->setLevel(-1); + ava->setType(portal->getType()); + ava->setX(x); + ava->setY(y); + avatars->push_back(ava); + } + + void removePortal(int x, int y) + { + if (!socialWindow || !player_node) + return; + + Map* map = socialWindow->getMap(); + if (!map) + return; + + std::vector<Avatar*> *avatars = mBeings->getMembers(); + + std::vector<Avatar*>::iterator i = avatars->begin(); + + if (!avatars) + return; + + while (i != avatars->end()) + { + Avatar *ava = (*i); + + if (!ava) + break; + + if (ava && ava->getX() == x && ava->getY() == y) + { + avatars->erase(i); + return; + } + + ++ i; + } + } + + void updateAvatar(std::string) + { + } + + void resetDamage(std::string) + { + } + +private: + BeingsListModal *mBeings; + +protected: +// friend class SocialWindow; +}; + + +class CreatePopup : public Popup, public LinkHandler +{ +public: + CreatePopup(): + Popup("SocialCreatePopup") + { + mBrowserBox = new BrowserBox; + mBrowserBox->setPosition(4, 4); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mBrowserBox->setOpaque(false); + mBrowserBox->setLinkHandler(this); + + if (Net::getGuildHandler()->isSupported()) + mBrowserBox->addRow(strprintf("@@guild|%s@@", _("Create Guild"))); + mBrowserBox->addRow(strprintf("@@party|%s@@", _("Create Party"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + add(mBrowserBox); + + setContentSize(mBrowserBox->getWidth() + 8, + mBrowserBox->getHeight() + 8); + } + + void handleLink(const std::string &link, gcn::MouseEvent *event _UNUSED_) + { + if (link == "guild" && socialWindow) + { + socialWindow->showGuildCreate(); + } + else if (link == "party" && socialWindow) + { + socialWindow->showPartyCreate(); + } + + setVisible(false); + } + + void show(gcn::Widget *parent) + { + if (!parent) + return; + + int x, y; + parent->getAbsolutePosition(x, y); + y += parent->getHeight(); + setPosition(x, y); + setVisible(true); + requestMoveToTop(); + } + +private: + BrowserBox* mBrowserBox; +}; + +SocialWindow::SocialWindow() : + Window(_("Social")), + mGuildInvited(0), + mGuildAcceptDialog(0), + mPartyAcceptDialog(0), + mMap(0), + mLastUpdateTime(0), + mNeedUpdate(false), + mProcessedPortals(false) +{ + setWindowName("Social"); + setVisible(false); + setSaveVisible(true); + setResizable(true); + setSaveVisible(true); + setCloseButton(true); + setMinWidth(120); + setMinHeight(55); + setDefaultSize(590, 200, 150, 120); + setupWindow->registerWindowForReset(this); + + mCreateButton = new Button(_("Create"), "create", this); + mInviteButton = new Button(_("Invite"), "invite", this); + mLeaveButton = new Button(_("Leave"), "leave", this); + mTabs = new TabbedArea; + + place(0, 0, mCreateButton); + place(1, 0, mInviteButton); + place(2, 0, mLeaveButton); + place(0, 1, mTabs, 4, 4); + + widgetResized(NULL); + + mCreatePopup = new CreatePopup(); + + loadWindowState(); + + mPlayers = new PlayersTab("P"); + mTabs->addTab(mPlayers, mPlayers->mScroll); + + mNavigation = new NavigationTab(); + mTabs->addTab(mNavigation, mNavigation->mScroll); + + if (player_node && player_node->getParty()) + addTab(player_node->getParty()); + + if (player_node && player_node->getGuild()) + addTab(player_node->getGuild()); + + updateButtons(); +} + +SocialWindow::~SocialWindow() +{ + // Cleanup invites + if (mGuildAcceptDialog) + { + mGuildAcceptDialog->close(); + mGuildAcceptDialog->scheduleDelete(); + mGuildAcceptDialog = NULL; + + mGuildInvited = 0; + } + + if (mPartyAcceptDialog) + { + mPartyAcceptDialog->close(); + mPartyAcceptDialog->scheduleDelete(); + mPartyAcceptDialog = NULL; + + mPartyInviter = ""; + } + delete mCreatePopup; + mCreatePopup = 0; + delete mPlayers; + mPlayers = 0; +} + +bool SocialWindow::addTab(Guild *guild) +{ + if (mGuilds.find(guild) != mGuilds.end()) + return false; + + GuildTab *tab = new GuildTab(guild); + mGuilds[guild] = tab; + + mTabs->addTab(tab, tab->mScroll); + + updateButtons(); + + return true; +} + +bool SocialWindow::removeTab(Guild *guild) +{ + GuildMap::iterator it = mGuilds.find(guild); + if (it == mGuilds.end()) + return false; + + mTabs->removeTab(it->second); + delete it->second; + mGuilds.erase(it); + + updateButtons(); + + return true; +} + +bool SocialWindow::addTab(Party *party) +{ + if (mParties.find(party) != mParties.end()) + return false; + + PartyTab *tab = new PartyTab(party); + mParties[party] = tab; + + mTabs->addTab(tab, tab->mScroll); + + updateButtons(); + + return true; +} + +bool SocialWindow::removeTab(Party *party) +{ + PartyMap::iterator it = mParties.find(party); + if (it == mParties.end()) + return false; + + mTabs->removeTab(it->second); + delete it->second; + mParties.erase(it); + + updateButtons(); + + return true; +} + +void SocialWindow::action(const gcn::ActionEvent &event) +{ + const std::string &eventId = event.getId(); + + if (event.getSource() == mPartyAcceptDialog) + { + // check if they accepted the invite + if (eventId == "yes") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Accepted party invite from %s."), + mPartyInviter.c_str())); + } + Net::getPartyHandler()->inviteResponse(mPartyInviter, true); + } + else if (eventId == "no") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Rejected party invite from %s."), + mPartyInviter.c_str())); + } + Net::getPartyHandler()->inviteResponse(mPartyInviter, false); + } + + mPartyInviter = ""; + mPartyAcceptDialog = NULL; + } + else if (event.getSource() == mGuildAcceptDialog) + { + // check if they accepted the invite + if (eventId == "yes") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Accepted guild invite from %s."), + mPartyInviter.c_str())); + } + Net::getGuildHandler()->inviteResponse(mGuildInvited, true); + } + else if (eventId == "no") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Rejected guild invite from %s."), + mPartyInviter.c_str())); + } + Net::getGuildHandler()->inviteResponse(mGuildInvited, false); + } + + mGuildInvited = 0; + mGuildAcceptDialog = NULL; + } + else if (event.getId() == "create") + { + if (Net::getGuildHandler()->isSupported()) + { + if (mCreatePopup) + mCreatePopup->show(mCreateButton); + } + else + { + showPartyCreate(); + } + } + else if (event.getId() == "invite" && mTabs->getSelectedTabIndex() > -1) + { + if (mTabs->getSelectedTab()) + static_cast<SocialTab*>(mTabs->getSelectedTab())->invite(); + } + else if (event.getId() == "leave" && mTabs->getSelectedTabIndex() > -1) + { + if (mTabs->getSelectedTab()) + static_cast<SocialTab*>(mTabs->getSelectedTab())->leave(); + } + else if (event.getId() == "create guild") + { + std::string name = mGuildCreateDialog->getText(); + + if (name.size() > 16) + { + // TODO : State too many characters in input. + return; + } + + Net::getGuildHandler()->create(name); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Creating guild called %s."), + name.c_str()), BY_SERVER); + } + + mGuildCreateDialog = NULL; + } + else if (event.getId() == "~create guild") + { + mGuildCreateDialog = NULL; + } + else if (event.getId() == "create party") + { + std::string name = mPartyCreateDialog->getText(); + + if (name.size() > 16) + { + // TODO : State too many characters in input. + return; + } + + Net::getPartyHandler()->create(name); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Creating party called %s."), + name.c_str()), BY_SERVER); + } + + mPartyCreateDialog = NULL; + } + else if (event.getId() == "~create party") + { + mPartyCreateDialog = NULL; + } +} + +void SocialWindow::showGuildCreate() +{ + mGuildCreateDialog = new TextDialog(_("Guild Name"), + _("Choose your guild's name."), this); + mGuildCreateDialog->setActionEventId("create guild"); + mGuildCreateDialog->addActionListener(this); +} + +void SocialWindow::showGuildInvite(const std::string &guildName, + const int guildId, + const std::string &inviterName) +{ + // check there isnt already an invite showing + if (mGuildInvited != 0) + { + if (localChatTab) + { + localChatTab->chatLog(_("Received guild request, but one already " + "exists."), BY_SERVER); + } + return; + } + + std::string msg = strprintf(_("%s has invited you to join the guild %s."), + inviterName.c_str(), guildName.c_str()); + if (localChatTab) + localChatTab->chatLog(msg, BY_SERVER); + + // show invite + mGuildAcceptDialog = new ConfirmDialog(_("Accept Guild Invite"), + msg, false, false, this); + mGuildAcceptDialog->addActionListener(this); + + mGuildInvited = guildId; +} + +void SocialWindow::showPartyInvite(const std::string &partyName, + const std::string &inviter) +{ + // check there isnt already an invite showing + if (mPartyInviter != "") + { + if (localChatTab) + { + localChatTab->chatLog(_("Received party request, but one already " + "exists."), BY_SERVER); + } + return; + } + + std::string msg; + if (inviter.empty()) + { + if (partyName.empty()) + { + msg = _("You have been invited you to join a party."); + } + else + { + msg = strprintf(_("You have been invited to join the %s party."), + partyName.c_str()); + } + } + else + { + if (partyName.empty()) + { + msg = strprintf(_("%s has invited you to join their party."), + inviter.c_str()); + } + else + { + msg = strprintf(_("%s has invited you to join the %s party."), + inviter.c_str(), partyName.c_str()); + } + } + + if (localChatTab) + localChatTab->chatLog(msg, BY_SERVER); + + // show invite + mPartyAcceptDialog = new ConfirmDialog(_("Accept Party Invite"), + msg, false, false, this); + mPartyAcceptDialog->addActionListener(this); + + mPartyInviter = inviter; +} + +void SocialWindow::showPartyCreate() +{ + if (!player_node) + return; + + if (player_node->getParty()) + { + new OkDialog(_("Create Party"), + _("Cannot create party. You are already in a party"), + this); + return; + } + + mPartyCreateDialog = new TextDialog(_("Party Name"), + _("Choose your party's name."), this); + mPartyCreateDialog->setActionEventId("create party"); + mPartyCreateDialog->addActionListener(this); +} + +void SocialWindow::updateActiveList() +{ + mNeedUpdate = true; +} + +void SocialWindow::logic() +{ + unsigned int nowTime = cur_time; + if (nowTime - mLastUpdateTime > 1 && mNeedUpdate) + { + mPlayers->updateList(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + else if (nowTime - mLastUpdateTime > 5) + { + mPlayers->updateList(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + + Window::logic(); +} + +void SocialWindow::updateAvatar(std::string name) +{ + mPlayers->updateAvatar(name); +} + +void SocialWindow::resetDamage(std::string name) +{ + mPlayers->resetDamage(name); +} + +void SocialWindow::updateButtons() +{ + if (!mTabs) + return; + + bool hasTabs = mTabs->getNumberOfTabs() > 0; + mInviteButton->setEnabled(hasTabs); + mLeaveButton->setEnabled(hasTabs); +} + +void SocialWindow::updatePortals() +{ + if (mNavigation) + mNavigation->updateList(); +} + +void SocialWindow::updatePortalNames() +{ + if (mNavigation) + static_cast<NavigationTab*>(mNavigation)->updateNames(); +} + +void SocialWindow::selectPortal(unsigned num) +{ + if (mNavigation) + mNavigation->selectIndex(num); +} + +int SocialWindow::getPortalIndex(int x, int y) +{ + if (mNavigation) + return static_cast<NavigationTab*>(mNavigation)->getPortalIndex(x, y); + else + return -1; +} + +void SocialWindow::addPortal(int x, int y) +{ + if (mNavigation) + static_cast<NavigationTab*>(mNavigation)->addPortal(x, y); +} + +void SocialWindow::removePortal(int x, int y) +{ + if (mNavigation) + static_cast<NavigationTab*>(mNavigation)->removePortal(x, y); +} + +void SocialWindow::nextTab() +{ + if (!mTabs) + return; + + int tab = mTabs->getSelectedTabIndex(); + + tab++; + if (tab == mTabs->getNumberOfTabs()) + tab = 0; + + mTabs->setSelectedTab(tab); +} + +void SocialWindow::prevTab() +{ + if (!mTabs) + return; + + int tab = mTabs->getSelectedTabIndex(); + + if (tab == 0) + tab = mTabs->getNumberOfTabs(); + tab--; + + mTabs->setSelectedTab(tab); +}
\ No newline at end of file diff --git a/src/gui/socialwindow.h b/src/gui/socialwindow.h new file mode 100644 index 000000000..6e654c4c6 --- /dev/null +++ b/src/gui/socialwindow.h @@ -0,0 +1,159 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SOCIALWINDOW_H +#define SOCIALWINDOW_H + +#include "gui/widgets/window.h" + +#include <guichan/actionevent.hpp> +#include <guichan/actionlistener.hpp> + +#include <string> +#include <map> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Button; +class ConfirmDialog; +class CreatePopup; +class Guild; +class Map; +class NavigateTab; +class Party; +class SocialTab; +class Tab; +class TabbedArea; +class TextDialog; +class PlayersTab; + +/** + * Party window. + * + * \ingroup Interface + */ +class SocialWindow : public Window, gcn::ActionListener +{ +public: + SocialWindow(); + + ~SocialWindow(); + + bool addTab(Guild *guild); + + bool removeTab(Guild *guild); + + bool addTab(Party *party); + + bool removeTab(Party *party); + + /** + * Handle events. + */ + void action(const gcn::ActionEvent &event); + + void showGuildInvite(const std::string &guildName, const int guildId, + const std::string &inviterName); + + void showGuildCreate(); + + void showPartyInvite(const std::string &partyName, + const std::string &inviter = ""); + + void showPartyCreate(); + + void updateActiveList(); + + void updateAvatar(std::string name); + + void resetDamage(std::string name); + + void logic(); + + void updatePortals(); + + void updatePortalNames(); + + int getPortalIndex(int x, int y); + + void addPortal(int x, int y); + + void removePortal(int x, int y); + + void nextTab(); + + void prevTab(); + + Map* getMap() + { return mMap; } + + void setMap(Map *map) + { mMap = map; mProcessedPortals = false; } + + bool getProcessedPortals() + { return mProcessedPortals; } + + void setProcessedPortals(int n) + { mProcessedPortals = n; } + + void selectPortal(unsigned num); + +protected: + friend class SocialTab; + + void updateButtons(); + + int mGuildInvited; + ConfirmDialog *mGuildAcceptDialog; + TextDialog *mGuildCreateDialog; + + std::string mPartyInviter; + ConfirmDialog *mPartyAcceptDialog; + TextDialog *mPartyCreateDialog; + + typedef std::map<Guild*, SocialTab*> GuildMap; + GuildMap mGuilds; + + typedef std::map<Party*, SocialTab*> PartyMap; + PartyMap mParties; + + SocialTab *mPlayers; + SocialTab *mNavigation; + + CreatePopup *mCreatePopup; + + Button *mCreateButton; + Button *mInviteButton; + Button *mLeaveButton; + TabbedArea *mTabs; + Map *mMap; + + int mLastUpdateTime; + bool mNeedUpdate; + bool mProcessedPortals; +}; + +extern SocialWindow *socialWindow; + +#endif // SOCIALWINDOW_H diff --git a/src/gui/specialswindow.cpp b/src/gui/specialswindow.cpp new file mode 100644 index 000000000..bd968191d --- /dev/null +++ b/src/gui/specialswindow.cpp @@ -0,0 +1,257 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/specialswindow.h" + +#include "log.h" + +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/container.h" +#include "gui/widgets/icon.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" +#include "gui/widgets/flowcontainer.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/specialhandler.h" + +#include "resources/specialdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include <string> + +#define SPECIALS_WIDTH 200 +#define SPECIALS_HEIGHT 32 + +class SpecialEntry; + + +class SpecialEntry : public Container +{ + public: + SpecialEntry(SpecialInfo *info); + + void update(int current, int needed); + + protected: + friend class SpecialsWindow; + SpecialInfo *mInfo; + + private: + Icon *mIcon; // icon to display + Label *mNameLabel; // name to display + Label *mLevelLabel; // level number label (only shown when applicable) + Button *mUse; // use button (only shown when applicable) + ProgressBar *mRechargeBar; // recharge bar (only shown when applicable) +}; + +SpecialsWindow::SpecialsWindow(): + Window(_("Specials")) +{ + setWindowName("Specials"); + setCloseButton(true); + setResizable(true); + setSaveVisible(true); + setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425); + setupWindow->registerWindowForReset(this); + + mTabs = new TabbedArea(); + + place(0, 0, mTabs, 5, 5); + + setLocationRelativeTo(getParent()); + loadWindowState(); +} + +SpecialsWindow::~SpecialsWindow() +{ + // Clear gui +} + +void SpecialsWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "use") + { + if (!event.getSource()) + return; + + SpecialEntry *disp = dynamic_cast<SpecialEntry*>( + event.getSource()->getParent()); + + if (disp) + { + /*Being *target = player_node->getTarget(); + + if (target) + Net::getSpecialHandler()->use(disp->mInfo->id, + 1, target->getId()); + else*/ + Net::getSpecialHandler()->use(disp->mInfo->id); + } + } + else if (event.getId() == "close") + { + setVisible(false); + } +} + +void SpecialsWindow::draw(gcn::Graphics *graphics) +{ + // update the progress bars + std::map<int, Special> specialData = PlayerInfo::getSpecialStatus(); + bool foundNew = false; + unsigned int found = 0; // number of entries in specialData + // which match mEntries + + for (std::map<int, Special>::iterator i = specialData.begin(); + i != specialData.end(); i++) + { + std::map<int, SpecialEntry *>::iterator e = mEntries.find(i->first); + if (e == mEntries.end()) + { + // found a new special - abort update and rebuild from scratch + foundNew = true; + break; + } + else + { + // update progress bar of special + if (e->second) + e->second->update(i->second.currentMana, i->second.neededMana); + found++; + } + } + // a rebuild is needed when a) the number of specials changed or b) + // an existing entry isn't found anymore + if (foundNew || found != mEntries.size()) + rebuild(specialData); + + Window::draw(graphics); +} + +void SpecialsWindow::rebuild(const std::map<int, Special> &specialData) +{ + make_dtor(mEntries); + mEntries.clear(); + int vPos = 0; //vertical position of next placed element + + for (std::map<int, Special>::const_iterator i = specialData.begin(); + i != specialData.end(); + i++) + { + logger->log("Updating special GUI for %d", i->first); + + SpecialInfo* info = SpecialDB::get(i->first); + if (info) + { + info->rechargeCurrent = i->second.currentMana; + info->rechargeNeeded = i->second.neededMana; + SpecialEntry* entry = new SpecialEntry(info); + entry->setPosition(0, vPos); + vPos += entry->getHeight(); + add(entry); + mEntries[i->first] = entry; + } + else + { + logger->log("Warning: No info available of special %d", i->first); + } + } +} + + +SpecialEntry::SpecialEntry(SpecialInfo *info) : + mInfo(info), + mIcon(NULL), + mLevelLabel(NULL), + mUse(NULL), + mRechargeBar(NULL) +{ + setFrameSize(1); + setOpaque(false); + setSize(SPECIALS_WIDTH, SPECIALS_HEIGHT); + + if (info && !info->icon.empty()) + mIcon = new Icon(info->icon); + else + mIcon = new Icon(Theme::resolveThemePath("unknown-item.png")); + + mIcon->setPosition(1, 0); + add(mIcon); + + if (info) + mNameLabel = new Label(info->name); + else + mNameLabel = new Label(""); + + mNameLabel->setPosition(35, 0); + add(mNameLabel); + + if (info && info->hasLevel) + { + mLevelLabel = new Label(toString(info->level)); + mLevelLabel->setPosition(getWidth() - mLevelLabel->getWidth(), 0); + add(mLevelLabel); + } + + if (info && info->isActive) + { + mUse = new Button("Use", "use", specialsWindow); + mUse->setPosition(getWidth() - mUse->getWidth(), 13); + add(mUse); + } + + if (info->hasRechargeBar) + { + float progress = 0; + if (info->rechargeNeeded) + { + progress = (float)info->rechargeCurrent + / (float)info->rechargeNeeded; + } + mRechargeBar = new ProgressBar(progress, 100, 10, Theme::PROG_MP); + mRechargeBar->setSmoothProgress(false); + mRechargeBar->setPosition(0, 13); + add(mRechargeBar); + } + +} + +void SpecialEntry::update(int current, int needed) +{ + if (mRechargeBar && needed) + { + float progress = (float)current / (float)needed; + mRechargeBar->setProgress(progress); + } +} diff --git a/src/gui/specialswindow.h b/src/gui/specialswindow.h new file mode 100644 index 000000000..66ef5e375 --- /dev/null +++ b/src/gui/specialswindow.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPECIALSWINDOW_H +#define SPECIALSWINDOW_H + +#include <vector> + +#include "guichanfwd.h" + +#include "playerinfo.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +#include <map> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Label; +class ScrollArea; +class Tab; +class TabbedArea; + +struct SpecialEntry; + +class SpecialsWindow : public Window, public gcn::ActionListener +{ + public: + SpecialsWindow(); + + ~SpecialsWindow(); + + /** + * Called when receiving actions from widget. + */ + void action(const gcn::ActionEvent &actionEvent); + + void draw(gcn::Graphics *graphics); + + private: + // (re)constructs the list of specials + void rebuild(const std::map<int, Special> &specialData); + + TabbedArea *mTabs; + std::map<int, SpecialEntry *> mEntries; +}; + +extern SpecialsWindow *specialsWindow; + +#endif // SPECIALSWINDOW_H diff --git a/src/gui/speechbubble.cpp b/src/gui/speechbubble.cpp new file mode 100644 index 000000000..08d000380 --- /dev/null +++ b/src/gui/speechbubble.cpp @@ -0,0 +1,91 @@ +/* + * Speech bubbles + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/speechbubble.h" + +#include "graphics.h" + +#include "gui/gui.h" +#include "gui/theme.h" + +#include "gui/widgets/textbox.h" + +#include <guichan/font.hpp> + +#include <guichan/widgets/label.hpp> + +SpeechBubble::SpeechBubble(): + Popup("Speech", "speechbubble.xml") +{ + setContentSize(140, 46); + setMinWidth(29); + setMinHeight(29); + + mCaption = new gcn::Label; + mCaption->setFont(boldFont); + + mSpeechBox = new TextBox; + mSpeechBox->setEditable(false); + mSpeechBox->setOpaque(false); + mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::CHAT)); + + add(mCaption); + add(mSpeechBox); +} + +void SpeechBubble::setCaption(const std::string &name, const gcn::Color *color) +{ + mCaption->setCaption(name); + mCaption->adjustSize(); + mCaption->setForegroundColor(*color); +} + +void SpeechBubble::setText(const std::string &text, bool showName) +{ + if (text == mText && (mCaption->getWidth() <= mSpeechBox->getMinWidth())) + return; + + mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::TEXT)); + + int width = mCaption->getWidth() + 2 * getPadding(); + mSpeechBox->setTextWrapped(text, 130 > width ? 130 : width); + const int speechWidth = mSpeechBox->getMinWidth() + 2 * getPadding(); + + const int fontHeight = getFont()->getHeight(); + const int nameHeight = showName ? mCaption->getHeight() + + (getPadding() / 2) : 0; + const int numRows = mSpeechBox->getNumberOfRows(); + const int height = (numRows * fontHeight) + nameHeight + getPadding(); + + if (width < speechWidth) + width = speechWidth; + + width += 2 * getPadding(); + + setContentSize(width, height); + + const int xPos = ((getWidth() - width) / 2); + const int yPos = ((getHeight() - height) / 2) + nameHeight; + + mCaption->setPosition(xPos, getPadding()); + mSpeechBox->setPosition(xPos, yPos); +} diff --git a/src/gui/speechbubble.h b/src/gui/speechbubble.h new file mode 100644 index 000000000..8682ab7e9 --- /dev/null +++ b/src/gui/speechbubble.h @@ -0,0 +1,58 @@ +/* + * Speech bubbles + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEECHBUBBLE_H +#define SPEECHBUBBLE_H + +#include "gui/theme.h" + +#include "gui/widgets/popup.h" + +class TextBox; + +class SpeechBubble : public Popup +{ + public: + /** + * Constructor. Initializes the speech bubble. + */ + SpeechBubble(); + + /** + * Sets the name displayed for the speech bubble, and in what color. + */ + void setCaption(const std::string &name, + const gcn::Color *color = + &Theme::getThemeColor(Theme::TEXT)); + + /** + * Sets the text to be displayed. + */ + void setText(const std::string &text, bool showName = true); + + private: + std::string mText; + gcn::Label *mCaption; + TextBox *mSpeechBox; +}; + +#endif diff --git a/src/gui/spellpopup.cpp b/src/gui/spellpopup.cpp new file mode 100644 index 000000000..80fa9f378 --- /dev/null +++ b/src/gui/spellpopup.cpp @@ -0,0 +1,105 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/spellpopup.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "textcommand.h" + +#include "graphics.h" +#include "units.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> +#include <guichan/widgets/label.hpp> + +SpellPopup::SpellPopup(): + Popup("SpellPopup") +{ + // Item Name + mItemName = new gcn::Label; + mItemName->setFont(boldFont); + mItemName->setPosition(getPadding(), getPadding()); + + add(mItemName); + addMouseListener(this); +} + +SpellPopup::~SpellPopup() +{ +} + +void SpellPopup::setItem(TextCommand *spell) +{ + if (spell) + mItemName->setCaption(spell->getName()); + else + mItemName->setCaption("?"); + + mItemName->adjustSize(); + int minWidth = mItemName->getWidth(); + + minWidth += 8; + setWidth(minWidth); + + setContentSize(minWidth, getPadding() + getFont()->getHeight()); +} + +void SpellPopup::view(int x, int y) +{ + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + { + if (graphics->getWidth() > getWidth()) + posX = graphics->getWidth() - getWidth(); + else + posX = 0; + } + if (posY + getHeight() > graphics->getHeight()) + { + if (y > getHeight() + distance) + posY = y - getHeight() - distance; + else + y = 0; + } + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void SpellPopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + // When the mouse moved on top of the popup, hide it + setVisible(false); +} diff --git a/src/gui/spellpopup.h b/src/gui/spellpopup.h new file mode 100644 index 000000000..1b14e0e4c --- /dev/null +++ b/src/gui/spellpopup.h @@ -0,0 +1,67 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPELLPOPUP_H +#define SPELLPOPUP_H + +#include "gui/widgets/popup.h" + +#include "textcommand.h" + +#include <guichan/mouselistener.hpp> + +class TextBox; + +/** + * A popup that displays information about an item. + */ +class SpellPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + SpellPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~SpellPopup(); + + /** + * Sets the info to be displayed given a particular item. + */ + void setItem(TextCommand *spell); + + /** + * Sets the location to display the item popup. + */ + void view(int x, int y); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + + private: + gcn::Label *mItemName; +}; + +#endif // SPELLPOPUP_H diff --git a/src/gui/statuspopup.cpp b/src/gui/statuspopup.cpp new file mode 100644 index 000000000..4d2c48fa7 --- /dev/null +++ b/src/gui/statuspopup.cpp @@ -0,0 +1,543 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/statuspopup.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "gui/widgets/layout.h" +#include "gui/widgets/textbox.h" + +#include "graphics.h" +#include "localplayer.h" +#include "units.h" +#include "viewport.h" +#include "keyboardconfig.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> +#include <guichan/widgets/label.hpp> + +StatusPopup::StatusPopup(): + Popup("StatusPopup") +{ + + const int fontHeight = getFont()->getHeight(); + + mMoveType = new gcn::Label; + mMoveType->setPosition(getPadding(), getPadding()); + + mCrazyMoveType = new gcn::Label; + mCrazyMoveType->setPosition(getPadding(), fontHeight + getPadding()); + + mMoveToTargetType = new gcn::Label; + mMoveToTargetType->setPosition(getPadding(), + 2 * fontHeight + getPadding()); + + mFollowMode = new gcn::Label; + mFollowMode->setPosition(getPadding(), 3 * fontHeight + getPadding()); + + mAttackWeaponType = new gcn::Label; + mAttackWeaponType->setPosition(getPadding(), + 4 + 4 * fontHeight + getPadding()); + + mAttackType = new gcn::Label; + mAttackType->setPosition(getPadding(), 4 + 5 * fontHeight + getPadding()); + + mMagicAttackType = new gcn::Label; + mMagicAttackType->setPosition(getPadding(), + 4 + 6 * fontHeight + getPadding()); + + mDropCounter = new gcn::Label; + mDropCounter->setPosition(getPadding(), 8 + 7 * fontHeight + getPadding()); + + mPickUpType = new gcn::Label; + mPickUpType->setPosition(getPadding(), 8 + 8 * fontHeight + getPadding()); + + mMapType = new gcn::Label; + mMapType->setPosition(getPadding(), 12 + 9 * fontHeight + getPadding()); + + mImitationMode = new gcn::Label; + mImitationMode->setPosition(getPadding(), + 16 + 10 * fontHeight + getPadding()); + + mAwayMode = new gcn::Label; + mAwayMode->setPosition(getPadding(), 16 + 11 * fontHeight + getPadding()); + + mCameraMode = new gcn::Label; + mCameraMode->setPosition(getPadding(), + 16 + 12 * fontHeight + getPadding()); + + mDisableGameModifiers = new gcn::Label; + mDisableGameModifiers->setPosition(getPadding(), + 20 + 13 * fontHeight + getPadding()); + + add(mMoveType); + add(mCrazyMoveType); + add(mMoveToTargetType); + add(mFollowMode); + add(mAttackWeaponType); + add(mAttackType); + add(mDropCounter); + add(mPickUpType); + add(mMapType); + add(mMagicAttackType); + add(mDisableGameModifiers); + add(mImitationMode); + add(mAwayMode); + add(mCameraMode); + +// addMouseListener(this); +} + +StatusPopup::~StatusPopup() +{ +} + +void StatusPopup::update() +{ + updateLabels(); + + int minWidth = mMoveType->getWidth(); + + if (mMoveToTargetType->getWidth() > minWidth) + minWidth = mMoveToTargetType->getWidth(); + if (mFollowMode->getWidth() > minWidth) + minWidth = mFollowMode->getWidth(); + if (mCrazyMoveType->getWidth() > minWidth) + minWidth = mCrazyMoveType->getWidth(); + if (mAttackWeaponType->getWidth() > minWidth) + minWidth = mAttackWeaponType->getWidth(); + if (mAttackType->getWidth() > minWidth) + minWidth = mAttackType->getWidth(); + if (mDropCounter->getWidth() > minWidth) + minWidth = mDropCounter->getWidth(); + if (mPickUpType->getWidth() > minWidth) + minWidth = mPickUpType->getWidth(); + if (mMapType->getWidth() > minWidth) + minWidth = mMapType->getWidth(); + if (mMagicAttackType->getWidth() > minWidth) + minWidth = mMagicAttackType->getWidth(); + if (mDisableGameModifiers->getWidth() > minWidth) + minWidth = mDisableGameModifiers->getWidth(); + if (mAwayMode->getWidth() > minWidth) + minWidth = mAwayMode->getWidth(); + if (mCameraMode->getWidth() > minWidth) + minWidth = mCameraMode->getWidth(); + if (mImitationMode->getWidth() > minWidth) + minWidth = mImitationMode->getWidth(); + + minWidth += 16 + 2 * getPadding(); + setWidth(minWidth); + + const int fontHeight = getFont()->getHeight(); + + setHeight(24 + 8 + 14 * fontHeight + getPadding()); +} + +void StatusPopup::view(int x, int y) +{ + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + posX = graphics->getWidth() - getWidth(); + if (posY + getHeight() > graphics->getHeight()) + posY = y - getHeight() - distance; + + update(); + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void StatusPopup::updateLabels() +{ + if (!player_node || !viewport) + return; + + switch (player_node->getInvertDirection()) + { + case 0: + mMoveType->setCaption("(D) default moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + case 1: + mMoveType->setCaption("(I) invert moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + case 2: + mMoveType->setCaption("(c) moves with some crazy moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + + case 3: + mMoveType->setCaption("(C) moves with crazy moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + case 4: + mMoveType->setCaption("(d) double normal + crazy " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + default: + mMoveType->setCaption("(?) move " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + } + mMoveType->adjustSize(); + + if (player_node->getCrazyMoveType() < 10) + { + mCrazyMoveType->setCaption(strprintf("(%d) crazy move number %d ", + player_node->getCrazyMoveType(), player_node->getCrazyMoveType()) + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE)); + } + else + { + switch (player_node->getCrazyMoveType()) + { + case 10: + mCrazyMoveType->setCaption("(a) custom crazy move " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE)); + break; + default: + mCrazyMoveType->setCaption("(?) crazy move " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE)); + break; + } + } + mCrazyMoveType->adjustSize(); + + switch (player_node->getMoveToTargetType()) + { + case 0: + mMoveToTargetType->setCaption("(0) default moves to target " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 1: + mMoveToTargetType->setCaption("(1) moves to target in distance 1 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 2: + mMoveToTargetType->setCaption("(2) moves to target in distance 3 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 3: + mMoveToTargetType->setCaption("(3) moves to target in distance 3 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 4: + mMoveToTargetType->setCaption("(5) moves to target in distance 5 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 5: + mMoveToTargetType->setCaption("(7) moves to target in distance 7 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 6: + mMoveToTargetType->setCaption( + "(A) moves to target in attack range " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + default: + mMoveToTargetType->setCaption("(?) move to target " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + } + mMoveToTargetType->adjustSize(); + + switch (player_node->getFollowMode()) + { + case 0: + mFollowMode->setCaption("(D) default follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + case 1: + mFollowMode->setCaption("(R) relative follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + case 2: + mFollowMode->setCaption("(M) mirror follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + case 3: + mFollowMode->setCaption("(P) pet follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + default: + mFollowMode->setCaption("(?) unknown follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + } + mFollowMode->adjustSize(); + + switch (player_node->getAttackWeaponType()) + { + case 1: + mAttackWeaponType->setCaption("(D) default attack " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + case 2: + mAttackWeaponType->setCaption("(s) switch attack without shield " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + case 3: + mAttackWeaponType->setCaption("(S) switch attack with shield " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + default: + mAttackWeaponType->setCaption("(?) attack " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + } + mAttackWeaponType->adjustSize(); + + switch (player_node->getAttackType()) + { + case 0: + mAttackType->setCaption("(D) default attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + case 1: + mAttackType->setCaption("(G) go and attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + case 2: + mAttackType->setCaption("(A) go, attack, pickup " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + case 3: + mAttackType->setCaption("(d) without auto attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + default: + mAttackType->setCaption("(?) attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + } + mAttackType->adjustSize(); + + mDropCounter->setCaption(strprintf("(%d) drop counter %d ", + player_node->getQuickDropCounter(), player_node->getQuickDropCounter()) + + keyboard.getKeyValueString(keyboard.KEY_SWITCH_QUICK_DROP)); + mDropCounter->adjustSize(); + + switch (player_node->getPickUpType()) + { + case 0: + mPickUpType->setCaption("(S) small pick up 1x1 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 1: + mPickUpType->setCaption("(D) default pick up 2x1 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 2: + mPickUpType->setCaption("(F) forward pick up 2x3 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 3: + mPickUpType->setCaption("(3) pick up 3x3 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 4: + mPickUpType->setCaption("(g) go and pick up in distance 4 " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 5: + mPickUpType->setCaption("(G) go and pick up in distance 8 " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 6: + mPickUpType->setCaption("(A) go and pick up in max distance " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + default: + mPickUpType->setCaption("(?) pick up " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + } + mPickUpType->adjustSize(); + + switch (viewport->getDebugPath()) + { + case 0: + mMapType->setCaption("(N) normal map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 1: + mMapType->setCaption("(D) debug map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 2: + mMapType->setCaption("(u) ultra map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 3: + mMapType->setCaption("(U) ultra map view 2 " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 4: + mMapType->setCaption("(e) empty map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 5: + mMapType->setCaption("(b) black & white map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + default: + mMapType->setCaption("(?) map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + } + mMapType->adjustSize(); + + switch (player_node->getMagicAttackType()) + { + case 0: + mMagicAttackType->setCaption("(f) use #flar for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 1: + mMagicAttackType->setCaption("(c) use #chiza for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 2: + mMagicAttackType->setCaption("(I) use #ingrav for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 3: + mMagicAttackType->setCaption("(F) use #frillyar for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 4: + mMagicAttackType->setCaption("(U) use #upmarmu for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + default: + mMagicAttackType->setCaption("(?) magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + } + mMagicAttackType->adjustSize(); + + switch (player_node->getImitationMode()) + { + case 0: + mImitationMode->setCaption("(D) default imitation " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_IMITATION_MODE)); + break; + case 1: + mImitationMode->setCaption("(O) outfits imitation " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_IMITATION_MODE)); + break; + default: + mImitationMode->setCaption("(?) imitation " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_IMITATION_MODE)); + break; + } + mImitationMode->adjustSize(); + + switch (player_node->getAwayMode()) + { + case 0: + mAwayMode->setCaption("(O) on keyboard " + + keyboard.getKeyValueString(keyboard.KEY_AWAY)); + break; + case 1: + mAwayMode->setCaption("(A) away " + + keyboard.getKeyValueString(keyboard.KEY_AWAY)); + break; + default: + mAwayMode->setCaption("(?) away " + + keyboard.getKeyValueString(keyboard.KEY_AWAY)); + break; + } + mAwayMode->adjustSize(); + + switch (viewport->getCameraMode()) + { + case 0: + mCameraMode->setCaption("(G) game camera mode " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + case 1: + mCameraMode->setCaption("(F) free camera mode " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + case 2: + mCameraMode->setCaption("(D) design camera mode " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + default: + mCameraMode->setCaption("(?) away " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + } + mCameraMode->adjustSize(); + + if (player_node->getDisableGameModifiers()) + { + mDisableGameModifiers->setCaption("Game modifiers are disabled " + + keyboard.getKeyValueString(keyboard.KEY_DISABLE_GAME_MODIFIERS)); + } + else + { + mDisableGameModifiers->setCaption("Game modifiers are enabled " + + keyboard.getKeyValueString(keyboard.KEY_DISABLE_GAME_MODIFIERS)); + } + mDisableGameModifiers->adjustSize(); +} diff --git a/src/gui/statuspopup.h b/src/gui/statuspopup.h new file mode 100644 index 000000000..62d952384 --- /dev/null +++ b/src/gui/statuspopup.h @@ -0,0 +1,78 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef StatusPopup_H +#define StatusPopup_H + +#include "gui/widgets/popup.h" + +#include "resources/iteminfo.h" + +#include <guichan/mouselistener.hpp> + +class TextBox; + +/** + * A popup that displays information about an item. + */ +class StatusPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + StatusPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~StatusPopup(); + + /** + * Sets the location to display the item popup. + */ + void view(int x, int y); + +// void mouseMoved(gcn::MouseEvent &mouseEvent); + + void update(); + + private: + void updateLabels(); + gcn::Label *mMoveType; + gcn::Label *mCrazyMoveType; + gcn::Label *mMoveToTargetType; + gcn::Label *mFollowMode; + gcn::Label *mAttackType; + gcn::Label *mAttackWeaponType; + gcn::Label *mDropCounter; + gcn::Label *mPickUpType; + gcn::Label *mMapType; + gcn::Label *mMagicAttackType; + gcn::Label *mDisableGameModifiers; + gcn::Label *mImitationMode; + gcn::Label *mAwayMode; + gcn::Label *mCameraMode; +}; + +#endif // StatusPopup_H diff --git a/src/gui/statuswindow.cpp b/src/gui/statuswindow.cpp new file mode 100644 index 000000000..349893bfa --- /dev/null +++ b/src/gui/statuswindow.cpp @@ -0,0 +1,880 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/statuswindow.h" + +#include "configuration.h" +#include "event.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "units.h" +#include "viewport.h" + +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/vertcontainer.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/playerhandler.h" +#include "net/gamehandler.h" + +#include "utils/gettext.h" +#include "utils/mathutils.h" +#include "utils/stringutils.h" + +class AttrDisplay : public Container +{ + public: + enum Type + { + DERIVED = 0, + CHANGEABLE, + UNKNOWN + }; + + ~AttrDisplay(); + + virtual std::string update(); + + virtual Type getType() + { return UNKNOWN; } + + protected: + AttrDisplay(int id, const std::string &name); + + const int mId; + const std::string mName; + + LayoutHelper *mLayout; + Label *mLabel; + Label *mValue; +}; + +class DerDisplay : public AttrDisplay +{ + public: + DerDisplay(int id, const std::string &name); + + virtual Type getType() + { return DERIVED; } +}; + +class ChangeDisplay : public AttrDisplay, gcn::ActionListener +{ + public: + ChangeDisplay(int id, const std::string &name); + + std::string update(); + + virtual Type getType() + { return CHANGEABLE; } + + void setPointsNeeded(int needed); + + private: + void action(const gcn::ActionEvent &event); + + int mNeeded; + + Label *mPoints; + Button *mDec; + Button *mInc; +}; + +StatusWindow::StatusWindow(): + Window(player_node ? player_node->getName() : "?") +{ + listen(CHANNEL_ATTRIBUTES); + + setWindowName("Status"); + setupWindow->registerWindowForReset(this); + setResizable(true); + setCloseButton(true); + setSaveVisible(true); + setDefaultSize((windowContainer->getWidth() - 365) / 2, + (windowContainer->getHeight() - 255) / 2, 365, 275); + + // ---------------------- + // Status Part + // ---------------------- + + mLvlLabel = new Label(strprintf(_("Level: %d"), 0)); + mMoneyLabel = new Label(strprintf(_("Money: %s"), "")); + + int max = PlayerInfo::getAttribute(MAX_HP); + if (!max) + max = 1; + + mHpLabel = new Label(_("HP:")); + mHpBar = new ProgressBar(max ? + static_cast<float>(PlayerInfo::getAttribute(HP)) + / static_cast<float>(max): + static_cast<float>(0), 80, 15, Theme::PROG_HP); + + max = PlayerInfo::getAttribute(EXP_NEEDED); + mXpLabel = new Label(_("Exp:")); + mXpBar = new ProgressBar(max ? + static_cast<float>(PlayerInfo::getAttribute(EXP)) + / static_cast<float>(max): + static_cast<float>(0), 80, 15, Theme::PROG_EXP); + + bool magicBar = Net::getGameHandler()->canUseMagicBar(); + + int job = Net::getPlayerHandler()->getJobLocation() + && serverConfig.getValueBool("showJob", false); + + if (magicBar) + { + max = PlayerInfo::getAttribute(MAX_MP); + mMpLabel = new Label(_("MP:")); + mMpBar = new ProgressBar(max ? + static_cast<float>(PlayerInfo::getAttribute(MAX_MP)) + / static_cast<float>(max) : static_cast<float>(0), + 80, 15, Net::getPlayerHandler()->canUseMagic() ? + Theme::PROG_MP : Theme::PROG_NO_MP); + } + else + { + mMpLabel = 0; + mMpBar = 0; + } + + place(0, 0, mLvlLabel, 3); + // 5, 0 Job Level + place(8, 0, mMoneyLabel, 3); + place(0, 1, mHpLabel).setPadding(3); + place(1, 1, mHpBar, 4); + place(5, 1, mXpLabel).setPadding(3); + place(6, 1, mXpBar, 5); + if (magicBar) + { + place(0, 2, mMpLabel).setPadding(3); + // 5, 2 and 6, 2 Job Progress Bar + if (job) + place(1, 2, mMpBar, 4); + else + place(1, 2, mMpBar, 10); + } + + if (job) + { + mJobLvlLabel = new Label(strprintf(_("Job: %d"), 0)); + mJobLabel = new Label(_("Job:")); + mJobBar = new ProgressBar(0.0f, 80, 15, Theme::PROG_JOB); + + place(5, 0, mJobLvlLabel, 3); + place(5, 2, mJobLabel).setPadding(3); + place(6, 2, mJobBar, 5); + } + else + { + mJobLvlLabel = 0; + mJobLabel = 0; + mJobBar = 0; + } + + // ---------------------- + // Stats Part + // ---------------------- + + mAttrCont = new VertContainer(32); + mAttrScroll = new ScrollArea(mAttrCont); + mAttrScroll->setOpaque(false); + mAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + mAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO); + place(0, 3, mAttrScroll, 5, 3); + + mDAttrCont = new VertContainer(32); + mDAttrScroll = new ScrollArea(mDAttrCont); + mDAttrScroll->setOpaque(false); + mDAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + mDAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO); + place(6, 3, mDAttrScroll, 5, 3); + + getLayout().setRowHeight(3, Layout::AUTO_SET); + + mCharacterPointsLabel = new Label("C"); + place(0, 6, mCharacterPointsLabel, 5); + + if (Net::getPlayerHandler()->canCorrectAttributes()) + { + mCorrectionPointsLabel = new Label("C"); + place(0, 7, mCorrectionPointsLabel, 5); + } + + loadWindowState(); + + // Update bars + updateHPBar(mHpBar, true); + if (magicBar) + updateMPBar(mMpBar, true); + updateXPBar(mXpBar, false); + + mMoneyLabel->setCaption(strprintf(_("Money: %s"), + Units::formatCurrency(PlayerInfo::getAttribute(MONEY)).c_str())); + mMoneyLabel->adjustSize(); + mCharacterPointsLabel->setCaption(strprintf(_("Character points: %d"), + PlayerInfo::getAttribute(CHAR_POINTS))); + mCharacterPointsLabel->adjustSize(); + + if (player_node && player_node->isGM()) + { + mLvlLabel->setCaption(strprintf(_("Level: %d (GM %d)"), + PlayerInfo::getAttribute(LEVEL), player_node->getGMLevel())); + } + else + { + mLvlLabel->setCaption(strprintf(_("Level: %d"), + PlayerInfo::getAttribute(LEVEL))); + } + mLvlLabel->adjustSize(); +} + +void StatusWindow::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + switch(event.getInt("id")) + { + case HP: case MAX_HP: + updateHPBar(mHpBar, true); + break; + + case MP: case MAX_MP: + updateMPBar(mMpBar, true); + break; + + case EXP: case EXP_NEEDED: + updateXPBar(mXpBar, false); + break; + + case MONEY: + mMoneyLabel->setCaption(strprintf(_("Money: %s"), + Units::formatCurrency(event.getInt("newValue")).c_str())); + mMoneyLabel->adjustSize(); + break; + + case CHAR_POINTS: + mCharacterPointsLabel->setCaption(strprintf( + _("Character points: %d"), event.getInt("newValue"))); + + mCharacterPointsLabel->adjustSize(); + // Update all attributes + for (Attrs::iterator it = mAttrs.begin(); + it != mAttrs.end(); it++) + { + if (it->second) + it->second->update(); + } + break; + + case CORR_POINTS: + mCorrectionPointsLabel->setCaption(strprintf( + _("Correction points: %d"), event.getInt("newValue"))); + mCorrectionPointsLabel->adjustSize(); + // Update all attributes + for (Attrs::iterator it = mAttrs.begin(); + it != mAttrs.end(); it++) + { + if (it->second) + it->second->update(); + } + break; + + case LEVEL: + mLvlLabel->setCaption(strprintf(_("Level: %d"), + event.getInt("newValue"))); + mLvlLabel->adjustSize(); + break; + + default: + break; + } + } + else if (event.getName() == EVENT_UPDATESTAT) + { + int id = event.getInt("id"); + if (id == Net::getPlayerHandler()->getJobLocation()) + { + if (mJobLvlLabel) + { + mJobLvlLabel->setCaption(strprintf(_("Job: %d"), + PlayerInfo::getStatBase(id))); + mJobLvlLabel->adjustSize(); + + updateProgressBar(mJobBar, id, false); + } + } + else + { + updateMPBar(mMpBar, true); + Attrs::iterator it = mAttrs.find(id); + if (it != mAttrs.end() && it->second) + { + if (it->second) + it->second->update(); + } + } + } +} + +void StatusWindow::setPointsNeeded(int id, int needed) +{ + Attrs::iterator it = mAttrs.find(id); + + if (it != mAttrs.end()) + { + AttrDisplay *disp = it->second; + if (disp && disp->getType() == AttrDisplay::CHANGEABLE) + static_cast<ChangeDisplay*>(disp)->setPointsNeeded(needed); + } +} + +void StatusWindow::addAttribute(int id, const std::string &name, + bool modifiable, + const std::string &description _UNUSED_) +{ + AttrDisplay *disp; + + if (modifiable) + { + disp = new ChangeDisplay(id, name); + mAttrCont->add(disp); + } + else + { + disp = new DerDisplay(id, name); + mDAttrCont->add(disp); + } + + mAttrs[id] = disp; +} + +void StatusWindow::updateHPBar(ProgressBar *bar, bool showMax) +{ + if (!bar) + return; + + if (showMax) + bar->setText(toString(PlayerInfo::getAttribute(HP)) + + "/" + toString(PlayerInfo::getAttribute(MAX_HP))); + else + bar->setText(toString(PlayerInfo::getAttribute(HP))); + + float prog = 1.0; + + if (PlayerInfo::getAttribute(MAX_HP) > 0) + { + prog = static_cast<float>(PlayerInfo::getAttribute(HP)) + / static_cast<float>(PlayerInfo::getAttribute(MAX_HP)); + } + bar->setProgress(prog); +} + +void StatusWindow::updateMPBar(ProgressBar *bar, bool showMax) +{ + if (!bar) + return; + + if (showMax) + { + bar->setText(toString(PlayerInfo::getAttribute(MP)) + + "/" + toString(PlayerInfo::getAttribute(MAX_MP))); + } + else + { + bar->setText(toString(PlayerInfo::getAttribute(MP))); + } + + float prog = 1.0f; + + if (PlayerInfo::getAttribute(MAX_MP) > 0) + { + if (PlayerInfo::getAttribute(MAX_MP)) + { + prog = static_cast<float>(PlayerInfo::getAttribute(MP)) + / static_cast<float>(PlayerInfo::getAttribute(MAX_MP)); + } + else + { + prog = static_cast<float>(PlayerInfo::getAttribute(MP)); + } + } + + if (Net::getPlayerHandler()->canUseMagic()) + bar->setProgressPalette(Theme::PROG_MP); + else + bar->setProgressPalette(Theme::PROG_NO_MP); + + bar->setProgress(prog); +} + +void StatusWindow::updateProgressBar(ProgressBar *bar, int value, int max, + bool percent) +{ + if (!bar) + return; + + if (max == 0) + { + bar->setText(_("Max")); + bar->setProgress(1.0); + } + else + { + float progress = static_cast<float>(value) + / static_cast<float>(max); + + if (percent) + bar->setText(strprintf("%2.5f", 100 * progress) + "%"); + else + bar->setText(toString(value) + "/" + toString(max)); + + bar->setProgress(progress); + } +} + +void StatusWindow::updateXPBar(ProgressBar *bar, bool percent) +{ + if (!bar) + return; + + updateProgressBar(bar, PlayerInfo::getAttribute(EXP), + PlayerInfo::getAttribute(EXP_NEEDED), percent); +} + +void StatusWindow::updateProgressBar(ProgressBar *bar, int id, bool percent) +{ + std::pair<int, int> exp = PlayerInfo::getStatExperience(id); + updateProgressBar(bar, exp.first, exp.second, percent); +} + +void StatusWindow::updateStatusBar(ProgressBar *bar, bool percent _UNUSED_) +{ + if (!player_node || !viewport) + return; + + std::string str; + + switch (player_node->getInvertDirection()) + { + case 0: + str = "D"; + break; + case 1: + str = "I"; + break; + case 2: + str = "c"; + break; + case 3: + str = "C"; + break; + case 4: + str = "d"; + break; + default: + str = "?"; + break; + } + + if (player_node->getCrazyMoveType() < 10) + str += toString(player_node->getCrazyMoveType()); + else + { + switch (player_node->getCrazyMoveType()) + { + case 10: + str += "a"; + break; + default: + str += "?"; + break; + } + } + + switch (player_node->getMoveToTargetType()) + { + case 0: + str += "0"; + break; + case 1: + str += "1"; + break; + case 2: + str += "2"; + break; + case 3: + str += "3"; + break; + case 4: + str += "5"; + break; + case 5: + str += "7"; + break; + case 6: + str += "A"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getFollowMode()) + { + case 0: + str += "D"; + break; + case 1: + str += "R"; + break; + case 2: + str += "M"; + break; + case 3: + str += "P"; + break; + default: + str += "?"; + break; + } + + str += " "; + switch (player_node->getAttackWeaponType()) + { + case 1: + str += "D"; + break; + case 2: + str += "s"; + break; + case 3: + str += "S"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getAttackType()) + { + case 0: + str += "D"; + break; + case 1: + str += "G"; + break; + case 2: + str += "A"; + break; + case 3: + str += "d"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getMagicAttackType()) + { + case 0: + str += "f"; + break; + case 1: + str += "c"; + break; + case 2: + str += "I"; + break; + case 3: + str += "F"; + break; + case 4: + str += "U"; + break; + default: + str += "?"; + break; + } + + str += " " + toString(player_node->getQuickDropCounter()); + + switch (player_node->getPickUpType()) + { + case 0: + str += "S"; + break; + case 1: + str += "D"; + break; + case 2: + str += "F"; + break; + case 3: + str += "3"; + break; + case 4: + str += "g"; + break; + case 5: + str += "G"; + break; + case 6: + str += "A"; + break; + default: + str += "?"; + break; + } + + switch (viewport->getDebugPath()) + { + case 0: + str += " N"; + break; + case 1: + str += " D"; + break; + case 2: + str += " u"; + break; + case 3: + str += " U"; + break; + case 4: + str += " e"; + break; + case 5: + str += " b"; + break; + default: + str += " ?"; + break; + } + + switch (player_node->getImitationMode()) + { + case 0: + str += " D"; + break; + case 1: + str += " O"; + break; + default: + str += " ?"; + break; + } + + switch (viewport->getCameraMode()) + { + case 0: + str += "G"; + break; + case 1: + str += "F"; + break; + case 2: + str += "D"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getAwayMode()) + { + case 0: + str += "O"; + break; + case 1: + str += "A"; + break; + default: + str += "?"; + break; + } + + bar->setText(str); + bar->setProgress(50); + if (player_node->getDisableGameModifiers()) + { + gcn::Color col; + col.r = 100; + col.g = 100; + col.b = 100; +// bar->setColor(new gcn::Color(100, 100, 100)); + bar->setColor(col); + } + else + { + gcn::Color col; + col.r = 255; + col.g = 255; + col.b = 0; +// bar->setColor(new gcn::Color(255, 255, 0)); + bar->setColor(col); + } +} + +AttrDisplay::AttrDisplay(int id, const std::string &name): + mId(id), + mName(name) +{ + setSize(100, 32); + mLabel = new Label(name); + mValue = new Label("1 "); + + mLabel->setAlignment(Graphics::CENTER); + mValue->setAlignment(Graphics::CENTER); + + mLayout = new LayoutHelper(this); +} + +AttrDisplay::~AttrDisplay() +{ + delete mLayout; +} + +std::string AttrDisplay::update() +{ + int base = PlayerInfo::getStatBase(mId); + int bonus = PlayerInfo::getStatMod(mId); + std::string value = toString(base + bonus); + if (bonus) + value += strprintf("=%d%+d", base, bonus); + mValue->setCaption(value); + return mName; +} + +DerDisplay::DerDisplay(int id, const std::string &name): + AttrDisplay(id, name) +{ + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = mLayout->getPlacer(0, 0); + + place(0, 0, mLabel, 3); + place(3, 0, mValue, 2); + + update(); +} + +ChangeDisplay::ChangeDisplay(int id, const std::string &name): + AttrDisplay(id, name), mNeeded(1) +{ + mPoints = new Label(_("Max")); + mInc = new Button(_("+"), "inc", this); + + // Do the layout + ContainerPlacer place = mLayout->getPlacer(0, 0); + + place(0, 0, mLabel, 3); + place(4, 0, mValue, 2); + place(6, 0, mInc); + place(7, 0, mPoints); + + if (Net::getPlayerHandler()->canCorrectAttributes()) + { + mDec = new Button(_("-"), "dec", this); + mDec->setWidth(mInc->getWidth()); + + place(3, 0, mDec); + } + else + { + mDec = 0; + } + + update(); +} + +std::string ChangeDisplay::update() +{ + if (mNeeded > 0) + mPoints->setCaption(toString(mNeeded)); + else + mPoints->setCaption(_("Max")); + + if (mDec) + mDec->setEnabled(PlayerInfo::getAttribute(CORR_POINTS)); + + mInc->setEnabled(PlayerInfo::getAttribute(CHAR_POINTS) >= mNeeded && + mNeeded > 0); + + return AttrDisplay::update(); +} + +void ChangeDisplay::setPointsNeeded(int needed) +{ + mNeeded = needed; + + update(); +} + +void ChangeDisplay::action(const gcn::ActionEvent &event) +{ + if (Net::getPlayerHandler()->canCorrectAttributes() && + event.getSource() == mDec) + { + int newcorpoints = PlayerInfo::getAttribute(CORR_POINTS) - 1; + PlayerInfo::setAttribute(CORR_POINTS, newcorpoints); + + int newpoints = PlayerInfo::getAttribute(CHAR_POINTS) + 1; + PlayerInfo::setAttribute(CHAR_POINTS, newpoints); + + int newbase = PlayerInfo::getStatBase(mId) - 1; + PlayerInfo::setStatBase(mId, newbase); + + Net::getPlayerHandler()->decreaseAttribute(mId); + } + else if (event.getSource() == mInc) + { + int cnt = 1; + if (config.getBoolValue("quickStats")) + { + cnt = mInc->getClickCount(); + if (cnt > 10) + cnt = 10; + } + + int newpoints = PlayerInfo::getAttribute(CHAR_POINTS) - cnt; + PlayerInfo::setAttribute(CHAR_POINTS, newpoints); + + int newbase = PlayerInfo::getStatBase(mId) + cnt; + PlayerInfo::setStatBase(mId, newbase); + + for (unsigned f = 0; f < mInc->getClickCount(); f ++) + { + Net::getPlayerHandler()->increaseAttribute(mId); + if (cnt != 1) + SDL_Delay(100); + } + } +} diff --git a/src/gui/statuswindow.h b/src/gui/statuswindow.h new file mode 100644 index 000000000..4b324073e --- /dev/null +++ b/src/gui/statuswindow.h @@ -0,0 +1,99 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef STATUS_H +#define STATUS_H + +#include "guichanfwd.h" +#include "listener.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +#include <map> + +class AttrDisplay; +class ProgressBar; +class ScrollArea; +class VertContainer; + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * The player status dialog. + * + * \ingroup Interface + */ +class StatusWindow : public Window, public Mana::Listener +{ + public: + /** + * Constructor. + */ + StatusWindow(); + + void event(Channels channel, const Mana::Event &event); + + void setPointsNeeded(int id, int needed); + + void addAttribute(int id, const std::string &name, bool modifiable, + const std::string &description); + + static void updateHPBar(ProgressBar *bar, bool showMax = false); + static void updateMPBar(ProgressBar *bar, bool showMax = false); + static void updateXPBar(ProgressBar *bar, bool percent = true); + static void updateStatusBar(ProgressBar *bar, bool percent = true); + static void updateProgressBar(ProgressBar *bar, int value, int max, + bool percent); + void updateProgressBar(ProgressBar *bar, int id, + bool percent = true); + + private: + /** + * Status Part + */ + gcn::Label *mLvlLabel, *mMoneyLabel; + gcn::Label *mHpLabel, *mMpLabel, *mXpLabel; + ProgressBar *mHpBar, *mMpBar, *mXpBar; + + gcn::Label *mJobLvlLabel, *mJobLabel; + ProgressBar *mJobBar; + + VertContainer *mAttrCont; + ScrollArea *mAttrScroll; + VertContainer *mDAttrCont; + ScrollArea *mDAttrScroll; + + gcn::Label *mCharacterPointsLabel; + gcn::Label *mCorrectionPointsLabel; + + typedef std::map<int, AttrDisplay*> Attrs; + Attrs mAttrs; +}; + +extern StatusWindow *statusWindow; + +#endif diff --git a/src/gui/textcommandeditor.cpp b/src/gui/textcommandeditor.cpp new file mode 100644 index 000000000..2300033a5 --- /dev/null +++ b/src/gui/textcommandeditor.cpp @@ -0,0 +1,390 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/textcommandeditor.h" + +#include <SDL.h> +#include <SDL_thread.h> +#include <vector> +#include <algorithm> + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/radiobutton.h" +#include "gui/widgets/table.h" +#include "gui/widgets/textfield.h" + +#include "chat.h" +#include "configuration.h" +#include "item.h" +#include "localplayer.h" +#include "main.h" +#include "keyboardconfig.h" +#include "spellmanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include "resources/itemdb.h" +#include "resources/iteminfo.h" + +class IconsModal : public gcn::ListModel +{ +public: + IconsModal() + { + std::map<int, ItemInfo*> info = ItemDB::getItemInfos(); + std::list<std::string> tempStrings; + + for (std::map<int, ItemInfo*>::const_iterator + i = info.begin(), i_end = info.end(); + i != i_end; ++i) + { + if (i->first < 0) + continue; + + ItemInfo info = (*i->second); + std::string name = info.getName(); + if (name != "unnamed" && !info.getName().empty() + && info.getName() != "unnamed") + { + tempStrings.push_back(name); + } + } + tempStrings.sort(); + mStrings.push_back(""); + for (std::list<std::string>::const_iterator i = tempStrings.begin(), + i_end = tempStrings.end(); i != i_end; ++i) + { + mStrings.push_back(*i); + } + } + + virtual ~IconsModal() + {} + + virtual int getNumberOfElements() + { + return static_cast<int>(mStrings.size()); + } + + virtual std::string getElementAt(int i) + { + if (i < 0 || i >= getNumberOfElements()) + return _("???"); + + return mStrings.at(i); + } +private: + std::vector<std::string> mStrings; +}; + + +const char *TARGET_TYPE_TEXT[3] = +{ + N_("No Target"), + N_("Allow Target"), + N_("Need Target"), +}; + +const char *MAGIC_SCHOOL_TEXT[6] = +{ + N_("General Magic"), + N_("Life Magic"), + N_("War Magic"), + N_("Transmute Magic"), + N_("Nature Magic"), + N_("Astral Magic") +}; + +class TargetTypeModel : public gcn::ListModel +{ +public: + virtual ~TargetTypeModel() { } + + virtual int getNumberOfElements() + { + return 3; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return TARGET_TYPE_TEXT[i]; + } +}; + +class MagicSchoolModel : public gcn::ListModel +{ +public: + virtual ~MagicSchoolModel() { } + + virtual int getNumberOfElements() + { + return 6; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return MAGIC_SCHOOL_TEXT[i]; + } +}; + + +TextCommandEditor::TextCommandEditor(TextCommand *command): + Window(_("Command Editor")) +{ + int w = 350; + int h = 350; + + mEnabledKeyboard = keyboard.isEnabled(); + keyboard.setEnabled(false); + + setWindowName("TextCommandEditor"); + //setCloseButton(true); + setDefaultSize(w, h, ImageRect::CENTER); + + mAdvanced = false; + mCommand = command; + + mIsMagicCommand = (command->getCommandType() == TEXT_COMMAND_MAGIC); + + mIsMagic = new RadioButton(_("magic"), "magic", mIsMagicCommand); + mIsMagic->setActionEventId("magic"); + mIsMagic->addActionListener(this); + + mIsOther = new RadioButton(_("other"), "magic", !mIsMagicCommand); + mIsOther->setActionEventId("other"); + mIsOther->addActionListener(this); + + + mSymbolLabel = new Label(_("Symbol:")); + mSymbolTextField = new TextField(); + + mCommandLabel = new Label(_("Command:")); + mCommandTextField = new TextField(); + + mManaLabel = new Label(_("Mana:")); + mManaField = new IntTextField(0); + mManaField->setRange(0, 500); + mManaField->setWidth(20); + + mTypeLabel = new Label(_("Target Type:")); + mTypeDropDown = new DropDown(new TargetTypeModel); + mTypeDropDown->setActionEventId("type"); + mTypeDropDown->addActionListener(this); + + mIconLabel = new Label(_("Icon:")); + mIconDropDown = new DropDown(new IconsModal); + mIconDropDown->setActionEventId("icon"); + mIconDropDown->addActionListener(this); + mIconDropDown->setSelectedString(mCommand->getIcon()); + + mMagicLvlLabel = new Label(_("Magic level:")); + mMagicLvlField = new IntTextField(0); + mMagicLvlField->setRange(0, 5); + mMagicLvlField->setWidth(20); + + mSchoolLabel = new Label(_("Magic School:")); + mSchoolDropDown = new DropDown(new MagicSchoolModel); + mSchoolDropDown->setActionEventId("school"); + mSchoolDropDown->addActionListener(this); + mSchoolDropDown->setSelected(0); + + mSchoolLvlLabel = new Label(_("School level:")); + mSchoolLvlField = new IntTextField(0); + mSchoolLvlField->setRange(0, 5); + mSchoolLvlField->setWidth(20); + + mSaveButton = new Button(_("Save"), "save", this); + mSaveButton->adjustSize(); + + mCancelButton = new Button(_("Cancel"), "cancel", this); + mCancelButton->adjustSize(); + + mDeleteButton = new Button(_("Delete"), "delete", this); + mDeleteButton->adjustSize(); + + if (command->getCommandType() == TEXT_COMMAND_MAGIC) + showControls(true); + else + showControls(false); + + mSymbolTextField->setText(command->getSymbol()); + mCommandTextField->setText(command->getCommand()); + mManaField->setValue(command->getMana()); + mTypeDropDown->setSelected(command->getTargetType()); + mMagicLvlField->setValue(command->getBaseLvl()); + mSchoolDropDown->setSelected(command->getSchool() - MAGIC_START_ID); + mSchoolLvlField->setValue(command->getSchoolLvl()); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mIsMagic, 1); + place(2, 0, mIsOther, 1); + place(0, 1, mSymbolLabel, 2).setPadding(3); + place(2, 1, mSymbolTextField, 3).setPadding(3); + place(0, 2, mCommandLabel, 2).setPadding(3); + place(2, 2, mCommandTextField, 4).setPadding(3); + place(0, 3, mTypeLabel, 2).setPadding(3); + place(2, 3, mTypeDropDown, 3).setPadding(3); + + place(0, 4, mIconLabel, 2).setPadding(3); + place(2, 4, mIconDropDown, 3).setPadding(3); + + place(0, 5, mManaLabel, 2).setPadding(3); + place(2, 5, mManaField, 3).setPadding(3); + place(0, 6, mMagicLvlLabel, 2).setPadding(3); + place(2, 6, mMagicLvlField, 3).setPadding(3); + + place(0, 7, mSchoolLabel, 2).setPadding(3); + place(2, 7, mSchoolDropDown, 3).setPadding(3); + place(0, 8, mSchoolLvlLabel, 2).setPadding(3); + place(2, 8, mSchoolLvlField, 3).setPadding(3); + + place(0, 9, mSaveButton, 2).setPadding(3); + place(2, 9, mCancelButton, 2).setPadding(3); + place(4, 9, mDeleteButton, 2).setPadding(3); + + setWidth(w); + setHeight(h); + + center(); + + setVisible(true); +} + +TextCommandEditor::~TextCommandEditor() +{ +} + +void TextCommandEditor::action(const gcn::ActionEvent &event) +{ + const std::string &eventId = event.getId(); + + if (eventId == "magic") + { + mIsMagicCommand = true; + showControls(true); + } + else if (eventId == "other") + { + mIsMagicCommand = false; + showControls(false); + } + else if (eventId == "save") + { + save(); + scheduleDelete(); + } + else if (eventId == "cancel") + { + scheduleDelete(); + } + else if (eventId == "delete") + { + deleteCommand(); + scheduleDelete(); + } +} + +void TextCommandEditor::update() +{ +} + +void TextCommandEditor::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); +} + +void TextCommandEditor::updateList() +{ +} + +void TextCommandEditor::reset() +{ +} + +void TextCommandEditor::showControls(bool show) +{ + mManaField->setVisible(show); + mManaLabel->setVisible(show); + mMagicLvlLabel->setVisible(show); + mMagicLvlField->setVisible(show); + mSchoolLabel->setVisible(show); + mSchoolDropDown->setVisible(show); + mSchoolLvlLabel->setVisible(show); + mSchoolLvlField->setVisible(show); +} + +void TextCommandEditor::scheduleDelete() +{ + keyboard.setEnabled(mEnabledKeyboard); + Window::scheduleDelete(); +} + +void TextCommandEditor::save() +{ + if (mIsMagicCommand) + mCommand->setCommandType(TEXT_COMMAND_MAGIC); + else + mCommand->setCommandType(TEXT_COMMAND_TEXT); + + mCommand->setSymbol(mSymbolTextField->getText()); + mCommand->setCommand(mCommandTextField->getText()); + mCommand->setMana(mManaField->getValue()); + mCommand->setTargetType( + static_cast<SpellTarget>(mTypeDropDown->getSelected())); + mCommand->setIcon(mIconDropDown->getSelectedString()); + mCommand->setBaseLvl(mMagicLvlField->getValue()); + mCommand->setSchool(static_cast<MagicSchool>( + mSchoolDropDown->getSelected() + MAGIC_START_ID)); + mCommand->setSchoolLvl(mSchoolLvlField->getValue()); + if (spellManager) + spellManager->save(); +} + +void TextCommandEditor::deleteCommand() +{ + mCommand->setCommandType(TEXT_COMMAND_TEXT); + mCommand->setSymbol(""); + mCommand->setCommand(""); + mCommand->setMana(0); + mCommand->setTargetType(NOTARGET); + mCommand->setIcon(""); + mCommand->setBaseLvl(0); + mCommand->setSchool(SKILL_MAGIC); + mCommand->setSchoolLvl(0); + if (spellManager) + spellManager->save(); +} diff --git a/src/gui/textcommandeditor.h b/src/gui/textcommandeditor.h new file mode 100644 index 000000000..9f8c0ad51 --- /dev/null +++ b/src/gui/textcommandeditor.h @@ -0,0 +1,105 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TEXTCOMMANDEDITOR_H +#define TEXTCOMMANDEDITOR_H + +#include "gui/widgets/window.h" + +#include "textcommand.h" + +#include <guichan/actionlistener.hpp> + +class RadioButton; +class Label; +class TextBox; +class TextField; +class DropDown; +class ListModel; +class Button; +class TextCommand; +class IntTextField; + +class TextCommandEditor : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + TextCommandEditor(TextCommand *command); + + /** + * Destructor. + */ + ~TextCommandEditor(); + + void action(const gcn::ActionEvent &event); + + void update(); + + void widgetResized(const gcn::Event &event); + + void updateList(); + + void reset(); + + void scheduleDelete(); + + private: + void showControls(bool show); + + void save(); + + void deleteCommand(); + + TextCommand *mCommand; + bool mAdvanced; + + RadioButton *mIsMagic; + RadioButton *mIsOther; + Label *mSymbolLabel; + TextField *mSymbolTextField; + Label *mCommandLabel; + TextField *mCommandTextField; + Label *mTypeLabel; + DropDown *mTypeDropDown; + Label *mIconLabel; + DropDown *mIconDropDown; + Label *mManaLabel; + IntTextField *mManaField; + Label *mMagicLvlLabel; + IntTextField *mMagicLvlField; + Label *mSchoolLabel; + DropDown *mSchoolDropDown; + Label *mSchoolLvlLabel; + IntTextField *mSchoolLvlField; + + //Button *mAdvancedButton; + Button *mCancelButton; + Button *mSaveButton; + Button *mDeleteButton; + + bool mEnabledKeyboard; + bool mIsMagicCommand; +}; + +#endif diff --git a/src/gui/textdialog.cpp b/src/gui/textdialog.cpp new file mode 100644 index 000000000..c47e028c4 --- /dev/null +++ b/src/gui/textdialog.cpp @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/textdialog.h" + +#include "keyboardconfig.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/textfield.h" + +#include "utils/gettext.h" + +int TextDialog::instances = 0; + +TextDialog::TextDialog(const std::string &title, const std::string &msg, + Window *parent): + Window(title, true, parent), + mTextField(new TextField) +{ + mEnabledKeyboard = keyboard.isEnabled(); + keyboard.setEnabled(false); + + gcn::Label *textLabel = new Label(msg); + mOkButton = new Button(_("OK"), "OK", this); + gcn::Button *cancelButton = new Button(_("Cancel"), "CANCEL", this); + + place(0, 0, textLabel, 4); + place(0, 1, mTextField, 4); + place(2, 2, mOkButton); + place(3, 2, cancelButton); + + reflowLayout(static_cast<short>(textLabel->getWidth() + 20)); + + if (getParent()) + { + setLocationRelativeTo(getParent()); + getParent()->moveToTop(this); + } + setVisible(true); + requestModalFocus(); + mTextField->requestFocus(); + + instances++; +} + +TextDialog::~TextDialog() +{ + instances--; +} + +void TextDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "CANCEL") + setActionEventId("~" + getActionEventId()); + + distributeActionEvent(); + close(); +} + +const std::string &TextDialog::getText() const +{ + return mTextField->getText(); +} + +void TextDialog::setText(std::string text) +{ + if (mTextField) + mTextField->setText(text); +} + +void TextDialog::close() +{ + keyboard.setEnabled(mEnabledKeyboard); + scheduleDelete(); +}
\ No newline at end of file diff --git a/src/gui/textdialog.h b/src/gui/textdialog.h new file mode 100644 index 000000000..9c7e8d312 --- /dev/null +++ b/src/gui/textdialog.h @@ -0,0 +1,74 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_GUILD_DIALOG_H +#define GUI_GUILD_DIALOG_H + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +class TextField; + +/** +* An option dialog. + * + * \ingroup GUI + */ +class TextDialog : public Window, public gcn::ActionListener +{ +public: + /** + * Constructor. + * + * @see Window::Window + */ + TextDialog(const std::string &title, const std::string &msg, + Window *parent = 0); + + ~TextDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Get the text in the textfield + */ + const std::string &getText() const; + + void setText(std::string text); + + static bool isActive() + { return instances; } + + void close(); + +private: + static int instances; + + TextField *mTextField; + gcn::Button *mOkButton; + bool mEnabledKeyboard; +}; + +#endif diff --git a/src/gui/textpopup.cpp b/src/gui/textpopup.cpp new file mode 100644 index 000000000..270b0f759 --- /dev/null +++ b/src/gui/textpopup.cpp @@ -0,0 +1,99 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/textpopup.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "graphics.h" +#include "units.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> +#include <guichan/widgets/label.hpp> + +TextPopup::TextPopup(): + Popup("TextPopup") +{ + const int fontHeight = getFont()->getHeight(); + + mText1 = new gcn::Label; + mText1->setPosition(getPadding(), getPadding()); + + mText2 = new gcn::Label; + mText2->setPosition(getPadding(), fontHeight + 2 * getPadding()); + + add(mText1); + add(mText2); + addMouseListener(this); +} + +TextPopup::~TextPopup() +{ +} + +void TextPopup::show(int x, int y, const std::string &str1, + const std::string &str2) +{ + mText1->setCaption(str1); + mText1->adjustSize(); + mText2->setCaption(str2); + mText2->adjustSize(); + + int minWidth = mText1->getWidth(); + if (mText2->getWidth() > minWidth) + minWidth = mText2->getWidth(); + + minWidth += 4 * getPadding(); + setWidth(minWidth); + + if (!str2.empty()) + setHeight((2 * getPadding() + mText1->getFont()->getHeight()) * 2); + else + setHeight(2 * getPadding() + mText1->getFont()->getHeight()); + + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + posX = graphics->getWidth() - getWidth(); + if (posY + getHeight() > graphics->getHeight()) + posY = y - getHeight() - distance; + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void TextPopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + // When the mouse moved on top of the popup, hide it + setVisible(false); +} diff --git a/src/gui/textpopup.h b/src/gui/textpopup.h new file mode 100644 index 000000000..3b4158f6e --- /dev/null +++ b/src/gui/textpopup.h @@ -0,0 +1,68 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TEXTPOPUP_H +#define TEXTPOPUP_H + +#include "gui/widgets/popup.h" + +#include <guichan/mouselistener.hpp> + +class TextBox; + +/** + * A popup that displays information about an item. + */ +class TextPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + TextPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~TextPopup(); + + /** + * Sets the text to be displayed. + */ + void show(int x, int y, const std::string &str1) + { show(x, y, str1, static_cast<const char*>("")); }; + + /** + * Sets the text to be displayed. + */ + void show(int x, int y, const std::string &str1, + const std::string &str2); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + + private: + gcn::Label *mText1; + gcn::Label *mText2; +}; + +#endif // TEXTPOPUP_H diff --git a/src/gui/theme.cpp b/src/gui/theme.cpp new file mode 100644 index 000000000..b45cef5b0 --- /dev/null +++ b/src/gui/theme.cpp @@ -0,0 +1,791 @@ +/* + * Gui Skinning + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/theme.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" + +#include "resources/dye.h" +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include "utils/dtor.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include <physfs.h> + +#include <algorithm> +#include <physfs.h> + +static std::string defaultThemePath; + +std::string Theme::mThemePath; +std::string Theme::mThemeName; +Theme *Theme::mInstance = 0; + +// Set the theme path... +static void initDefaultThemePath() +{ + ResourceManager *resman = ResourceManager::getInstance(); + defaultThemePath = branding.getStringValue("guiThemePath"); + + logger->log("defaultThemePath: " + defaultThemePath); + if (!defaultThemePath.empty() && resman->isDirectory(defaultThemePath)) + return; + else + defaultThemePath = "themes/"; +} + +Skin::Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown, + const std::string &filePath, + const std::string &name): + instances(0), + mFilePath(filePath), + mName(name), + mBorder(skin), + mCloseImage(close), + mStickyImageUp(stickyUp), + mStickyImageDown(stickyDown) +{} + +Skin::~Skin() +{ + // Clean up static resources + for (int i = 0; i < 9; i++) + { + delete mBorder.grid[i]; + mBorder.grid[i] = 0; + } + + if (mCloseImage) + { + mCloseImage->decRef(); + mCloseImage = 0; + } + delete mStickyImageUp; + mStickyImageUp = 0; + delete mStickyImageDown; + mStickyImageDown = 0; +} + +void Skin::updateAlpha(float minimumOpacityAllowed) +{ + const float alpha = static_cast<float>( + std::max(static_cast<double>(minimumOpacityAllowed), + static_cast<double>(Client::getGuiAlpha()))); + + for_each(mBorder.grid, mBorder.grid + 9, + std::bind2nd(std::mem_fun(&Image::setAlpha), alpha)); + + if (mCloseImage) + mCloseImage->setAlpha(alpha); + if (mStickyImageUp) + mStickyImageUp->setAlpha(alpha); + if (mStickyImageDown) + mStickyImageDown->setAlpha(alpha); +} + +int Skin::getMinWidth() const +{ + if (!mBorder.grid[ImageRect::UPPER_LEFT] + || !mBorder.grid[ImageRect::UPPER_RIGHT]) + { + return 1; + } + + return mBorder.grid[ImageRect::UPPER_LEFT]->getWidth() + + mBorder.grid[ImageRect::UPPER_RIGHT]->getWidth(); +} + +int Skin::getMinHeight() const +{ + if (!mBorder.grid[ImageRect::UPPER_LEFT] + || !mBorder.grid[ImageRect::LOWER_LEFT]) + { + return 1; + } + + return mBorder.grid[ImageRect::UPPER_LEFT]->getHeight() + + mBorder.grid[ImageRect::LOWER_LEFT]->getHeight(); +} + +Theme::Theme(): + Palette(THEME_COLORS_END), + mMinimumOpacity(-1.0f), + mProgressColors(ProgressColors(THEME_PROG_END)) +{ + initDefaultThemePath(); + + config.addListener("guialpha", this); + loadColors(); + + mColors[HIGHLIGHT].ch = 'H'; + mColors[CHAT].ch = 'C'; + mColors[GM].ch = 'G'; + mColors[PLAYER].ch = 'Y'; + mColors[WHISPER].ch = 'W'; + mColors[WHISPER_OFFLINE].ch = 'w'; + mColors[IS].ch = 'I'; + mColors[PARTY_CHAT_TAB].ch = 'P'; + mColors[GUILD_CHAT_TAB].ch = 'U'; + mColors[SERVER].ch = 'S'; + mColors[LOGGER].ch = 'L'; + mColors[HYPERLINK].ch = '<'; +} + +Theme::~Theme() +{ + delete_all(mSkins); + config.removeListener("guialpha", this); + delete_all(mProgressColors); +} + +Theme *Theme::instance() +{ + if (!mInstance) + mInstance = new Theme; + + return mInstance; +} + +void Theme::deleteInstance() +{ + delete mInstance; + mInstance = 0; +} + +gcn::Color Theme::getProgressColor(int type, float progress) +{ + int color[3] = {0, 0, 0}; + + if (mInstance) + { + DyePalette *dye = mInstance->mProgressColors[type]; + + if (dye) + dye->getColor(progress, color); + else + logger->log("color not found: " + toString(type)); + } + + return gcn::Color(color[0], color[1], color[2]); +} + +Skin *Theme::load(const std::string &filename, const std::string &defaultPath) +{ + // Check if this skin was already loaded + + SkinIterator skinIterator = mSkins.find(filename); + if (mSkins.end() != skinIterator) + { + if (skinIterator->second) + skinIterator->second->instances++; + return skinIterator->second; + } + + Skin *skin = readSkin(filename); + + if (!skin) + { + // Try falling back on the defaultPath if this makes sense + if (filename != defaultPath) + { + logger->log("Error loading skin '%s', falling back on default.", + filename.c_str()); + + skin = readSkin(defaultPath); + } + + if (!skin) + { + logger->log(strprintf("Error: Loading default skin '%s' failed. " + "Make sure the skin file is valid.", + defaultPath.c_str())); + } + } + + // Add the skin to the loaded skins + mSkins[filename] = skin; + + return skin; +} + +void Theme::setMinimumOpacity(float minimumOpacity) +{ + if (minimumOpacity > 1.0f) + return; + + mMinimumOpacity = minimumOpacity; + updateAlpha(); +} + +void Theme::updateAlpha() +{ + for (SkinIterator iter = mSkins.begin(); iter != mSkins.end(); ++iter) + { + if (iter->second) + iter->second->updateAlpha(mMinimumOpacity); + } +} + +void Theme::optionChanged(const std::string &) +{ + updateAlpha(); +} + +Skin *Theme::readSkin(const std::string &filename) +{ + if (filename.empty()) + return 0; + +// std::string filename = filename0; +// ResourceManager *resman = ResourceManager::getInstance(); + logger->log("Loading skin '%s'.", filename.c_str()); +// filename = resman->mapPathToSkin(filename0); + + XML::Document doc(resolveThemePath(filename)); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "skinset")) + return 0; + + const std::string skinSetImage = XML::getProperty(rootNode, "image", ""); + + if (skinSetImage.empty()) + { + logger->log1("Theme::readSkin(): Skinset does not define an image!"); + return 0; + } + + logger->log("Theme::load(): <skinset> defines '%s' as a skin image.", + skinSetImage.c_str()); + + Image *dBorders = Theme::getImageFromTheme(skinSetImage); + ImageRect border; + memset(&border, 0, sizeof(ImageRect)); + + // iterate <widget>'s + for_each_xml_child_node(widgetNode, rootNode) + { + if (!xmlStrEqual(widgetNode->name, BAD_CAST "widget")) + continue; + + const std::string widgetType = + XML::getProperty(widgetNode, "type", "unknown"); + if (widgetType == "Window") + { + // Iterate through <part>'s + // LEEOR / TODO: + // We need to make provisions to load in a CloseButton image. For + // now it can just be hard-coded. + for_each_xml_child_node(partNode, widgetNode) + { + if (!xmlStrEqual(partNode->name, BAD_CAST "part")) + continue; + + const std::string partType = + XML::getProperty(partNode, "type", "unknown"); + // TOP ROW + const int xPos = XML::getProperty(partNode, "xpos", 0); + const int yPos = XML::getProperty(partNode, "ypos", 0); + const int width = XML::getProperty(partNode, "width", 1); + const int height = XML::getProperty(partNode, "height", 1); + + if (partType == "top-left-corner") + { + if (dBorders) + { + border.grid[0] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[0] = 0; + } + } + else if (partType == "top-edge") + { + if (dBorders) + { + border.grid[1] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[1] = 0; + } + } + else if (partType == "top-right-corner") + { + if (dBorders) + { + border.grid[2] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[2] = 0; + } + } + + // MIDDLE ROW + else if (partType == "left-edge") + { + if (dBorders) + { + border.grid[3] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[3] = 0; + } + } + else if (partType == "bg-quad") + { + if (dBorders) + { + border.grid[4] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[4] = 0; + } + } + else if (partType == "right-edge") + { + if (dBorders) + { + border.grid[5] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[5] = 0; + } + } + + // BOTTOM ROW + else if (partType == "bottom-left-corner") + { + if (dBorders) + { + border.grid[6] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[6] = 0; + } + } + else if (partType == "bottom-edge") + { + if (dBorders) + { + border.grid[7] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[7] = 0; + } + } + else if (partType == "bottom-right-corner") + { + if (dBorders) + { + border.grid[8] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[8] = 0; + } + } + + else + { + logger->log("Theme::readSkin(): Unknown part type '%s'", + partType.c_str()); + } + } + } + else + { + logger->log("Theme::readSkin(): Unknown widget type '%s'", + widgetType.c_str()); + } + } + + if (dBorders) + dBorders->decRef(); + + logger->log1("Finished loading skin."); + + // Hard-coded for now until we update the above code + // to look for window buttons + Image *closeImage = Theme::getImageFromTheme("close_button.png"); + Image *sticky = Theme::getImageFromTheme("sticky_button.png"); + Image *stickyImageUp = 0; + Image *stickyImageDown = 0; + if (sticky) + { + stickyImageUp = sticky->getSubImage(0, 0, 15, 15); + stickyImageDown = sticky->getSubImage(15, 0, 15, 15); + sticky->decRef(); + } + + Skin *skin = new Skin(border, closeImage, stickyImageUp, stickyImageDown, + filename); + skin->updateAlpha(mMinimumOpacity); + return skin; +} + +bool Theme::tryThemePath(std::string themeName) +{ + if (!themeName.empty()) + { + std::string path = defaultThemePath + themeName; + if (PHYSFS_exists(path.c_str())) + { + mThemePath = path; + mThemeName = themeName; + return true; + } + } + + return false; +} + +void Theme::fillSkinsList(std::vector<std::string> &list) +{ + char **skins = PHYSFS_enumerateFiles( + branding.getStringValue("guiThemePath").c_str()); + + for (char **i = skins; *i != 0; i++) + { + if (PHYSFS_isDirectory(( + branding.getStringValue("guiThemePath") + *i).c_str())) + { + list.push_back(*i); + } + } + + PHYSFS_freeList(skins); +} + +void Theme::fillFontsList(std::vector<std::string> &list) +{ + PHYSFS_permitSymbolicLinks(1); + char **fonts = PHYSFS_enumerateFiles( + branding.getStringValue("fontsPath").c_str()); + + for (char **i = fonts; *i != 0; i++) + { + if (!PHYSFS_isDirectory(( + branding.getStringValue("fontsPath") + *i).c_str())) + { + list.push_back(*i); + } + } + + PHYSFS_freeList(fonts); + PHYSFS_permitSymbolicLinks(0); +} + +void Theme::selectSkin() +{ + prepareThemePath(); +} + +void Theme::prepareThemePath() +{ + initDefaultThemePath(); + + mThemePath = ""; + mThemeName = ""; + + // Try theme from settings + if (tryThemePath(config.getValue("selectedSkin", ""))) + return; + + // Try theme from settings + if (tryThemePath(config.getValue("theme", ""))) + return; + + // Try theme from branding + if (tryThemePath(branding.getValue("theme", ""))) + return; + + if (mThemePath.empty()) + mThemePath = "graphics/gui"; + + instance()->loadColors(mThemePath); + + logger->log("Selected Theme: " + mThemePath); +} + +std::string Theme::resolveThemePath(const std::string &path) +{ + // Need to strip off any dye info for the existence tests + int pos = static_cast<int>(path.find('|')); + std::string file; + if (pos > 0) + file = path.substr(0, pos); + else + file = path; + + // Might be a valid path already + if (PHYSFS_exists(file.c_str())) + return path; + + // Try the theme + file = getThemePath() + "/" + file; + if (PHYSFS_exists(file.c_str())) + return getThemePath() + "/" + path; + + // Backup + return branding.getStringValue("guiPath") + path; +} + +Image *Theme::getImageFromTheme(const std::string &path) +{ + ResourceManager *resman = ResourceManager::getInstance(); + return resman->getImage(resolveThemePath(path)); +} + +ImageSet *Theme::getImageSetFromTheme(const std::string &path, + int w, int h) +{ + ResourceManager *resman = ResourceManager::getInstance(); + return resman->getImageSet(resolveThemePath(path), w, h); +} + +static int readColorType(const std::string &type) +{ + static std::string colors[] = + { + "TEXT", + "SHADOW", + "OUTLINE", + "PROGRESS_BAR", + "BUTTON", + "BUTTON_DISABLED", + "TAB", + "PARTY_CHAT_TAB", + "PARTY_SOCIAL_TAB", + "GUILD_CHAT_TAB", + "GUILD_SOCIAL_TAB", + "BACKGROUND", + "HIGHLIGHT", + "TAB_FLASH", + "TAB_PLAYER_FLASH", + "SHOP_WARNING", + "ITEM_EQUIPPED", + "CHAT", + "GM", + "PLAYER", + "WHISPER", + "WHISPER_OFFLINE", + "IS", + "SERVER", + "LOGGER", + "HYPERLINK", + "UNKNOWN_ITEM", + "GENERIC", + "HEAD", + "USABLE", + "TORSO", + "ONEHAND", + "LEGS", + "FEET", + "TWOHAND", + "SHIELD", + "RING", + "NECKLACE", + "ARMS", + "AMMO", + "SERVER_VERSION_NOT_SUPPORTED", + "WARNING" + }; + + if (type.empty()) + return -1; + + for (int i = 0; i < Theme::THEME_COLORS_END; i++) + { + if (compareStrI(type, colors[i]) == 0) + return i; + } + + return -1; +} + +static gcn::Color readColor(const std::string &description) +{ + int size = static_cast<int>(description.length()); + if (size < 7 || description[0] != '#') + { + logger->log("Error, invalid theme color palette: %s", + description.c_str()); + return Palette::BLACK; + } + + int v = 0; + for (int i = 1; i < 7; ++i) + { + char c = description[i]; + int n; + + if ('0' <= c && c <= '9') + { + n = c - '0'; + } + else if ('A' <= c && c <= 'F') + { + n = c - 'A' + 10; + } + else if ('a' <= c && c <= 'f') + { + n = c - 'a' + 10; + } + else + { + logger->log("Error, invalid theme color palette: %s", + description.c_str()); + return Palette::BLACK; + } + + v = (v << 4) | n; + } + + return gcn::Color(v); +} + +static Palette::GradientType readColorGradient(const std::string &grad) +{ + static std::string grads[] = + { + "STATIC", + "PULSE", + "SPECTRUM", + "RAINBOW" + }; + + if (grad.empty()) + return Palette::STATIC; + + for (int i = 0; i < 4; i++) + { + if (compareStrI(grad, grads[i])) + return static_cast<Palette::GradientType>(i); + } + + return Palette::STATIC; +} + +static int readProgressType(const std::string &type) +{ + static std::string colors[] = + { + "DEFAULT", + "HP", + "MP", + "NO_MP", + "EXP", + "INVY_SLOTS", + "WEIGHT", + "JOB" + }; + + if (type.empty()) + return -1; + + for (int i = 0; i < Theme::THEME_PROG_END; i++) + { + if (compareStrI(type, colors[i]) == 0) + return i; + } + + return -1; +} + +void Theme::loadColors(std::string file) +{ +// if (file == mThemePath) +// return; // No need to reload + + if (file == "") + file = "colors.xml"; + else + file += "/colors.xml"; + + XML::Document doc(resolveThemePath(file)); + xmlNodePtr root = doc.rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "colors")) + { + logger->log("Error loading colors file: %s", file.c_str()); + return; + } + + logger->log("Loading colors file: %s", file.c_str()); + + int type; + std::string temp; + gcn::Color color; + GradientType grad; + + for_each_xml_child_node(node, root) + { + if (xmlStrEqual(node->name, BAD_CAST "color")) + { + type = readColorType(XML::getProperty(node, "id", "")); + if (type < 0) // invalid or no type given + continue; + + temp = XML::getProperty(node, "color", ""); + if (temp.empty()) // no color set, so move on + continue; + + color = readColor(temp); + grad = readColorGradient(XML::getProperty(node, "effect", "")); + + mColors[type].set(type, color, grad, 10); + } + else if (xmlStrEqual(node->name, BAD_CAST "progressbar")) + { + type = readProgressType(XML::getProperty(node, "id", "")); + if (type < 0) // invalid or no type given + continue; + + mProgressColors[type] = new DyePalette(XML::getProperty(node, + "color", "")); + } + } +} diff --git a/src/gui/theme.h b/src/gui/theme.h new file mode 100644 index 000000000..bb7a66f85 --- /dev/null +++ b/src/gui/theme.h @@ -0,0 +1,273 @@ +/* + * Gui Skinning + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SKIN_H +#define SKIN_H + +#include "configlistener.h" +#include "graphics.h" + +#include "gui/palette.h" + +#include <map> +#include <string> +#include <vector> + +class DyePalette; +class Image; +class ImageSet; +class ProgressBar; + +class Skin +{ + public: + Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown, + const std::string &filePath, + const std::string &name = ""); + + ~Skin(); + + /** + * Returns the skin's name. Useful for giving a human friendly skin + * name if a dialog for skin selection for a specific window type is + * done. + */ + const std::string &getName() const + { return mName; } + + /** + * Returns the skin's xml file path. + */ + const std::string &getFilePath() const + { return mFilePath; } + + /** + * Returns the background skin. + */ + const ImageRect &getBorder() const + { return mBorder; } + + /** + * Returns the image used by a close button for this skin. + */ + Image *getCloseImage() const + { return mCloseImage; } + + /** + * Returns the image used by a sticky button for this skin. + */ + Image *getStickyImage(bool state) const + { return state ? mStickyImageDown : mStickyImageUp; } + + /** + * Returns the minimum width which can be used with this skin. + */ + int getMinWidth() const; + + /** + * Returns the minimum height which can be used with this skin. + */ + int getMinHeight() const; + + /** + * Updates the alpha value of the skin + */ + void updateAlpha(float minimumOpacityAllowed = 0.0f); + + int instances; + + private: + std::string mFilePath; /**< File name path for the skin */ + std::string mName; /**< Name of the skin to use */ + ImageRect mBorder; /**< The window border and background */ + Image *mCloseImage; /**< Close Button Image */ + Image *mStickyImageUp; /**< Sticky Button Image */ + Image *mStickyImageDown; /**< Sticky Button Image */ +}; + +class Theme : public Palette, public ConfigListener +{ + public: + static Theme *instance(); + + static void deleteInstance(); + + static void prepareThemePath(); + + static void selectSkin(); + + static std::string getThemePath() + { return mThemePath; } + + static std::string getThemeName() + { return mThemeName; } + + static void fillSkinsList(std::vector<std::string> &list); + + static void fillFontsList(std::vector<std::string> &list); + + /** + * Returns the patch to the given gui resource relative to the theme + * or, if it isn't in the theme, relative to 'graphics/gui'. + */ + static std::string resolveThemePath(const std::string &path); + + static Image *getImageFromTheme(const std::string &path); + + static ImageSet *getImageSetFromTheme(const std::string &path, + int w, int h); + + enum ThemePalette + { + TEXT = 0, + SHADOW, + OUTLINE, + PROGRESS_BAR, + BUTTON, + BUTTON_DISABLED, + TAB, + PARTY_CHAT_TAB, + PARTY_SOCIAL_TAB, + GUILD_CHAT_TAB, + GUILD_SOCIAL_TAB, + BACKGROUND, + HIGHLIGHT, + TAB_FLASH, + TAB_PLAYER_FLASH, + SHOP_WARNING, + ITEM_EQUIPPED, + CHAT, + GM, + PLAYER, + WHISPER, + WHISPER_OFFLINE, + IS, + SERVER, + LOGGER, + HYPERLINK, + UNKNOWN_ITEM, + GENERIC, + HEAD, + USABLE, + TORSO, + ONEHAND, + LEGS, + FEET, + TWOHAND, + SHIELD, + RING, + NECKLACE, + ARMS, + AMMO, + SERVER_VERSION_NOT_SUPPORTED, + WARNING, + THEME_COLORS_END + }; + + enum ProgressPalette + { + PROG_DEFAULT = 0, + PROG_HP, + PROG_MP, + PROG_NO_MP, + PROG_EXP, + PROG_INVY_SLOTS, + PROG_WEIGHT, + PROG_JOB, + THEME_PROG_END + }; + + /** + * Gets the color associated with the type. Sets the alpha channel + * before returning. + * + * @param type the color type requested + * @param alpha alpha channel to use + * + * @return the requested color + */ + inline static const gcn::Color &getThemeColor(int type, + int alpha = 255) + { return mInstance->getColor(type, alpha); } + + const static gcn::Color &getThemeColor(char c, bool &valid) + { return mInstance->getColor(c, valid); } + + static gcn::Color getProgressColor(int type, float progress); + + /** + * Loads a skin. + */ + Skin *load(const std::string &filename, + const std::string &defaultPath = getThemePath()); + + /** + * Updates the alpha values of all of the skins. + */ + void updateAlpha(); + + /** + * Get the minimum opacity allowed to skins. + */ + float getMinimumOpacity() + { return mMinimumOpacity; } + + /** + * Set the minimum opacity allowed to skins. + * Set a negative value to free the minimum allowed. + */ + void setMinimumOpacity(float minimumOpacity); + + void optionChanged(const std::string &); + + private: + Theme(); + ~Theme(); + + Skin *readSkin(const std::string &filename0); + + // Map containing all window skins + typedef std::map<std::string, Skin*> Skins; + typedef Skins::iterator SkinIterator; + + Skins mSkins; + + static std::string mThemePath; + static std::string mThemeName; + static Theme *mInstance; + + static bool tryThemePath(std::string themePath); + + void loadColors(std::string file = ""); + + /** + * Tells if the current skins opacity + * should not get less than the given value + */ + float mMinimumOpacity; + + typedef std::vector<DyePalette*> ProgressColors; + ProgressColors mProgressColors; +}; + +#endif diff --git a/src/gui/trade.cpp b/src/gui/trade.cpp new file mode 100644 index 000000000..ee67dc6da --- /dev/null +++ b/src/gui/trade.cpp @@ -0,0 +1,420 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/trade.h" + +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "units.h" + +#include "gui/inventorywindow.h" +#include "gui/itemamount.h" +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/itemcontainer.h" +#include "gui/widgets/label.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/layout.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" +#include "net/tradehandler.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/font.hpp> + +#include <sstream> + +#define CAPTION_PROPOSE _("Propose trade") +#define CAPTION_CONFIRMED _("Confirmed. Waiting...") +#define CAPTION_ACCEPT _("Agree trade") +#define CAPTION_ACCEPTED _("Agreed. Waiting...") + +TradeWindow::TradeWindow(): + Window(_("Trade: You")), + mMyInventory(new Inventory(Inventory::TRADE)), + mPartnerInventory(new Inventory(Inventory::TRADE)), + mStatus(PROPOSING), + mAutoAddItem(0), + mAutoAddToNick(""), + mGotMoney(0), + mAutoMoney(0) +{ + logger->log1("TradeWindow::TradeWindow nick"); + + setWindowName("Trade"); + setResizable(true); + setCloseButton(true); + setDefaultSize(386, 180, ImageRect::CENTER); + setMinWidth(386); + setMinHeight(180); + + if (setupWindow) + setupWindow->registerWindowForReset(this); + + std::string longestName = getFont()->getWidth(_("OK")) > + getFont()->getWidth(_("Trade")) ? + _("OK") : _("Trade"); + + mAddButton = new Button(_("Add"), "add", this); + mOkButton = new Button("", "", this); // Will be filled in later + + int width = std::max(mOkButton->getFont()->getWidth(CAPTION_PROPOSE), + mOkButton->getFont()->getWidth(CAPTION_CONFIRMED)); + width = std::max(width, mOkButton->getFont()->getWidth(CAPTION_ACCEPT)); + width = std::max(width, mOkButton->getFont()->getWidth(CAPTION_ACCEPTED)); + + mOkButton->setWidth(8 + width); + + mMyItemContainer = new ItemContainer(mMyInventory.get()); + mMyItemContainer->addSelectionListener(this); + + ScrollArea *myScroll = new ScrollArea(mMyItemContainer); + myScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mPartnerItemContainer = new ItemContainer(mPartnerInventory.get()); + mPartnerItemContainer->addSelectionListener(this); + + ScrollArea *partnerScroll = new ScrollArea(mPartnerItemContainer); + partnerScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mMoneyLabel = new Label(strprintf(_("You get %s"), "")); + gcn::Label *mMoneyLabel2 = new Label(_("You give:")); + + mMoneyField = new TextField; + mMoneyField->setWidth(40); + mMoneyChangeButton = new Button(_("Change"), "money", this); + + place(1, 0, mMoneyLabel); + place(0, 1, myScroll).setPadding(3); + place(1, 1, partnerScroll).setPadding(3); + ContainerPlacer place; + place = getPlacer(0, 0); + place(0, 0, mMoneyLabel2); + place(1, 0, mMoneyField, 2); + place(3, 0, mMoneyChangeButton).setHAlign(LayoutCell::LEFT); + place = getPlacer(0, 2); + place(0, 0, mAddButton); + place(1, 0, mOkButton); + Layout &layout = getLayout(); + layout.extend(0, 2, 2, 1); + layout.setRowHeight(1, Layout::AUTO_SET); + layout.setRowHeight(2, 0); + layout.setColWidth(0, Layout::AUTO_SET); + layout.setColWidth(1, Layout::AUTO_SET); + + loadWindowState(); + + reset(); +} + +TradeWindow::~TradeWindow() +{ +} + +void TradeWindow::setMoney(int amount) +{ + if (amount < mGotMoney) + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::WARNING)); + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT)); + + mGotMoney = amount; + mMoneyLabel->setCaption(strprintf(_("You get %s"), + Units::formatCurrency(amount).c_str())); + mMoneyLabel->adjustSize(); +} + +void TradeWindow::addItem(int id, bool own, int quantity, int refine) +{ + if (own) + mMyInventory->addItem(id, quantity, refine); + else + mPartnerInventory->addItem(id, quantity, refine); +} + +void TradeWindow::addItem(int id, bool own, int quantity, + int refine, bool equipment) +{ + if (own) + mMyInventory->addItem(id, quantity, refine, equipment); + else + mPartnerInventory->addItem(id, quantity, refine, equipment); +} + +void TradeWindow::changeQuantity(int index, bool own, int quantity) +{ + if (own) + { + if (mMyInventory->getItem(index)) + mMyInventory->getItem(index)->setQuantity(quantity); + } + else + { + if (mPartnerInventory->getItem(index)) + mPartnerInventory->getItem(index)->setQuantity(quantity); + } +} + +void TradeWindow::increaseQuantity(int index, bool own, int quantity) +{ + if (own) + { + if (mMyInventory->getItem(index)) + mMyInventory->getItem(index)->increaseQuantity(quantity); + } + else + { + if (mPartnerInventory->getItem(index)) + mPartnerInventory->getItem(index)->increaseQuantity(quantity); + } +} + +void TradeWindow::reset() +{ + mMyInventory->clear(); + mPartnerInventory->clear(); + mOkOther = false; + mOkMe = false; + setMoney(0); + mMoneyField->setEnabled(true); + mMoneyField->setText(""); + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT)); + mAddButton->setEnabled(true); + mMoneyChangeButton->setEnabled(true); + mGotMoney = 0; + setStatus(PREPARING); +} + +void TradeWindow::receivedOk(bool own) +{ + if (own) + mOkMe = true; + else + mOkOther = true; + + if (mOkMe && mOkOther) + { + //mOkMe = false; + //mOkOther = false; + setStatus(ACCEPTING); + } +} + +void TradeWindow::tradeItem(Item *item, int quantity) +{ + Net::getTradeHandler()->addItem(item, quantity); +} + +void TradeWindow::valueChanged(const gcn::SelectionEvent &event) +{ + if (!mMyItemContainer || !mPartnerItemContainer) + return; + + /* If an item is selected in one container, make sure no item is selected + * in the other container. + */ + if (event.getSource() == mMyItemContainer && + mMyItemContainer->getSelectedItem()) + { + mPartnerItemContainer->selectNone(); + } + else if (mPartnerItemContainer->getSelectedItem()) + { + mMyItemContainer->selectNone(); + } +} + +void TradeWindow::setStatus(Status s) +{ + if (s == mStatus) + return; + mStatus = s; + + switch (s) + { + case PREPARING: + mOkButton->setCaption(CAPTION_PROPOSE); + mOkButton->setActionEventId("ok"); + break; + case PROPOSING: + mOkButton->setCaption(CAPTION_CONFIRMED); + mOkButton->setActionEventId(""); + break; + case ACCEPTING: + mOkButton->setCaption(CAPTION_ACCEPT); + mOkButton->setActionEventId("trade"); + break; + case ACCEPTED: + mOkButton->setCaption(CAPTION_ACCEPTED); + mOkButton->setActionEventId(""); + break; + default: + break; + } + + mOkButton->setEnabled((s != PROPOSING && s != ACCEPTED)); +} + +void TradeWindow::action(const gcn::ActionEvent &event) +{ + if (!inventoryWindow) + return; + + Item *item = inventoryWindow->getSelectedItem(); + + if (event.getId() == "add") + { + if (mStatus != PREPARING) + return; + + if (!inventoryWindow->isVisible()) + { + inventoryWindow->setVisible(true); + return; + } + + if (!item) + return; + + if (mMyInventory->getFreeSlot() == -1) + return; + + if (mMyInventory->contains(item)) + { + if (localChatTab) + { + localChatTab->chatLog(_("Failed adding item. You can not " + "overlap one kind of item on the window."), BY_SERVER); + } + return; + } + + // Choose amount of items to trade + ItemAmountWindow::showWindow(ItemAmountWindow::TradeAdd, this, item); + + setStatus(PREPARING); + } + else if (event.getId() == "cancel") + { + setVisible(false); + reset(); + PlayerInfo::setTrading(false); + + Net::getTradeHandler()->cancel(); + } + else if (event.getId() == "ok") + { + mMoneyField->setEnabled(false); + mAddButton->setEnabled(false); + mMoneyChangeButton->setEnabled(false); + receivedOk(true); + setStatus(PROPOSING); + Net::getTradeHandler()->confirm(); + } + else if (event.getId() == "trade") + { + receivedOk(true); + setStatus(ACCEPTED); + Net::getTradeHandler()->finish(); + } + else if (event.getId() == "money") + { + if (mStatus != PREPARING) + return; + + int v = atoi(mMoneyField->getText().c_str()); + int curMoney = PlayerInfo::getAttribute(MONEY); + if (v > curMoney) + { + if (localChatTab) + { + localChatTab->chatLog(_("You don't have enough money."), + BY_SERVER); + } + v = curMoney; + } + Net::getTradeHandler()->setMoney(v); + mMoneyField->setText(strprintf("%d", v)); + } +} + +void TradeWindow::close() +{ + Net::getTradeHandler()->cancel(); + clear(); +} + +void TradeWindow::clear() +{ + mAutoAddItem = 0; + mAutoAddToNick = ""; + mAutoMoney = 0; + mAutoAddAmount = 0; + mGotMoney = 0; + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT)); +} + +void TradeWindow::addAutoItem(std::string nick, Item* item, int amount) +{ + mAutoAddToNick = nick; + mAutoAddItem = item; + mAutoAddAmount = amount; +} + +void TradeWindow::addAutoMoney(std::string nick, int money) +{ + mAutoAddToNick = nick; + mAutoMoney = money; +} + +void TradeWindow::initTrade(std::string nick) +{ + if (!player_node) + return; + + if (!mAutoAddToNick.empty() && mAutoAddToNick == nick) + { + if (mAutoAddItem && mAutoAddItem->getQuantity()) + { + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(mAutoAddItem->getId()); + if (item) + tradeItem(item, mAutoAddItem->getQuantity()); + } + } + if (mAutoMoney) + { + Net::getTradeHandler()->setMoney(mAutoMoney); + mMoneyField->setText(strprintf("%d", mAutoMoney)); + } + } + clear(); +} diff --git a/src/gui/trade.h b/src/gui/trade.h new file mode 100644 index 000000000..1baa14fa8 --- /dev/null +++ b/src/gui/trade.h @@ -0,0 +1,170 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRADE_H +#define TRADE_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/selectionlistener.hpp> + +#include <memory> + +class Inventory; +class Item; +class ItemContainer; +class ScrollArea; + +/** + * Trade dialog. + * + * \ingroup Interface + */ +class TradeWindow : public Window, gcn::ActionListener, gcn::SelectionListener +{ + public: + /** + * Constructor. + */ + TradeWindow(); + + /** + * Destructor. + */ + ~TradeWindow(); + + /** + * Displays expected money in the trade window. + */ + void setMoney(int quantity); + + /** + * Add an item to the trade window. + */ + void addItem(int id, bool own, int quantity, int refine); + + /** + * Reset both item containers + */ + void reset(); + + /** + * Add an item to the trade window. + */ + void addItem(int id, bool own, int quantity, + int refine, bool equipment); + + /** + * Change quantity of an item. + */ + void changeQuantity(int index, bool own, int quantity); + + /** + * Increase quantity of an item. + */ + void increaseQuantity(int index, bool own, int quantity); + + /** + * Player received ok message from server + */ + void receivedOk(bool own); + + /** + * Send trade packet. + */ + void tradeItem(Item *item, int quantity); + + /** + * Updates the labels and makes sure only one item is selected in + * either my inventory or partner inventory. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Closes the Trade Window, as well as telling the server that the + * window has been closed. + */ + void close(); + + /** + * Clear auto trade items. + */ + void clear(); + + /** + * Add item what will be added to trade. + */ + void addAutoItem(std::string nick, Item* item, int amount); + + void addAutoMoney(std::string nick, int money); + + void initTrade(std::string nick); + + std::string getAutoTradeNick() + { return mAutoAddToNick; } + + private: + enum Status + { + PREPARING = 0, /**< Players are adding items. (1) */ + PROPOSING, /**< Local player has confirmed the trade. (1) */ + ACCEPTING, /**< Accepting the trade. (2) */ + ACCEPTED /**< Local player has accepted the trade. */ + }; + + /** + * Sets the current status of the trade. + */ + void setStatus(Status s); + + typedef const std::auto_ptr<Inventory> InventoryPtr; + InventoryPtr mMyInventory; + InventoryPtr mPartnerInventory; + + ItemContainer *mMyItemContainer; + ItemContainer *mPartnerItemContainer; + + gcn::Label *mMoneyLabel; + gcn::Button *mAddButton; + gcn::Button *mOkButton; + gcn::Button *mMoneyChangeButton; + gcn::TextField *mMoneyField; + + Status mStatus; + bool mOkOther, mOkMe; + Item* mAutoAddItem; + std::string mAutoAddToNick; + int mGotMoney; + int mAutoMoney; + int mAutoAddAmount; +}; + +extern TradeWindow *tradeWindow; + +#endif diff --git a/src/gui/truetypefont.cpp b/src/gui/truetypefont.cpp new file mode 100644 index 000000000..8e77c1ded --- /dev/null +++ b/src/gui/truetypefont.cpp @@ -0,0 +1,336 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/truetypefont.h" + +#include "client.h" +#include "graphics.h" +#include "log.h" +#include "main.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +#include <guichan/exception.hpp> + +const unsigned int CACHE_SIZE = 256; +const unsigned int CACHE_SIZE_SMALL1 = 90; +const unsigned int CACHE_SIZE_SMALL2 = 150; + +char *strBuf; + +class TextChunk +{ + public: + TextChunk(const std::string &text, const gcn::Color &color) : + img(0), text(text), color(color) + { + } + + ~TextChunk() + { + delete img; + img = 0; + } + + bool operator==(const TextChunk &chunk) const + { + return (chunk.text == text && chunk.color == color); + } + + void generate(TTF_Font *font) + { + SDL_Color sdlCol; + sdlCol.b = static_cast<Uint8>(color.b); + sdlCol.r = static_cast<Uint8>(color.r); + sdlCol.g = static_cast<Uint8>(color.g); + + getSafeUtf8String(text, strBuf); + +// SDL_Surface *surface = TTF_RenderUTF8_Solid( + SDL_Surface *surface = TTF_RenderUTF8_Blended( + font, strBuf, sdlCol); + + if (!surface) + { + img = 0; + return; + } + + img = Image::load(surface); + + SDL_FreeSurface(surface); + } + + Image *img; + std::string text; + gcn::Color color; +}; + +typedef std::list<TextChunk>::iterator CacheIterator; + +static int fontCounter; + +TrueTypeFont::TrueTypeFont(const std::string &filename, int size, int style) : + mCreateCounter(0), + mDeleteCounter(0) +{ + ResourceManager *resman = ResourceManager::getInstance(); + + if (fontCounter == 0 && TTF_Init() == -1) + { + throw GCN_EXCEPTION("Unable to initialize SDL_ttf: " + + std::string(TTF_GetError())); + } + + if (!fontCounter) + { + strBuf = new char[65535]; + memset(strBuf, 65535, 0); + } + + ++fontCounter; + mFont = TTF_OpenFont(resman->getPath(filename).c_str(), size); + + if (!mFont) + { + logger->log("Error finding font " + filename); + mFont = TTF_OpenFont(resman->getPath( + "fonts/dejavusans.ttf").c_str(), size); + if (!mFont) + { + throw GCN_EXCEPTION("SDLTrueTypeFont::SDLTrueTypeFont: " + + std::string(TTF_GetError())); + } + } + + TTF_SetFontStyle(mFont, style); + mCleanTime = cur_time + 120; +} + +TrueTypeFont::~TrueTypeFont() +{ + TTF_CloseFont(mFont); + mFont = 0; + --fontCounter; + + if (fontCounter == 0) + { + TTF_Quit(); + delete []strBuf; + } +} + +void TrueTypeFont::loadFont(const std::string &filename, int size, int style) +{ + ResourceManager *resman = ResourceManager::getInstance(); + + if (fontCounter == 0 && TTF_Init() == -1) + { + logger->log("Unable to initialize SDL_ttf: " + + std::string(TTF_GetError())); + return; + } + + TTF_Font *font = TTF_OpenFont(resman->getPath(filename).c_str(), size); + + if (!font) + { + logger->log("SDLTrueTypeFont::SDLTrueTypeFont: " + + std::string(TTF_GetError())); + return; + } + + if (mFont) + TTF_CloseFont(mFont); + + mFont = font; + TTF_SetFontStyle(mFont, style); + clear(); +} + +void TrueTypeFont::clear() +{ + for (unsigned short f = 0; f < (unsigned short)CACHES_NUMBER; f ++) + mCache[static_cast<unsigned short>(f)].clear(); +} + +void TrueTypeFont::drawString(gcn::Graphics *graphics, + const std::string &text, + int x, int y) +{ + if (text.empty()) + return; + + Graphics *g = dynamic_cast<Graphics *>(graphics); + + gcn::Color col = g->getColor(); + const float alpha = static_cast<float>(col.a) / 255.0f; + + /* The alpha value is ignored at string generation so avoid caching the + * same text with different alpha values. + */ + col.a = 255; + + TextChunk chunk(text, col); + + unsigned char chr = text[0]; + std::list<TextChunk> *cache = &mCache[chr]; + + bool found = false; + +#ifdef DEBUG_FONT + int cnt = 0; +#endif + + for (CacheIterator i = cache->begin(); i != cache->end(); ++i) + { + if (chunk == (*i)) + { + // Raise priority: move it to front + cache->splice(cache->begin(), *cache, i); + found = true; + break; + } +#ifdef DEBUG_FONT + cnt ++; +#endif + } +#ifdef DEBUG_FONT + logger->log("drawString: " + text + ", iterations: " + toString(cnt)); +#endif + + // Surface not found + if (!found) + { + if (cache->size() >= CACHE_SIZE) + { +#ifdef DEBUG_FONT_COUNTERS + mDeleteCounter ++; +#endif + cache->pop_back(); + } +#ifdef DEBUG_FONT_COUNTERS + mCreateCounter ++; +#endif + cache->push_front(chunk); + cache->front().generate(mFont); + + if (!mCleanTime) + { + mCleanTime = cur_time + 120; + } + else if (mCleanTime < cur_time) + { + doClean(); + mCleanTime = cur_time + 120; + } + } + + if (cache->front().img) + { + cache->front().img->setAlpha(alpha); + g->drawImage(cache->front().img, x, y); + } + +} + +void TrueTypeFont::createTextChunk(TextChunk *chunk) +{ + if (!chunk || chunk->text.empty()) + return; + + const float alpha = static_cast<float>(chunk->color.a) / 255.0f; + chunk->color.a = 255; + chunk->generate(mFont); + if (chunk->img) + chunk->img->setAlpha(alpha); +} + +int TrueTypeFont::getWidth(const std::string &text) const +{ + if (text.empty()) + return 0; + + unsigned char chr = text[0]; + std::list<TextChunk> *cache = &mCache[chr]; + +#ifdef DEBUG_FONT + int cnt = 0; +#endif + + for (CacheIterator i = cache->begin(); i != cache->end(); i++) + { + if (i->text == text) + { + // Raise priority: move it to front + // Assumption is that TTF::draw will be called next + cache->splice(cache->begin(), *cache, i); + if (i->img) + return i->img->getWidth(); + else + return 0; + } +#ifdef DEBUG_FONT + cnt ++; +#endif + } + +#ifdef DEBUG_FONT + logger->log("getWidth: " + text + ", iterations: " + toString(cnt)); +#endif + + int w, h; + getSafeUtf8String(text, strBuf); + TTF_SizeUTF8(mFont, strBuf, &w, &h); + return w; +} + +int TrueTypeFont::getHeight() const +{ + return TTF_FontHeight(mFont); +} + +void TrueTypeFont::doClean() +{ + for (int f = 0; f < CACHES_NUMBER; f ++) + { + std::list<TextChunk> *cache = &mCache[f]; + if (cache->size() > CACHE_SIZE_SMALL2) + { +#ifdef DEBUG_FONT_COUNTERS + mDeleteCounter += 10; +#endif + for (int d = 0; d < 10; d ++) + cache->pop_back(); + } + else if (cache->size() > CACHE_SIZE_SMALL1) + { +#ifdef DEBUG_FONT_COUNTERS + mDeleteCounter ++; +#endif + cache->pop_back(); + } + } +}
\ No newline at end of file diff --git a/src/gui/truetypefont.h b/src/gui/truetypefont.h new file mode 100644 index 000000000..7a28c3b97 --- /dev/null +++ b/src/gui/truetypefont.h @@ -0,0 +1,104 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRUETYPEFONT_H +#define TRUETYPEFONT_H + +#include <guichan/font.hpp> + +#ifdef __APPLE__ +#include <SDL_ttf/SDL_ttf.h> +#else +#ifdef __WIN32__ +#include <SDL/SDL_ttf.h> +#else +#include <SDL_ttf.h> +#endif +#endif + +#include <list> +#include <string> + +#define CACHES_NUMBER 256 + +class TextChunk; + +/** + * A wrapper around SDL_ttf for allowing the use of TrueType fonts. + * + * <b>NOTE:</b> This class initializes SDL_ttf as necessary. + */ +class TrueTypeFont : public gcn::Font +{ + public: + /** + * Constructor. + * + * @param filename Font filename. + * @param size Font size. + */ + TrueTypeFont(const std::string &filename, int size, int style = 0); + + /** + * Destructor. + */ + ~TrueTypeFont(); + + void loadFont(const std::string &filename, int size, int style = 0); + + void createTextChunk(TextChunk *chunk); + + virtual int getWidth(const std::string &text) const; + + virtual int getHeight() const; + + std::list<TextChunk> *getCache() + { return mCache; } + + /** + * @see Font::drawString + */ + void drawString(gcn::Graphics *graphics, + const std::string &text, + int x, int y); + + void clear(); + + void doClean(); + + int getCreateCounter() const + { return mCreateCounter; } + + int getDeleteCounter() const + { return mDeleteCounter; } + + private: + TTF_Font *mFont; + unsigned mCreateCounter; + unsigned mDeleteCounter; + + // Word surfaces cache + mutable std::list<TextChunk> mCache[CACHES_NUMBER]; + int mCleanTime; +}; + +#endif diff --git a/src/gui/unregisterdialog.cpp b/src/gui/unregisterdialog.cpp new file mode 100644 index 000000000..a226f0d84 --- /dev/null +++ b/src/gui/unregisterdialog.cpp @@ -0,0 +1,145 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/unregisterdialog.h" + +#include "client.h" +#include "log.h" + +#include "gui/okdialog.h" +#include "gui/register.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <string> +#include <sstream> + +UnRegisterDialog::UnRegisterDialog(LoginData *loginData): + Window(_("Unregister"), true), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + gcn::Label *userLabel = new Label(strprintf(_("Name: %s"), mLoginData-> + username.c_str())); + gcn::Label *passwordLabel = new Label(_("Password:")); + mPasswordField = new PasswordField(mLoginData->password); + mUnRegisterButton = new Button(_("Unregister"), "unregister", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + const int width = 210; + const int height = 80; + setContentSize(width, height); + + userLabel->setPosition(5, 5); + userLabel->setWidth(width - 5); + mPasswordField->setPosition( + 68, userLabel->getY() + userLabel->getHeight() + 7); + mPasswordField->setWidth(130); + + passwordLabel->setPosition(5, mPasswordField->getY() + 1); + + mCancelButton->setPosition( + width - 5 - mCancelButton->getWidth(), + height - 5 - mCancelButton->getHeight()); + mUnRegisterButton->setPosition( + mCancelButton->getX() - 5 - mUnRegisterButton->getWidth(), + mCancelButton->getY()); + + add(userLabel); + add(passwordLabel); + add(mPasswordField); + add(mUnRegisterButton); + add(mCancelButton); + + center(); + setVisible(true); + mPasswordField->requestFocus(); + mPasswordField->setActionEventId("cancel"); +} + +UnRegisterDialog::~UnRegisterDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void UnRegisterDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_CHAR_SELECT); + } + else if (event.getId() == "unregister") + { + const std::string username = mLoginData->username.c_str(); + const std::string password = mPasswordField->getText(); + logger->log("UnregisterDialog::unregistered, Username is %s", + username.c_str()); + + std::stringstream errorMessage; + bool error = false; + + unsigned int min = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int max = Net::getLoginHandler()->getMaxPasswordLength(); + + // Check password + if (password.length() < min) + { + // Pass too short + errorMessage << strprintf(_("The password needs to be at least %d " + "characters long."), min); + error = true; + } + else if (password.length() > max - 1) + { + // Pass too long + errorMessage << strprintf(_("The password needs to be less than " + "%d characters long."), max); + error = true; + } + + if (error) + { + mWrongDataNoticeListener->setTarget(this->mPasswordField); + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str()); + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, unregister the new user. + mUnRegisterButton->setEnabled(false); + mLoginData->password = password; + Client::setState(STATE_UNREGISTER_ATTEMPT); + } + } +} diff --git a/src/gui/unregisterdialog.h b/src/gui/unregisterdialog.h new file mode 100644 index 000000000..dd330afdc --- /dev/null +++ b/src/gui/unregisterdialog.h @@ -0,0 +1,68 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UNREGISTERDIALOG_H +#define UNREGISTERDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> + +class LoginData; +class OkDialog; +class WrongDataNoticeListener; + +/** + * The Unregister dialog. + * + * \ingroup Interface + */ +class UnRegisterDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + UnRegisterDialog(LoginData *loginData); + + ~UnRegisterDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + gcn::TextField *mPasswordField; + + gcn::Button *mUnRegisterButton; + gcn::Button *mCancelButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif diff --git a/src/gui/updatewindow.cpp b/src/gui/updatewindow.cpp new file mode 100644 index 000000000..e199d7a72 --- /dev/null +++ b/src/gui/updatewindow.cpp @@ -0,0 +1,672 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/updatewindow.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" +#include "main.h" + +#include "gui/sdlinput.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" + +#include "net/download.h" +#include "net/logindata.h" + +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include <iostream> +#include <fstream> + +#include <sys/stat.h> + +const std::string xmlUpdateFile = "resources.xml"; +const std::string txtUpdateFile = "resources2.txt"; + +std::vector<updateFile> loadXMLFile(const std::string &fileName); +std::vector<updateFile> loadTxtFile(const std::string &fileName); + +/** + * Load the given file into a vector of updateFiles. + */ +std::vector<updateFile> loadXMLFile(const std::string &fileName) +{ + std::vector<updateFile> files; + XML::Document doc(fileName, false); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "updates")) + { + logger->log("Error loading update file: %s", fileName.c_str()); + return files; + } + + for_each_xml_child_node(fileNode, rootNode) + { + // Ignore all tags except for the "update" tags + if (!xmlStrEqual(fileNode->name, BAD_CAST "update")) + continue; + + updateFile file; + file.name = XML::getProperty(fileNode, "file", ""); + file.hash = XML::getProperty(fileNode, "hash", ""); + file.type = XML::getProperty(fileNode, "type", "data"); + file.desc = XML::getProperty(fileNode, "description", ""); + if (XML::getProperty(fileNode, "required", "yes") == "yes") + file.required = true; + else + file.required = false; + + files.push_back(file); + } + + return files; +} + +std::vector<updateFile> loadTxtFile(const std::string &fileName) +{ + std::vector<updateFile> files; + std::ifstream fileHandler; + fileHandler.open(fileName.c_str(), std::ios::in); + + if (fileHandler.is_open()) + { + while (fileHandler.good()) + { + char name[256], hash[50]; + fileHandler.getline(name, 256, ' '); + fileHandler.getline(hash, 50); + + updateFile thisFile; + thisFile.name = name; + thisFile.hash = hash; + thisFile.type = "data"; + thisFile.required = true; + thisFile.desc = ""; + + files.push_back(thisFile); + } + } + else + { + logger->log("Error loading update file: %s", fileName.c_str()); + } + fileHandler.close(); + + return files; +} + +UpdaterWindow::UpdaterWindow(const std::string &updateHost, + const std::string &updatesDir, + bool applyUpdates, + int updateType): + Window(_("Updating...")), + mDownloadStatus(UPDATE_NEWS), + mUpdateHost(updateHost), + mUpdatesDir(updatesDir), + mCurrentFile("news.txt"), + mDownloadProgress(0.0f), + mCurrentChecksum(0), + mStoreInMemory(true), + mDownloadComplete(true), + mUserCancel(false), + mDownloadedBytes(0), + mMemoryBuffer(NULL), + mDownload(NULL), + mUpdateIndex(0), + mLoadUpdates(applyUpdates), + mUpdateType(updateType) +{ + mBrowserBox = new BrowserBox; + mScrollArea = new ScrollArea(mBrowserBox); + mLabel = new Label(_("Connecting...")); + mProgressBar = new ProgressBar(0.0, 310, 20); + mCancelButton = new Button(_("Cancel"), "cancel", this); + mPlayButton = new Button(_("Play"), "play", this); + + mProgressBar->setSmoothProgress(false); + mBrowserBox->setOpaque(false); + mPlayButton->setEnabled(false); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mScrollArea, 5, 3).setPadding(3); + place(0, 3, mLabel, 5); + place(0, 4, mProgressBar, 5); + place(3, 5, mCancelButton); + place(4, 5, mPlayButton); + + reflowLayout(450, 400); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + addKeyListener(this); + + center(); + setVisible(true); + mCancelButton->requestFocus(); + + // Try to download the updates list + download(); +} + +UpdaterWindow::~UpdaterWindow() +{ + if (mLoadUpdates) + loadUpdates(); + + if (mDownload) + { + mDownload->cancel(); + + delete mDownload; + mDownload = 0; + } + free(mMemoryBuffer); +} + +void UpdaterWindow::setProgress(float p) +{ + // Do delayed progress bar update, since Guichan isn't thread-safe + MutexLocker lock(&mDownloadMutex); + mDownloadProgress = p; +} + +void UpdaterWindow::setLabel(const std::string &str) +{ + // Do delayed label text update, since Guichan isn't thread-safe + MutexLocker lock(&mDownloadMutex); + mNewLabelCaption = str; +} + +void UpdaterWindow::enable() +{ + mCancelButton->setEnabled(false); + mPlayButton->setEnabled(true); + mPlayButton->requestFocus(); + + if (mUpdateType & LoginData::Upd_Close) + Client::setState(STATE_LOAD_DATA); +} + +void UpdaterWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + // Register the user cancel + mUserCancel = true; + // Skip the updating process + if (mDownloadStatus != UPDATE_COMPLETE) + { + mDownload->cancel(); + mDownloadStatus = UPDATE_ERROR; + } + } + else if (event.getId() == "play") + { + Client::setState(STATE_LOAD_DATA); + } +} + +void UpdaterWindow::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + { + action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId())); + Client::setState(STATE_WORLD_SELECT); + } + else if (key.getValue() == Key::ENTER) + { + if (mDownloadStatus == UPDATE_COMPLETE || + mDownloadStatus == UPDATE_ERROR) + { + action(gcn::ActionEvent(NULL, mPlayButton->getActionEventId())); + } + else + { + action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId())); + } + } +} + +void UpdaterWindow::loadNews() +{ + if (!mMemoryBuffer) + { + logger->log1("Couldn't load news"); + return; + } + + // Reallocate and include terminating 0 character + mMemoryBuffer = static_cast<char*>(realloc( + mMemoryBuffer, mDownloadedBytes + 1)); + + mMemoryBuffer[mDownloadedBytes] = '\0'; + + mBrowserBox->clearRows(); + + // Tokenize and add each line separately + char *line = strtok(mMemoryBuffer, "\n"); + while (line) + { + mBrowserBox->addRow(line); + line = strtok(NULL, "\n"); + } + + // Free the memory buffer now that we don't need it anymore + free(mMemoryBuffer); + mMemoryBuffer = NULL; + mDownloadedBytes = 0; + + mScrollArea->setVerticalScrollAmount(0); +} + +void UpdaterWindow::loadPatch() +{ + if (!mMemoryBuffer) + { + logger->log1("Couldn't load patch"); + return; + } + + // Reallocate and include terminating 0 character + mMemoryBuffer = static_cast<char*>( + realloc(mMemoryBuffer, mDownloadedBytes + 1)); + mMemoryBuffer[mDownloadedBytes] = '\0'; + + std::string version; + + // Tokenize and add each line separately + char *line = strtok(mMemoryBuffer, "\n"); + if (line) + { + version = line; + if (version > CHECK_VERSION) + { + mBrowserBox->addRow("", true); + mBrowserBox->addRow(" ##1http://tmw.cetki.com/4144/", true); + mBrowserBox->addRow("##1You can download it from", true); + mBrowserBox->addRow("##1ManaPlus updated.", true); + } + else + { + mBrowserBox->addRow("You have latest client version.", true); + } + } + + // Free the memory buffer now that we don't need it anymore + free(mMemoryBuffer); + mMemoryBuffer = NULL; + mDownloadedBytes = 0; + + mScrollArea->setVerticalScrollAmount(0); +} + +int UpdaterWindow::updateProgress(void *ptr, DownloadStatus status, + size_t dt, size_t dn) +{ + UpdaterWindow *uw = reinterpret_cast<UpdaterWindow *>(ptr); + if (!uw) + return -1; + + if (status == DOWNLOAD_STATUS_COMPLETE) + { + uw->mDownloadComplete = true; + } + else if (status == DOWNLOAD_STATUS_ERROR || + status == DOWNLOAD_STATUS_CANCELLED) + { + uw->mDownloadStatus = UPDATE_ERROR; + } + + if (!dt) + dt = 1; + + float progress = static_cast<float>(dn) / + static_cast<float>(dt); + + if (progress != progress) + progress = 0.0f; // check for NaN + if (progress < 0.0f) + progress = 0.0f; // no idea how this could ever happen, + // but why not check for it anyway. + if (progress > 1.0f) + progress = 1.0f; + + uw->setLabel(uw->mCurrentFile + " (" + + toString(static_cast<int>(progress * 100)) + "%)"); + + uw->setProgress(progress); + + if (Client::getState() != STATE_UPDATE + || uw->mDownloadStatus == UPDATE_ERROR) + { + // If the action was canceled return an error code to stop the mThread + return -1; + } + + return 0; +} + +size_t UpdaterWindow::memoryWrite(void *ptr, size_t size, + size_t nmemb, void *stream) +{ + UpdaterWindow *uw = reinterpret_cast<UpdaterWindow *>(stream); + size_t totalMem = size * nmemb; + uw->mMemoryBuffer = static_cast<char*>(realloc(uw->mMemoryBuffer, + uw->mDownloadedBytes + totalMem)); + if (uw->mMemoryBuffer) + { + memcpy(&(uw->mMemoryBuffer[uw->mDownloadedBytes]), ptr, totalMem); + uw->mDownloadedBytes += static_cast<int>(totalMem); + } + + return totalMem; +} + +void UpdaterWindow::download() +{ + if (mDownload) + { + mDownload->cancel(); + delete mDownload; + } + if (mDownloadStatus == UPDATE_PATCH) + { + mDownload = new Net::Download(this, "http://tmw.cetki.com/update/" + + mCurrentFile, updateProgress); + } + else + { + mDownload = new Net::Download(this, mUpdateHost + "/" + mCurrentFile, + updateProgress); + } + + if (mStoreInMemory) + { + mDownload->setWriteFunction(UpdaterWindow::memoryWrite); + } + else + { + if (mDownloadStatus == UPDATE_RESOURCES) + { + mDownload->setFile(mUpdatesDir + "/" + mCurrentFile, + mCurrentChecksum); + } + else + { + mDownload->setFile(mUpdatesDir + "/" + mCurrentFile); + } + } + + if (mDownloadStatus != UPDATE_RESOURCES) + mDownload->noCache(); + + setLabel(mCurrentFile + " (0%)"); + mDownloadComplete = false; + + // TODO: check return + mDownload->start(); +} + +void UpdaterWindow::loadUpdates() +{ + ResourceManager *resman = ResourceManager::getInstance(); + + if (mUpdateFiles.empty()) + { // updates not downloaded + mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); + if (!mUpdateFiles.size()) + { + logger->log("Warning this server does not have a" + " %s file falling back to %s", xmlUpdateFile.c_str(), + txtUpdateFile.c_str()); + mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); + } + } + + std::string fixPath = mUpdatesDir + "/fix"; + for (mUpdateIndex = 0; mUpdateIndex < mUpdateFiles.size(); mUpdateIndex++) + { + UpdaterWindow::addUpdateFile(resman, mUpdatesDir, fixPath, + mUpdateFiles[mUpdateIndex].name, false); + } +} + +void UpdaterWindow::loadLocalUpdates(std::string dir) +{ + ResourceManager *resman = ResourceManager::getInstance(); + + std::vector<updateFile> updateFiles + = loadXMLFile(dir + "/" + xmlUpdateFile); + + if (updateFiles.empty()) + { + logger->log("Warning this server does not have a" + " %s file falling back to %s", xmlUpdateFile.c_str(), + txtUpdateFile.c_str()); + updateFiles = loadTxtFile(dir + "/" + txtUpdateFile); + } + + std::string fixPath = dir + "/fix"; + for (unsigned int updateIndex = 0; + updateIndex < updateFiles.size(); updateIndex++) + { + UpdaterWindow::addUpdateFile(resman, dir, fixPath, + updateFiles[updateIndex].name, false); + } +} + +void UpdaterWindow::addUpdateFile(ResourceManager *resman, std::string path, + std::string fixPath, std::string file, + bool append) +{ + if (!append) + resman->addToSearchPath(path + "/" + file, append); + + const std::string fixFile = fixPath + "/" + file; + struct stat statbuf; + if (!stat(fixFile.c_str(), &statbuf)) + resman->addToSearchPath(fixFile, append); + + if (append) + resman->addToSearchPath(path + "/" + file, append); +} + +void UpdaterWindow::logic() +{ + // Update Scroll logic + mScrollArea->logic(); + + // Synchronize label caption when necessary + { + MutexLocker lock(&mDownloadMutex); + + if (mLabel->getCaption() != mNewLabelCaption) + { + mLabel->setCaption(mNewLabelCaption); + mLabel->adjustSize(); + } + + mProgressBar->setProgress(mDownloadProgress); + } + + std::string filename = mUpdatesDir + "/" + mCurrentFile; + + switch (mDownloadStatus) + { + case UPDATE_ERROR: + // TODO: Only send complete sentences to gettext + mBrowserBox->addRow(""); + mBrowserBox->addRow(_("##1 The update process is incomplete.")); + // TRANSLATORS: Continues "you try again later.". + mBrowserBox->addRow(_("##1 It is strongly recommended that")); + // TRANSLATORS: Begins "It is strongly recommended that". + mBrowserBox->addRow(_("##1 you try again later.")); + + mBrowserBox->addRow(mDownload->getError()); + mScrollArea->setVerticalScrollAmount( + mScrollArea->getVerticalMaxScroll()); + mDownloadStatus = UPDATE_COMPLETE; + break; + case UPDATE_NEWS: + if (mDownloadComplete) + { + // Parse current memory buffer as news and dispose of the data + loadNews(); + + mCurrentFile = xmlUpdateFile; + mStoreInMemory = false; + mDownloadStatus = UPDATE_LIST; + download(); // download() changes mDownloadComplete to false + } + break; + case UPDATE_PATCH: + if (mDownloadComplete) + { + // Parse current memory buffer as news and dispose of the data + loadPatch(); + +/* + mCurrentFile = "news.txt"; + mStoreInMemory = true; + mDownloadStatus = UPDATE_NEWS; + download(); // download() changes mDownloadComplete to false +*/ + mDownloadStatus = UPDATE_COMPLETE; + } + break; + + case UPDATE_LIST: + if (mDownloadComplete) + { + if (mCurrentFile == xmlUpdateFile) + { + mUpdateFiles = loadXMLFile( + mUpdatesDir + "/" + xmlUpdateFile); + + if (mUpdateFiles.size() == 0) + { + logger->log("Warning this server does not have a %s" + " file falling back to %s", + xmlUpdateFile.c_str(), + txtUpdateFile.c_str()); + + // If the resources.xml file fails, + // fall back onto a older version + mCurrentFile = txtUpdateFile; + mStoreInMemory = false; + mDownloadStatus = UPDATE_LIST; + download(); + break; + } + } + else if (mCurrentFile == txtUpdateFile) + { + mUpdateFiles = loadTxtFile( + mUpdatesDir + "/" + txtUpdateFile); + } + mStoreInMemory = false; + mDownloadStatus = UPDATE_RESOURCES; + } + break; + case UPDATE_RESOURCES: + if (mDownloadComplete) + { + if (mUpdateIndex < mUpdateFiles.size()) + { + updateFile thisFile = mUpdateFiles[mUpdateIndex]; + if (!thisFile.required) + { + // This statement checks to see if the file type + // is music, and if download-music is true + // If it fails, this statement returns true, + // and results in not downloading the file + // Else it will ignore the break, + // and download the file. + + if (!(thisFile.type == "music" + && config.getBoolValue("download-music"))) + { + mUpdateIndex++; + break; + } + } + mCurrentFile = thisFile.name; + std::string checksum; + checksum = thisFile.hash; + std::stringstream ss(checksum); + ss >> std::hex >> mCurrentChecksum; + + std::ifstream temp( + (mUpdatesDir + "/" + mCurrentFile).c_str()); + + if (!temp.is_open()) + { + temp.close(); + download(); + } + else + { + temp.close(); + logger->log("%s already here", mCurrentFile.c_str()); + } + mUpdateIndex++; + } + else + { + // Download of updates completed +// mDownloadStatus = UPDATE_COMPLETE; + mCurrentFile = "latest.txt"; + mStoreInMemory = true; + mDownloadStatus = UPDATE_PATCH; + download(); // download() changes + // mDownloadComplete to false + } + } + break; + case UPDATE_COMPLETE: + enable(); + setLabel(_("Completed")); + break; + case UPDATE_IDLE: + break; + default: + logger->log("UpdaterWindow::logic unknown status: " + + toString(static_cast<unsigned>(mDownloadStatus))); + break; + } +} diff --git a/src/gui/updatewindow.h b/src/gui/updatewindow.h new file mode 100644 index 000000000..841af23a5 --- /dev/null +++ b/src/gui/updatewindow.h @@ -0,0 +1,210 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _UPDATERWINDOW_H +#define _UPDATERWINDOW_H + +#include "gui/widgets/window.h" + +#include "net/download.h" + +#include "utils/mutex.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> + +#include <string> +#include <vector> + +class BrowserBox; +class Button; +class ProgressBar; +class ResourceManager; +class ScrollArea; + +struct updateFile +{ + public: + std::string name; + std::string hash; + std::string type; + bool required; + std::string desc; +}; + +/** + * Update progress window GUI + * + * \ingroup GUI + */ +class UpdaterWindow : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor. + * + * @param updateHost Host where to get the updated files. + * @param updatesDir Directory where to store updates (should be absolute + * and already created). + * @param applyUpdates If true, the update window will pass the updates to teh + * resource manager + */ + UpdaterWindow(const std::string &updateHost, + const std::string &updatesDir, + bool applyUpdates, int updateType); + + /** + * Destructor + */ + ~UpdaterWindow(); + + /** + * Set's progress bar status + */ + void setProgress(float p); + + /** + * Set's label above progress + */ + void setLabel(const std::string &); + + /** + * Enables play button + */ + void enable(); + + /** + * Loads and display news. Assumes the news file contents have been loaded + * into the memory buffer. + */ + void loadNews(); + + void loadPatch(); + + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + void logic(); + + static void loadLocalUpdates(std::string dir); + + static void addUpdateFile(ResourceManager *resman, std::string path, + std::string fixPath, std::string file, + bool append); + + int updateState; + +private: + void download(); + + /** + * Loads the updates this window has gotten into the resource manager + */ + void loadUpdates(); + + + /** + * A download callback for progress updates. + */ + static int updateProgress(void *ptr, DownloadStatus status, + size_t dt, size_t dn); + + /** + * A libcurl callback for writing to memory. + */ + static size_t memoryWrite(void *ptr, size_t size, size_t nmemb, + void *stream); + + enum UpdateDownloadStatus + { + UPDATE_ERROR = 0, + UPDATE_IDLE, + UPDATE_LIST, + UPDATE_COMPLETE, + UPDATE_NEWS, + UPDATE_RESOURCES, + UPDATE_PATCH + }; + + /** Status of the current download. */ + UpdateDownloadStatus mDownloadStatus; + + /** Host where we get the updated files. */ + std::string mUpdateHost; + + /** Place where the updates are stored (absolute path). */ + std::string mUpdatesDir; + + /** The file currently downloading. */ + std::string mCurrentFile; + + /** The new label caption to be set in the logic method. */ + std::string mNewLabelCaption; + + /** The new progress value to be set in the logic method. */ + float mDownloadProgress; + + /** The mutex used to guard access to mNewLabelCaption and mDownloadProgress. */ + Mutex mDownloadMutex; + + /** The Adler32 checksum of the file currently downloading. */ + unsigned long mCurrentChecksum; + + /** A flag to indicate whether to use a memory buffer or a regular file. */ + bool mStoreInMemory; + + /** Flag that show if current download is complete. */ + bool mDownloadComplete; + + /** Flag that show if the user has canceled the update. */ + bool mUserCancel; + + /** Byte count currently downloaded in mMemoryBuffer. */ + int mDownloadedBytes; + + /** Buffer for files downloaded to memory. */ + char *mMemoryBuffer; + + /** Download handle. */ + Net::Download *mDownload; + + /** List of files to download. */ + std::vector<updateFile> mUpdateFiles; + + /** Index of the file to be downloaded. */ + unsigned int mUpdateIndex; + + /** Tells ~UpdaterWindow() if it should load updates */ + bool mLoadUpdates; + + int mUpdateType; + + gcn::Label *mLabel; /**< Progress bar caption. */ + Button *mCancelButton; /**< Button to stop the update process. */ + Button *mPlayButton; /**< Button to start playing. */ + ProgressBar *mProgressBar; /**< Update progress bar. */ + BrowserBox *mBrowserBox; /**< Box to display news. */ + ScrollArea *mScrollArea; /**< Used to scroll news box. */ +}; + +#endif diff --git a/src/gui/userpalette.cpp b/src/gui/userpalette.cpp new file mode 100644 index 000000000..03e5c1eed --- /dev/null +++ b/src/gui/userpalette.cpp @@ -0,0 +1,292 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net> + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "userpalette.h" + +#include "configuration.h" +#include "client.h" +#include "log.h" + +#include "gui/gui.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <math.h> + +const std::string ColorTypeNames[] = +{ + "ColorBeing", + "ColorFriend", + "ColorDisregarded", + "ColorIgnored", + "ColorErased", + "ColorPlayer", + "ColorSelf", + "ColorGM", + "ColorNPC", + "ColorMonster", + "ColorMonsterHp", + "ColorMonsterHp2", + "ColorParty", + "ColorGuild", + "ColorParticle", + "ColorPickupInfo", + "ColorExpInfo", + "ColorHitPlayerMonster", + "ColorHitMonsterPlayer", + "ColorHitPlayerPlayer", + "ColorHitCritical", + "ColorHitLocalPlayerMonster", + "ColorHitLocalPlayerCritical", + "ColorHitLocalPlayerMiss", + "ColorMiss", + "ColorPortalHighlight", + "ColorCollisionHighlight", + "ColorWalkableTileHighlight", + "ColorAttackRange", + "ColorAttackRangeBorder", + "ColorMonsterAttackRange", + "ColorHomePlace", + "ColorHomePlaceBorder", + "ColorRoadPoint" +}; + +std::string UserPalette::getConfigName(const std::string &typeName) +{ + std::string res = "Color" + typeName; + + int pos = 5; + for (size_t i = 0; i < typeName.length(); i++) + { + if (i == 0 || typeName[i] == '_') + { + if (i > 0) + i++; + + res[pos] = typeName[i]; + } + else + { + res[pos] = static_cast<char>(tolower(typeName[i])); + } + pos++; + } + res.erase(pos, res.length() - pos); + + return res; +} + +UserPalette::UserPalette(): + Palette(USER_COLOR_LAST) +{ + mColors[BEING] = ColorElem(); + mColors[PC] = ColorElem(); + mColors[SELF] = ColorElem(); + mColors[GM] = ColorElem(); + mColors[NPC] = ColorElem(); + mColors[MONSTER] = ColorElem(); + + addColor(BEING, 0xffffff, STATIC, _("Being")); + addColor(FRIEND, 0xb0ffb0, STATIC, _("Friend Names")); + addColor(DISREGARDED, 0xa00000, STATIC, _("Disregarded Names")); + addColor(IGNORED, 0xff0000, STATIC, _("Ignored Names")); + addColor(ERASED, 0xff0000, STATIC, _("Erased Names")); + addColor(PC, 0xffffff, STATIC, _("Other Players' Names")); + addColor(SELF, 0xff8040, STATIC, _("Own Name")); + addColor(GM, 0x00ff00, STATIC, _("GM Names")); + addColor(NPC, 0xc8c8ff, STATIC, _("NPCs")); + addColor(MONSTER, 0xff4040, STATIC, _("Monsters")); + addColor(MONSTER_HP, 0x00ff00, STATIC, _("Monster HP bar"), 50); + addColor(MONSTER_HP2, 0xff0000, STATIC, + _("Monster HP bar (second color)"), 50); + addColor(PARTY, 0xff00d8, STATIC, _("Party Members")); + addColor(GUILD, 0xff00d8, STATIC, _("Guild Members")); + addColor(PARTICLE, 0xffffff, STATIC, _("Particle Effects")); + addColor(PICKUP_INFO, 0x28dc28, STATIC, _("Pickup Notification")); + addColor(EXP_INFO, 0xffff00, STATIC, _("Exp Notification")); + addColor(HIT_PLAYER_MONSTER, 0x0064ff, STATIC, _("Player Hits Monster")); + addColor(HIT_MONSTER_PLAYER, 0xff3232, STATIC, _("Monster Hits Player")); + addColor(HIT_PLAYER_PLAYER, 0xff5050, STATIC, + _("Other Player Hits Local Player")); + addColor(HIT_CRITICAL, 0xff0000, RAINBOW, _("Critical Hit")); + addColor(HIT_LOCAL_PLAYER_MONSTER, 0x00ff00, STATIC, + _("Local Player Hits Monster")); + addColor(HIT_LOCAL_PLAYER_CRITICAL, 0xff0000, RAINBOW, + _("Local Player Critical Hit")); + addColor(HIT_LOCAL_PLAYER_MISS, 0x00ffa6, STATIC, + _("Local Player Miss")); + addColor(MISS, 0xffff00, STATIC, _("Misses")); + addColor(PORTAL_HIGHLIGHT, 0xC80000, STATIC, _("Portal Highlight")); + addColor(COLLISION_HIGHLIGHT, 0x0000C8, STATIC, + _("Collision Highlight"), 64); + addColor(WALKABLE_HIGHLIGHT, 0x00D000, STATIC, + _("Walkable Highlight"), 255); + addColor(ATTACK_RANGE, 0xffffff, STATIC, + _("Local Player Attack Range"), 5); + addColor(ATTACK_RANGE_BORDER, 0x0, STATIC, + _("Local Player Attack Range Border"), 76); + addColor(MONSTER_ATTACK_RANGE, 0xff0000, STATIC, + _("Monster Attack Range"), 20); + addColor(HOME_PLACE, 0xffffff, STATIC, + _("Home Place"), 20); + addColor(HOME_PLACE_BORDER, 0xffff00, STATIC, + _("Home Place Border"), 200); + addColor(ROAD_POINT, 0x000000, STATIC, + _("Road Point"), 100); + commit(true); +} + +UserPalette::~UserPalette() +{ + for (Colors::iterator col = mColors.begin(), + colEnd = mColors.end(); col != colEnd; ++col) + { + const std::string &configName = ColorTypeNames[col->type]; + config.setValue(configName + "Gradient", + static_cast<int>(col->committedGrad)); + config.setValue(configName + "Delay", col->delay); + + if (col->grad == STATIC || col->grad == PULSE) + { + char buffer[20]; + sprintf(buffer, "0x%06x", col->getRGB()); + config.setValue(configName, std::string(buffer)); + } + } +} + +void UserPalette::setColor(int type, int r, int g, int b) +{ + mColors[type].color.r = r; + mColors[type].color.g = g; + mColors[type].color.b = b; +} + +void UserPalette::setGradient(int type, GradientType grad) +{ + ColorElem *elem = &mColors[type]; + if (!elem) + return; + + if (elem->grad != STATIC && grad == STATIC) + { + for (size_t i = 0; i < mGradVector.size(); i++) + { + if (mGradVector[i] == elem) + { + mGradVector.erase(mGradVector.begin() + i); + break; + } + } + } + else if (elem->grad == STATIC && grad != STATIC) + { + mGradVector.push_back(elem); + } + + if (elem->grad != grad) + elem->grad = grad; +} + +std::string UserPalette::getElementAt(int i) +{ + if (i < 0 || i >= getNumberOfElements()) + return ""; + + return mColors[i].text; +} + +void UserPalette::commit(bool commitNonStatic) +{ + for (Colors::iterator i = mColors.begin(), iEnd = mColors.end(); + i != iEnd; ++i) + { + i->committedGrad = i->grad; + i->committedDelay = i->delay; + if (commitNonStatic || i->grad == STATIC) + i->committedColor = i->color; + else if (i->grad == PULSE) + i->committedColor = i->testColor; + } +} + +void UserPalette::rollback() +{ + for (Colors::iterator i = mColors.begin(), iEnd = mColors.end(); + i != iEnd; ++i) + { + if (i->grad != i->committedGrad) + setGradient(i->type, i->committedGrad); + + setGradientDelay(i->type, i->committedDelay); + setColor(i->type, i->committedColor.r, + i->committedColor.g, i->committedColor.b); + + if (i->grad == PULSE) + { + i->testColor.r = i->committedColor.r; + i->testColor.g = i->committedColor.g; + i->testColor.b = i->committedColor.b; + } + } +} + +int UserPalette::getColorTypeAt(int i) +{ + if (i < 0 || i >= getNumberOfElements()) + return BEING; + + return mColors[i].type; +} + +void UserPalette::addColor(unsigned type, unsigned rgb, + Palette::GradientType grad, const std::string &text, + int delay) +{ + const unsigned maxType = sizeof(ColorTypeNames) + / sizeof(ColorTypeNames[0]); + + if (type >= maxType) + return; + + const std::string &configName = ColorTypeNames[type]; + char buffer[20]; + sprintf(buffer, "0x%06x", rgb); + + const std::string rgbString = config.getValue(configName, + std::string(buffer)); + unsigned int rgbValue = 0; + if (rgbString.length() == 8 && rgbString[0] == '0' && rgbString[1] == 'x') + rgbValue = atox(rgbString); + else + rgbValue = atoi(rgbString.c_str()); + gcn::Color trueCol = rgbValue; + grad = static_cast<GradientType>(config.getValue(configName + "Gradient", + static_cast<int>(grad))); + delay = config.getValueInt(configName + "Delay", delay); + mColors[type].set(type, trueCol, grad, delay); + mColors[type].text = text; + + if (grad != STATIC) + mGradVector.push_back(&mColors[type]); +} diff --git a/src/gui/userpalette.h b/src/gui/userpalette.h new file mode 100644 index 000000000..057d47113 --- /dev/null +++ b/src/gui/userpalette.h @@ -0,0 +1,222 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey <dougaboffey@netscape.net> + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef USER_PALETTE_H +#define USER_PALETTE_H + +#include "gui/palette.h" + +#include <guichan/listmodel.hpp> + +/** + * Class controlling the game's color palette. + */ +class UserPalette : public Palette, public gcn::ListModel +{ + public: + /** List of all colors that are configurable. */ + enum + { + BEING = 0, + FRIEND, + DISREGARDED, + IGNORED, + ERASED, + PC, + SELF, + GM, + NPC, + MONSTER, + MONSTER_HP, + MONSTER_HP2, + PARTY, + GUILD, + PARTICLE, + PICKUP_INFO, + EXP_INFO, + HIT_PLAYER_MONSTER, + HIT_MONSTER_PLAYER, + HIT_PLAYER_PLAYER, + HIT_CRITICAL, + HIT_LOCAL_PLAYER_MONSTER, + HIT_LOCAL_PLAYER_CRITICAL, + HIT_LOCAL_PLAYER_MISS, + MISS, + PORTAL_HIGHLIGHT, + COLLISION_HIGHLIGHT, + WALKABLE_HIGHLIGHT, + ATTACK_RANGE, + ATTACK_RANGE_BORDER, + MONSTER_ATTACK_RANGE, + HOME_PLACE, + HOME_PLACE_BORDER, + ROAD_POINT, + USER_COLOR_LAST + }; + + /** + * Constructor + */ + UserPalette(); + + /** + * Destructor + */ + ~UserPalette(); + + /** + * Gets the committed color associated with the specified type. + * + * @param type the color type requested + * + * @return the requested committed color + */ + inline const gcn::Color &getCommittedColor(int type) + { + return mColors[type].committedColor; + } + + /** + * Gets the test color associated with the specified type. + * + * @param type the color type requested + * + * @return the requested test color + */ + inline const gcn::Color &getTestColor(int type) + { return mColors[type].testColor; } + + /** + * Sets the test color associated with the specified type. + * + * @param type the color type requested + * @param color the color that should be tested + */ + inline void setTestColor(int type, gcn::Color color) + { mColors[type].testColor = color; } + + /** + * Sets the color for the specified type. + * + * @param type color to be set + * @param r red component + * @param g green component + * @param b blue component + */ + void setColor(int type, int r, int g, int b); + + /** + * Sets the gradient type for the specified color. + * + * @param grad gradient type to set + */ + void setGradient(int type, Palette::GradientType grad); + + /** + * Sets the gradient delay for the specified color. + * + * @param grad gradient type to set + */ + void setGradientDelay(int type, int delay) + { mColors[type].delay = delay; } + + /** + * Returns the number of colors known. + * + * @return the number of colors known + */ + inline int getNumberOfElements() + { return static_cast<int>(mColors.size()); } + + /** + * Returns the name of the ith color. + * + * @param i index of color interested in + * + * @return the name of the color + */ + std::string getElementAt(int i); + + /** + * Commit the colors + */ + inline void commit() + { commit(false); } + + /** + * Rollback the colors + */ + void rollback(); + + /** + * Gets the ColorType used by the color for the element at index i in + * the current color model. + * + * @param i the index of the color + * + * @return the color type of the color with the given index + */ + int getColorTypeAt(int i); + + private: + /** + * Define a color replacement. + * + * @param i the index of the color to replace + * @param r red component + * @param g green component + * @param b blue component + */ + void setColorAt(int i, int r, int g, int b); + + /** + * Commit the colors. Commit the non-static color values, if + * commitNonStatic is true. Only needed in the constructor. + */ + void commit(bool commitNonStatic); + + /** + * Prefixes the given string with "Color", lowercases all letters but + * the first and all following a '_'. All '_'s will be removed. + * + * E.g.: HIT_PLAYER_MONSTER -> HitPlayerMonster + * + * @param typeName string to transform + * + * @return the transformed string + */ + static std::string getConfigName(const std::string &typeName); + + /** + * Initialise color + * + * @param c character that needs initialising + * @param rgb default color if not found in config + * @param text identifier of color + */ + void addColor(unsigned type, unsigned rgb, GradientType grad, + const std::string &text, int delay = GRADIENT_DELAY); +}; + +extern UserPalette *userPalette; + +#endif // USER_PALETTE_H diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp new file mode 100644 index 000000000..e2bab0621 --- /dev/null +++ b/src/gui/viewport.cpp @@ -0,0 +1,763 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/viewport.h" + +#include "actorsprite.h" +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "itemshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "map.h" +#include "textmanager.h" + +#include "gui/beingpopup.h" +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/ministatus.h" +#include "gui/popupmenu.h" +#include "gui/statuspopup.h" +#include "gui/textpopup.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" + +#include "net/net.h" + +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +extern volatile int tick_time; + +Viewport::Viewport(): + mMap(0), + mMouseX(0), + mMouseY(0), + mPixelViewX(0.0f), + mPixelViewY(0.0f), +// mTileViewX(0), +// mTileViewY(0), + mShowDebugPath(false), + mCameraMode(0), + mPlayerFollowMouse(false), + mLocalWalkTime(-1), + mHoverBeing(0), + mHoverItem(0), + mHoverSign(0), + mCameraRelativeX(0), + mCameraRelativeY(0) +{ + setOpaque(false); + addMouseListener(this); + + mScrollLaziness = config.getIntValue("ScrollLaziness"); + mScrollRadius = config.getIntValue("ScrollRadius"); + mScrollCenterOffsetX = config.getIntValue("ScrollCenterOffsetX"); + mScrollCenterOffsetY = config.getIntValue("ScrollCenterOffsetY"); + + config.addListener("ScrollLaziness", this); + config.addListener("ScrollRadius", this); + + mPopupMenu = new PopupMenu; + mBeingPopup = new BeingPopup; + mTextPopup = new TextPopup; + + setFocusable(true); +} + +Viewport::~Viewport() +{ + config.removeListener("ScrollLaziness", this); + config.removeListener("ScrollRadius", this); + + delete mPopupMenu; + mPopupMenu = 0; + delete mBeingPopup; + mBeingPopup = 0; + delete mTextPopup; + mTextPopup = 0; +} + +void Viewport::setMap(Map *map) +{ + if (mMap && map) + map->setDebugFlags(mMap->getDebugFlags()); + mMap = map; +} + +extern MiniStatusWindow *miniStatusWindow; + +void Viewport::draw(gcn::Graphics *gcnGraphics) +{ + static int lastTick = tick_time; + + if (!mMap || !player_node) + { + gcnGraphics->setColor(gcn::Color(64, 64, 64)); + gcnGraphics->fillRectangle( + gcn::Rectangle(0, 0, getWidth(), getHeight())); + return; + } + + Graphics *graphics = static_cast<Graphics*>(gcnGraphics); + + // Avoid freaking out when tick_time overflows + if (tick_time < lastTick) + lastTick = tick_time; + + // Calculate viewpoint + int midTileX = (graphics->getWidth() + mScrollCenterOffsetX) / 2; + int midTileY = (graphics->getHeight() + mScrollCenterOffsetX) / 2; + + const Vector &playerPos = player_node->getPosition(); + const int player_x = static_cast<int>(playerPos.x) + - midTileX + mCameraRelativeX; + const int player_y = static_cast<int>(playerPos.y) + - midTileY + mCameraRelativeY; + + if (mScrollLaziness < 1) + mScrollLaziness = 1; // Avoids division by zero + + // Apply lazy scrolling + while (lastTick < tick_time) + { + if (player_x > static_cast<int>(mPixelViewX) + mScrollRadius) + { + mPixelViewX += static_cast<float>(player_x + - static_cast<int>(mPixelViewX) - mScrollRadius) / + static_cast<float>(mScrollLaziness); + } + if (player_x < static_cast<int>(mPixelViewX) - mScrollRadius) + { + mPixelViewX += static_cast<float>(player_x + - static_cast<int>(mPixelViewX) + mScrollRadius) / + static_cast<float>(mScrollLaziness); + } + if (player_y > static_cast<int>(mPixelViewY) + mScrollRadius) + { + mPixelViewY += static_cast<float>(player_y + - static_cast<int>(mPixelViewY) - mScrollRadius) / + static_cast<float>(mScrollLaziness); + } + if (player_y < static_cast<int>(mPixelViewY) - mScrollRadius) + { + mPixelViewY += static_cast<float>(player_y + - static_cast<int>(mPixelViewY) + mScrollRadius) / + static_cast<float>(mScrollLaziness); + } + lastTick++; + } + + // Auto center when player is off screen + if (player_x - static_cast<int>(mPixelViewX) > graphics->getWidth() / 2 + || static_cast<int>(mPixelViewX) - player_x > graphics->getWidth() / 2 + || static_cast<int>(mPixelViewY) - player_y + > graphics->getHeight() / 2 + || player_y - static_cast<int>(mPixelViewY) + > graphics->getHeight() / 2) + { + mPixelViewX = static_cast<float>(player_x); + mPixelViewY = static_cast<float>(player_y); + }; + + // Don't move camera so that the end of the map is on screen + const int viewXmax = + mMap->getWidth() * mMap->getTileWidth() - graphics->getWidth(); + const int viewYmax = + mMap->getHeight() * mMap->getTileHeight() - graphics->getHeight(); + if (mMap) + { + if (mPixelViewX < 0) + mPixelViewX = 0; + if (mPixelViewY < 0) + mPixelViewY = 0; + if (mPixelViewX > viewXmax) + mPixelViewX = static_cast<float>(viewXmax); + if (mPixelViewY > viewYmax) + mPixelViewY = static_cast<float>(viewYmax); + } + + // Draw tiles and sprites + if (mMap) + { + mMap->draw(graphics, static_cast<int>(mPixelViewX), + static_cast<int>(mPixelViewY)); + + if (mShowDebugPath) + { + mMap->drawCollision(graphics, + static_cast<int>(mPixelViewX), + static_cast<int>(mPixelViewY), + mShowDebugPath); + if (mShowDebugPath == Map::MAP_DEBUG) + _drawDebugPath(graphics); + } + } + + if (player_node->getCheckNameSetting()) + { + player_node->setCheckNameSetting(false); + player_node->setName(player_node->getName()); + } + + // Draw text + if (textManager) + textManager->draw(graphics, static_cast<int>(mPixelViewX), + static_cast<int>(mPixelViewY)); + + // Draw player names, speech, and emotion sprite as needed + const ActorSprites &actors = actorSpriteManager->getAll(); + for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end(); + it != it_end; it++) + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM) + continue; + Being *b = static_cast<Being*>(*it); + + b->drawSpeech(static_cast<int>(mPixelViewX), + static_cast<int>(mPixelViewY)); + b->drawEmotion(graphics, static_cast<int>(mPixelViewX), + static_cast<int>(mPixelViewY)); + } + + if (miniStatusWindow) + miniStatusWindow->drawIcons(graphics); + + // Draw contained widgets + WindowContainer::draw(gcnGraphics); +} + +void Viewport::logic() +{ + WindowContainer::logic(); + + // Make the player follow the mouse position + // if the mouse is dragged elsewhere than in a window. + _followMouse(); +} + +void Viewport::_followMouse() +{ + Uint8 button = SDL_GetMouseState(&mMouseX, &mMouseY); + // If the left button is dragged + if (mPlayerFollowMouse && button & SDL_BUTTON(1)) + { + // We create a mouse event and send it to mouseDragged. + Uint8 *keys = SDL_GetKeyState(NULL); + gcn::MouseEvent mouseEvent(NULL, + (keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT]), + false, + false, + false, + gcn::MouseEvent::DRAGGED, + gcn::MouseEvent::LEFT, + mMouseX, + mMouseY, + 0); + + mouseDragged(mouseEvent); + } +} + +void Viewport::_drawDebugPath(Graphics *graphics) +{ + // Get the current mouse position + SDL_GetMouseState(&mMouseX, &mMouseY); + + Path debugPath; + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + const int mouseTileX = (mMouseX + static_cast<int>(mPixelViewX)) / 32; + const int mouseTileY = (mMouseY + static_cast<int>(mPixelViewY)) / 32; + const Vector &playerPos = player_node->getPosition(); + + debugPath = mMap->findPath( + static_cast<int>(playerPos.x - 16) / 32, + static_cast<int>(playerPos.y - 32) / 32, + mouseTileX, mouseTileY, 0, 500); + + _drawPath(graphics, debugPath); + } + else if (Net::getNetworkType() == ServerInfo::MANASERV) + { + const Vector &playerPos = player_node->getPosition(); + const int playerRadius = player_node->getCollisionRadius(); + // Draw player collision rectangle + graphics->setColor(gcn::Color(128, 128, 0, 120)); + graphics->fillRectangle( + gcn::Rectangle(static_cast<int>(playerPos.x) + - static_cast<int>(mPixelViewX) - playerRadius, + static_cast<int>(playerPos.y) + - static_cast<int>(mPixelViewY) - playerRadius, + playerRadius * 2, playerRadius * 2)); + + debugPath = mMap->findPixelPath( + static_cast<int>(playerPos.x), + static_cast<int>(playerPos.y), + mMouseX + static_cast<int>(mPixelViewX), + mMouseY + static_cast<int>(mPixelViewY), + playerRadius, 0xFF); + + // We draw the path proposed by mouse + _drawPath(graphics, debugPath, gcn::Color(128, 0, 128)); + + // But also the one currently walked on. + _drawPath(graphics, player_node->getPath(), gcn::Color(0, 0, 255)); + } +} + +void Viewport::_drawPath(Graphics *graphics, const Path &path, + gcn::Color color) +{ + graphics->setColor(color); + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + for (Path::const_iterator i = path.begin(); i != path.end(); ++i) + { + int squareX = i->x * 32 - static_cast<int>(mPixelViewX) + 12; + int squareY = i->y * 32 - static_cast<int>(mPixelViewY) + 12; + + graphics->fillRectangle(gcn::Rectangle(squareX, squareY, 8, 8)); + if (mMap) + { + graphics->drawText( + toString(mMap->getMetaTile(i->x, i->y)->Gcost), + squareX + 4, squareY + 12, gcn::Graphics::CENTER); + } + } + } + else if (Net::getNetworkType() == ServerInfo::MANASERV) + { + for (Path::const_iterator i = path.begin(); i != path.end(); ++i) + { + int squareX = i->x - static_cast<int>(mPixelViewX); + int squareY = i->y - static_cast<int>(mPixelViewY); + + graphics->fillRectangle(gcn::Rectangle(squareX - 4, squareY - 4, + 8, 8)); + if (mMap) + { + graphics->drawText( + toString(mMap->getMetaTile(i->x / 32, i->y / 32)->Gcost), + squareX + 4, squareY + 12, gcn::Graphics::CENTER); + } + } + + } +} + +void Viewport::mousePressed(gcn::MouseEvent &event) +{ + if (event.getSource() != this) + return; + + // Check if we are alive and kickin' +// if (!mMap || !player_node || !player_node->isAlive()) + if (!mMap || !player_node) + return; + + // Check if we are busy + // if commented, allow context menu if npc dialog open + if (Being::isTalking()) + return; + + mPlayerFollowMouse = false; + + const int pixelX = event.getX() + static_cast<int>(mPixelViewX); + const int pixelY = event.getY() + static_cast<int>(mPixelViewY); + + // Right click might open a popup + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + if (mHoverBeing) + { + if (actorSpriteManager) + { + std::list<Being*> beings; + const int x = getMouseX() + static_cast<int>(mPixelViewX); + const int y = getMouseY() + static_cast<int>(mPixelViewY); + actorSpriteManager->findBeingsByPixel(beings, x, y, true); + if (beings.size() > 1) + { + mPopupMenu->showPopup(event.getX(), event.getY(), beings); + return; + } + else + { + mPopupMenu->showPopup(event.getX(), event.getY(), + mHoverBeing); + return; + } + } + } + else if (mHoverItem) + { + mPopupMenu->showPopup(event.getX(), event.getY(), mHoverItem); + return; + } + else if (mHoverSign) + { + mPopupMenu->showPopup(event.getX(), event.getY(), mHoverSign); + return; + } + } + + // If a popup is active, just remove it + if (mPopupMenu->isVisible()) + { + mPopupMenu->setVisible(false); + return; + } + + // Left click can cause different actions + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Interact with some being + if (mHoverBeing) + { + if (!mHoverBeing->isAlive()) + return; + + if (mHoverBeing->canTalk()) + { + mHoverBeing->talkTo(); + } + else + { + if (mHoverBeing->getType() == ActorSprite::PLAYER) + { + if (actorSpriteManager) + actorSpriteManager->heal(player_node, mHoverBeing); + } + else if (player_node->withinAttackRange(mHoverBeing) || + keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->attack(mHoverBeing, + !keyboard.isKeyActive(keyboard.KEY_TARGET)); + } + else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->setGotoTarget(mHoverBeing); + } + } + // Picks up a item if we clicked on one + } + else if (mHoverItem) + { + player_node->pickUp(mHoverItem); + } + // Just walk around + else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->stopAttack(); + player_node->cancelFollow(); + mPlayerFollowMouse = true; + + // Make the player go to the mouse position + _followMouse(); + } + } + else if (event.getButton() == gcn::MouseEvent::MIDDLE) + { + // Find the being nearest to the clicked position + if (actorSpriteManager) + { + Being *target = actorSpriteManager->findNearestLivingBeing( + pixelX, pixelY, 20, ActorSprite::MONSTER); + + if (target) + player_node->setTarget(target); + } + } +} + +void Viewport::mouseDragged(gcn::MouseEvent &event) +{ + if (!mMap || !player_node) + return; + + if (mPlayerFollowMouse && !event.isShiftPressed()) + { + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + if (get_elapsed_time(mLocalWalkTime) >= walkingMouseDelay) + { + mLocalWalkTime = tick_time; + player_node->setDestination(event.getX() + + static_cast<int>(mPixelViewX), + event.getY() + + static_cast<int>(mPixelViewY)); + player_node->pathSetByMouse(); + } + } + else + { + if (mLocalWalkTime != player_node->getActionTime()) + { + mLocalWalkTime = player_node->getActionTime(); + int destX = static_cast<int>((static_cast<float>(event.getX()) + + mPixelViewX) + / static_cast<float>(mMap->getTileWidth())); + int destY = static_cast<int>((static_cast<float>(event.getY()) + + mPixelViewY) + / static_cast<float>(mMap->getTileHeight())); + player_node->setDestination(destX, destY); + } + } + } +} + +void Viewport::mouseReleased(gcn::MouseEvent &event _UNUSED_) +{ + mPlayerFollowMouse = false; + + // Only useful for eAthena but doesn't hurt under ManaServ + mLocalWalkTime = -1; +} + +void Viewport::showPopup(Window *parent, int x, int y, Item *item, + bool isInventory) +{ + mPopupMenu->showPopup(parent, x, y, item, isInventory); +} + +void Viewport::showPopup(MapItem *item) +{ + mPopupMenu->showPopup(getMouseX(), getMouseY(), item); +} + +void Viewport::showPopup(Window *parent, Item *item, bool isInventory) +{ + mPopupMenu->showPopup(parent, getMouseX(), getMouseY(), item, isInventory); +} + +void Viewport::showItemPopup(Item *item) +{ + mPopupMenu->showItemPopup(getMouseX(), getMouseY(), item); +} + +void Viewport::showItemPopup(int itemId) +{ + mPopupMenu->showItemPopup(getMouseX(), getMouseY(), itemId); +} + +void Viewport::showDropPopup(Item *item) +{ + mPopupMenu->showDropPopup(getMouseX(), getMouseY(), item); +} + +void Viewport::showOutfitsPopup(int x, int y) +{ + mPopupMenu->showOutfitsPopup(x, y); +} + +void Viewport::showOutfitsPopup() +{ + mPopupMenu->showOutfitsPopup(getMouseX(), getMouseY()); +} + +void Viewport::showSpellPopup(TextCommand *cmd) +{ + mPopupMenu->showSpellPopup(getMouseX(), getMouseY(), cmd); +} + +void Viewport::showChatPopup(int x, int y, ChatTab *tab) +{ + mPopupMenu->showChatPopup(x, y, tab); +} + +void Viewport::showChatPopup(ChatTab *tab) +{ + mPopupMenu->showChatPopup(getMouseX(), getMouseY(), tab); +} + +void Viewport::showPopup(int x, int y, Being *being) +{ + mPopupMenu->showPopup(x, y, being); +} + +void Viewport::showPlayerPopup(std::string nick) +{ + mPopupMenu->showPlayerPopup(getMouseX(), getMouseY(), nick); +} + +void Viewport::showPopup(int x, int y, Button *button) +{ + mPopupMenu->showPopup(x, y, button); +} + +void Viewport::closePopupMenu() +{ + if (mPopupMenu) + mPopupMenu->handleLink("cancel", 0); +} + +void Viewport::optionChanged(const std::string &name _UNUSED_) +{ + mScrollLaziness = config.getIntValue("ScrollLaziness"); + mScrollRadius = config.getIntValue("ScrollRadius"); +} + +void Viewport::mouseMoved(gcn::MouseEvent &event _UNUSED_) +{ + // Check if we are on the map + if (!mMap || !player_node || !actorSpriteManager) + return; + + const int x = getMouseX() + static_cast<int>(mPixelViewX); + const int y = getMouseY() + static_cast<int>(mPixelViewY); + + mHoverBeing = actorSpriteManager->findBeingByPixel(x, y, true); + if (mHoverBeing && mHoverBeing->getType() == Being::PLAYER) + { + mTextPopup->setVisible(false); + mBeingPopup->show(getMouseX(), getMouseY(), mHoverBeing); + } + else + { + mBeingPopup->setVisible(false); + } + + mHoverItem = 0; + if (!mHoverBeing && actorSpriteManager) + { + mHoverItem = actorSpriteManager->findItem(x / mMap->getTileWidth(), + y / mMap->getTileHeight()); + } + if (!mHoverBeing && !mHoverItem) + { + SpecialLayer *specialLayer = mMap->getSpecialLayer(); + if (specialLayer) + { + int mouseTileX = (getMouseX() + getCameraX()) + / mMap->getTileWidth(); + int mouseTileY = (getMouseY() + getCameraY()) + / mMap->getTileHeight(); + + mHoverSign = specialLayer->getTile(mouseTileX, mouseTileY); + if (mHoverSign && mHoverSign->getType() != MapItem::EMPTY) + { + if (!mHoverSign->getComment().empty()) + { + if (mBeingPopup) + mBeingPopup->setVisible(false); + mTextPopup->show(getMouseX(), getMouseY(), + mHoverSign->getComment()); + } + else + { + if (mTextPopup->isVisible()) + mTextPopup->setVisible(false); + } + return; + } + } + } + if (mTextPopup->isVisible()) + mTextPopup->setVisible(false); + + if (mHoverBeing) + { + switch (mHoverBeing->getType()) + { + // NPCs + case ActorSprite::NPC: + gui->setCursorType(Gui::CURSOR_TALK); + break; + + // Monsters + case ActorSprite::MONSTER: + gui->setCursorType(Gui::CURSOR_FIGHT); + break; + default: + gui->setCursorType(Gui::CURSOR_POINTER); + break; + } + } + // Item mouseover + else if (mHoverItem) + { + gui->setCursorType(Gui::CURSOR_PICKUP); + } + else + { + gui->setCursorType(Gui::CURSOR_POINTER); + } +} + +void Viewport::toggleDebugPath() +{ + mShowDebugPath++; + if (mShowDebugPath > Map::MAP_BLACKWHITE) + mShowDebugPath = Map::MAP_NORMAL; + if (mMap) + mMap->setDebugFlags(mShowDebugPath); +} + +void Viewport::toggleCameraMode() +{ + mCameraMode++; + if (mCameraMode > 1) + mCameraMode = 0; + if (!mCameraMode) + { + mCameraRelativeX = 0; + mCameraRelativeY = 0; + } + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void Viewport::hideBeingPopup() +{ + if (mBeingPopup) + mBeingPopup->setVisible(false); + if (mTextPopup) + mTextPopup->setVisible(false); +} + +void Viewport::clearHover(ActorSprite *actor) +{ + if (mHoverBeing == actor) + mHoverBeing = 0; + + if (mHoverItem == actor) + mHoverItem = 0; +} + +void Viewport::cleanHoverItems() +{ + mHoverBeing = 0; + mHoverItem = 0; + mHoverSign = 0; +} + +void Viewport::moveCamera(int dx, int dy) +{ + mCameraRelativeX += dx; + mCameraRelativeY += dy; +}
\ No newline at end of file diff --git a/src/gui/viewport.h b/src/gui/viewport.h new file mode 100644 index 000000000..cf5e53c4d --- /dev/null +++ b/src/gui/viewport.h @@ -0,0 +1,298 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef VIEWPORT_H +#define VIEWPORT_H + +#include "actorspritemanager.h" +#include "configlistener.h" +#include "position.h" + +#include "gui/widgets/windowcontainer.h" + +#include <guichan/mouselistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ActorSprite; +class Button; +class Being; +class BeingPopup; +class ChatTab; +class FloorItem; +class Graphics; +class ImageSet; +class Item; +class ItemShortcut; +class Map; +class PopupMenu; +class TextCommand; +class StatusPopup; +class TextPopup; +class Window; + +/** Delay between two mouse calls when dragging mouse and move the player */ +const int walkingMouseDelay = 500; + +/** + * The viewport on the map. Displays the current map and handles mouse input + * and the popup menu. + * + * TODO: This class is planned to be extended to allow floating widgets on top + * of it such as NPC messages, which are positioned using map pixel + * coordinates. + */ +class Viewport : public WindowContainer, public gcn::MouseListener, + public ConfigListener +{ + public: + /** + * Constructor. + */ + Viewport(); + + /** + * Destructor. + */ + ~Viewport(); + + /** + * Sets the map displayed by the viewport. + */ + void setMap(Map *map); + + /** + * Draws the viewport. + */ + void draw(gcn::Graphics *graphics); + + /** + * Implements player to keep following mouse. + */ + void logic(); + + /** + * Toggles whether the path debug graphics are shown. normal, debug with all images and grid, debug with out big images and with out grid. + */ + void toggleDebugPath(); + + void toggleCameraMode(); + + /** + * Handles mouse press on map. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse move on map + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse button release on map. + */ + void mouseReleased(gcn::MouseEvent &event); + + /** + * Handles mouse move on map. + */ + void mouseMoved(gcn::MouseEvent &event); + + /** + * Shows a popup for an item. + * TODO Find some way to get rid of Item here + */ + void showPopup(Window *parent, int x, int y, Item *item, + bool isInventory = true); + + /** + * Shows a popup for an item. + * TODO Find some way to get rid of Item here + */ + void showPopup(Window *parent, Item *item, bool isInventory = true); + + void showPopup(int x, int y, Button *button); + + void showPopup(MapItem *item); + + void showItemPopup(Item *item); + + void showItemPopup(int itemId); + + void showDropPopup(Item *item); + + /** + * Shows a popup for being. + */ + void showPopup(int x, int y, Being *being); + + void showPlayerPopup(std::string nick); + + void showOutfitsPopup(int x, int y); + + void showOutfitsPopup(); + + void showSpellPopup(TextCommand *cmd); + + /** + * Shows the related popup menu when right click on the chat + * at the specified mouse coordinates. + */ + void showChatPopup(int x, int y, ChatTab *tab); + + /** + * Shows the related popup menu when right click on the chat + */ + void showChatPopup(ChatTab *tab); + + /** + * Closes the popup menu. Needed for when the player dies or switching + * maps. + */ + void closePopupMenu(); + + /** + * A relevant config option changed. + */ + void optionChanged(const std::string &name); + + /** + * Returns camera x offset in pixels. + */ + int getCameraX() const + { return static_cast<int>(mPixelViewX); } + + /** + * Returns camera y offset in pixels. + */ + int getCameraY() const + { return static_cast<int>(mPixelViewY); } + + /** + * Returns mouse x in pixels. + */ + int getMouseX() const + { return mMouseX; } + + /** + * Returns mouse y in pixels. + */ + int getMouseY() const + { return mMouseY; } + + /** + * Changes viewpoint by relative pixel coordinates. + */ + void scrollBy(float x, float y) + { mPixelViewX += x; mPixelViewY += y; } + + /** + * Returns the current map object. + */ + Map *getCurrentMap() const + { return mMap; } + + int getDebugPath() + { return mShowDebugPath; } + + int getCameraMode() + { return mCameraMode; } + + /** + * Hides the BeingPopup. + */ + void hideBeingPopup(); + + /** + * Clear all hover item\being etc + */ + void cleanHoverItems(); + + Map *getMap() + { return mMap; } + + void moveCamera(int dx, int dy); + + int getCameraRelativeX() + { return mCameraRelativeX; } + + int getCameraRelativeY() + { return mCameraRelativeY; } + + protected: + friend class ActorSpriteManager; + + /// Clears any matching hovers + void clearHover(ActorSprite *actor); + + private: + /** + * Finds a path from the player to the mouse, and draws it. This is for + * debug purposes. + */ + void _drawDebugPath(Graphics *graphics); + + /** + * Draws the given path. + */ + void _drawPath(Graphics *graphics, const Path &path, + gcn::Color color = gcn::Color(255, 0, 0)); + + /** + * Make the player go to the mouse position. + */ + void _followMouse(); + + Map *mMap; /**< The current map. */ + + int mScrollRadius; + int mScrollLaziness; + int mScrollCenterOffsetX; + int mScrollCenterOffsetY; + int mMouseX; /**< Current mouse position in pixels. */ + int mMouseY; /**< Current mouse position in pixels. */ + float mPixelViewX; /**< Current viewpoint in pixels. */ + float mPixelViewY; /**< Current viewpoint in pixels. */ + int mShowDebugPath; /**< Show a path from player to pointer. */ + int mCameraMode; /**< Camera mode. */ + + bool mPlayerFollowMouse; + + int mLocalWalkTime; /**< Timestamp before the next walk can be sent. */ + + PopupMenu *mPopupMenu; /**< Popup menu. */ + Being *mHoverBeing; /**< Being mouse is currently over. */ + FloorItem *mHoverItem; /**< FloorItem mouse is currently over. */ + MapItem *mHoverSign; /**< Map sign mouse is currently over. */ + BeingPopup *mBeingPopup; /**< Being information popup. */ + TextPopup *mTextPopup; /**< Map Item information popup. */ + + int mCameraRelativeX; + int mCameraRelativeY; +}; + +extern Viewport *viewport; /**< The viewport. */ + +#endif diff --git a/src/gui/whoisonline.cpp b/src/gui/whoisonline.cpp new file mode 100644 index 000000000..84485b995 --- /dev/null +++ b/src/gui/whoisonline.cpp @@ -0,0 +1,550 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "whoisonline.h" + +#include <SDL.h> +#include <SDL_thread.h> +#include <vector> +#include <algorithm> + +#include "gui/viewport.h" +#include "gui/widgets/button.h" +#include "gui/widgets/browserbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/chattab.h" + +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "localplayer.h" +#include "playerrelations.h" +#include "main.h" + +#include "gui/chat.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +// Curl should be included after Guichan to avoid Windows redefinitions +#include <curl/curl.h> + +bool stringCompare(const std::string &left, const std::string &right); + +bool stringCompare(const std::string &left, const std::string &right ) +{ + for (std::string::const_iterator lit = left.begin(), + rit = right.begin(); + lit != left.end() && rit != right.end(); ++lit, ++rit) + { + if (tolower(*lit) < tolower(*rit)) + return true; + else if (tolower(*lit) > tolower(*rit)) + return false; + } + if (left.size() < right.size()) + return true; + return false; +} + +WhoIsOnline::WhoIsOnline(): + Window(_("Who Is Online - Updating")), + mThread(NULL), + mDownloadStatus(UPDATE_LIST), + mDownloadComplete(true), + mDownloadedBytes(0), + mMemoryBuffer(NULL), + mCurlError(new char[CURL_ERROR_SIZE]), + mAllowUpdate(true), + mShowLevel(false) +{ + mCurlError[0] = 0; + setWindowName("WhoIsOnline"); + + const int h = 350; + const int w = 200; + setDefaultSize(w, h, ImageRect::CENTER); +// setContentSize(w, h); + setCloseButton(true); + setResizable(true); + + mUpdateButton = new Button(_("Update"), "update", this); + mUpdateButton->setEnabled(false); + mUpdateButton->setDimension(gcn::Rectangle(5, 5, w - 10, 20 + 5)); + + mBrowserBox = new BrowserBox(); + mScrollArea = new ScrollArea(mBrowserBox); + mScrollArea->setOpaque(false); + mBrowserBox->setOpaque(false); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mScrollArea->setDimension(gcn::Rectangle(5, 20 + 10, w - 10, h - 10 - 30)); + mScrollArea->setSize(w - 10, h - 10 - 30); + mBrowserBox->setLinkHandler(this); + + add(mUpdateButton); + add(mScrollArea); + + mUpdateTimer = 0; + setLocationRelativeTo(getParent()); + + loadWindowState(); + + download(); + + config.addListener("updateOnlineList", this); + mUpdateOnlineList = config.getBoolValue("updateOnlineList"); +} + +WhoIsOnline::~WhoIsOnline() +{ + config.removeListener("updateOnlineList", this); + + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, NULL); + + free(mMemoryBuffer); + mMemoryBuffer = 0; + + // Remove possibly leftover temporary download + delete[] mCurlError; +} + +void WhoIsOnline::handleLink(const std::string& link, gcn::MouseEvent *event) +{ + if (!event || event->getButton() == gcn::MouseEvent::LEFT) + { + if (chatWindow) + { + if (config.getBoolValue("whispertab")) + chatWindow->localChatInput("/q " + link); + else + chatWindow->addInputText("/w \"" + link + "\" "); + } + } + else if (event->getButton() == gcn::MouseEvent::RIGHT) + { + if (player_node && link == player_node->getName()) + return; + + if (viewport) + { + if (actorSpriteManager) + { + Being* being = actorSpriteManager->findBeingByName( + link, Being::PLAYER); + + if (being && viewport) + { + viewport->showPopup(event->getX(), event->getY(), being); + return; + } + } + viewport->showPlayerPopup(link); + } + } +} + +void WhoIsOnline::loadList() +{ + if (!mMemoryBuffer) + return; + + // Reallocate and include terminating 0 character + mMemoryBuffer = static_cast<char*>( + realloc(mMemoryBuffer, mDownloadedBytes + 1)); + mMemoryBuffer[mDownloadedBytes] = '\0'; + + mBrowserBox->clearRows(); + bool listStarted(false); + std::string lineStr; + int numOnline(0); + std::vector<std::string> friends; + std::vector<std::string> neutral; + std::vector<std::string> disregard; + + // Tokenize and add each line separately + char *line = strtok(mMemoryBuffer, "\n"); + const std::string gmText = "(GM)"; + mOnlinePlayers.clear(); + + mShowLevel = config.getBoolValue("showlevel"); + + while (line != NULL) + { + std::string nick; + lineStr = line; + trim(lineStr); + if (listStarted == true) + { + size_t found; + found = lineStr.find(" users are online."); + if (found == std::string::npos) + { + int level = 0; + + std::string::size_type pos = 0; + if (lineStr.length() > 24) + { + nick = lineStr.substr(0, 24); + lineStr = lineStr.substr(25); + } + else + { + nick = lineStr; + lineStr = ""; + } + trim(nick); + + pos = lineStr.find(gmText, 0); + if (pos != std::string::npos) + lineStr = lineStr.substr(pos + gmText.length()); + + trim(lineStr); + pos = lineStr.find("/", 0); + + if (pos != std::string::npos) + lineStr = lineStr.substr(0, pos); + + if (!lineStr.empty()) + level = atoi(lineStr.c_str()); + + if (actorSpriteManager) + { + Being *being = actorSpriteManager->findBeingByName( + nick, Being::PLAYER); + if (being) + { + if (level > 0) + { + being->setLevel(level); + being->updateName(); + } + else + { + if (being->getLevel() > 1) + level = being->getLevel(); + } + } + } + + mOnlinePlayers.insert(nick); + + numOnline++; + switch (player_relations.getRelation(nick)) + { + case PlayerRelation::NEUTRAL: + neutral.push_back(prepareNick(nick, level, "0")); + break; + + case PlayerRelation::FRIEND: + friends.push_back(prepareNick(nick, level, "2")); + break; + + case PlayerRelation::DISREGARDED: + disregard.push_back(prepareNick(nick, level, "8")); + break; + + case PlayerRelation::IGNORED: + case PlayerRelation::ERASED: + default: + //Ignore the ignored. + break; + } + } + } + else if (lineStr.find("------------------------------") + != std::string::npos) + { + listStarted = true; + } + line = strtok(NULL, "\n"); + } + + //Set window caption + setCaption(_("Who Is Online - ") + toString(numOnline)); + + //List the online people + sort(friends.begin(), friends.end(), stringCompare); + sort(neutral.begin(), neutral.end(), stringCompare); + sort(disregard.begin(), disregard.end(), stringCompare); + bool addedFromSection(false); + for (int i = 0; i < static_cast<int>(friends.size()); i++) + { + mBrowserBox->addRow(friends.at(i)); + addedFromSection = true; + } + if (addedFromSection == true) + { + mBrowserBox->addRow("---"); + addedFromSection = false; + } + for (int i = 0; i < static_cast<int>(neutral.size()); i++) + { + mBrowserBox->addRow(neutral.at(i)); + addedFromSection = true; + } + if (addedFromSection == true && !disregard.empty()) + { + mBrowserBox->addRow("---"); + addedFromSection = false; + } + for (int i = 0; i < static_cast<int>(disregard.size()); i++) + { + mBrowserBox->addRow(disregard.at(i)); + } + + // Free the memory buffer now that we don't need it anymore + free(mMemoryBuffer); + mMemoryBuffer = 0; + + if (mScrollArea->getVerticalMaxScroll() < + mScrollArea->getVerticalScrollAmount()) + { + mScrollArea->setVerticalScrollAmount( + mScrollArea->getVerticalMaxScroll()); + } +} + +size_t WhoIsOnline::memoryWrite(void *ptr, size_t size, + size_t nmemb, FILE *stream) +{ + WhoIsOnline *wio = reinterpret_cast<WhoIsOnline *>(stream); + size_t totalMem = size * nmemb; + wio->mMemoryBuffer = static_cast<char*>(realloc(wio->mMemoryBuffer, + wio->mDownloadedBytes + totalMem)); + if (wio->mMemoryBuffer) + { + memcpy(&(wio->mMemoryBuffer[wio->mDownloadedBytes]), ptr, totalMem); + wio->mDownloadedBytes += static_cast<int>(totalMem); + } + + return totalMem; +} + +int WhoIsOnline::downloadThread(void *ptr) +{ + int attempts = 0; + WhoIsOnline *wio = reinterpret_cast<WhoIsOnline *>(ptr); + CURL *curl; + CURLcode res; + + std::string url(Client::getServerName() + "/online.txt"); + + while (attempts < 1 && !wio->mDownloadComplete) + { + curl = curl_easy_init(); + + if (curl) + { + if (!wio->mAllowUpdate) + { + curl_easy_cleanup(curl); + break; + } + wio->mDownloadedBytes = 0; + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, + WhoIsOnline::memoryWrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, ptr); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, + strprintf(PACKAGE_EXTENDED_VERSION, branding + .getValue("appShort", "mana").c_str()).c_str()); + + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, wio->mCurlError); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, ptr); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); + + struct curl_slist *pHeaders = 0; + // Make sure the resources2.txt and news.txt aren't cached, + // in order to always get the latest version. + pHeaders = curl_slist_append(pHeaders, "pragma: no-cache"); + pHeaders = + curl_slist_append(pHeaders, "Cache-Control: no-cache"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders); + + if ((res = curl_easy_perform(curl)) != 0) + { + wio->mDownloadStatus = UPDATE_ERROR; + switch (res) + { + case CURLE_COULDNT_CONNECT: + default: + std::cerr << "curl error " + << static_cast<unsigned>(res) << ": " + << wio->mCurlError << " host: " + << url.c_str() << std::endl; + break; + } + attempts++; + continue; + } + + curl_easy_cleanup(curl); + + curl_slist_free_all(pHeaders); + + // It's stored in memory, we're done + wio->mDownloadComplete = true; + } + if (!wio->mAllowUpdate) + break; + attempts++; + } + + if (!wio->mDownloadComplete) + wio->mDownloadStatus = UPDATE_ERROR; + +// wio->mThread = 0; + return 0; +} + +void WhoIsOnline::download() +{ + mDownloadComplete = true; + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, NULL); + + mDownloadComplete = false; + mThread = SDL_CreateThread(WhoIsOnline::downloadThread, this); + + if (mThread == NULL) + mDownloadStatus = UPDATE_ERROR; +} + +void WhoIsOnline::logic() +{ + // Update Scroll logic + mScrollArea->logic(); + + if (!mAllowUpdate) + return; + + if (mUpdateTimer == 0) + mUpdateTimer = cur_time; + + double timeDiff = difftime(cur_time, mUpdateTimer); + int timeLimit = isVisible() ? 20 : 120; + + if (mUpdateOnlineList && timeDiff >= timeLimit + && mDownloadStatus != UPDATE_LIST) + { + if (mDownloadComplete == true) + { + setCaption(_("Who Is Online - Updating")); + mUpdateTimer = 0; + mDownloadStatus = UPDATE_LIST; + download(); + } + } + + switch (mDownloadStatus) + { + case UPDATE_ERROR: + mBrowserBox->clearRows(); + mBrowserBox->addRow("##1Failed to fetch the online list!"); + mBrowserBox->addRow(mCurlError); + mDownloadStatus = UPDATE_COMPLETE; + setCaption(_("Who Is Online - error")); + mUpdateButton->setEnabled(true); + mUpdateTimer = cur_time + 240; + break; + case UPDATE_LIST: + if (mDownloadComplete == true) + { + loadList(); + mDownloadStatus = UPDATE_COMPLETE; + mUpdateButton->setEnabled(true); + mUpdateTimer = 0; + updateSize(); + if (mOnlinePlayers.size() > 0 && chatWindow) + chatWindow->updateOnline(mOnlinePlayers); + } + break; + default: + break; + } +} + +void WhoIsOnline::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "update") + { + if (mDownloadStatus == UPDATE_COMPLETE) + { + mUpdateTimer = cur_time - 20; + if (mUpdateButton) + mUpdateButton->setEnabled(false); + setCaption(_("Who Is Online - Update")); + if (mThread && SDL_GetThreadID(mThread)) + { + SDL_WaitThread(mThread, NULL); + mThread = NULL; + } + mDownloadComplete = true; + } + } +} + +void WhoIsOnline::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); + updateSize(); +} + +void WhoIsOnline::updateSize() +{ + if (mDownloadStatus == UPDATE_COMPLETE) + { + const gcn::Rectangle area = getChildrenArea(); + if (mUpdateButton) + mUpdateButton->setWidth(area.width - 10); + + if (mScrollArea) + mScrollArea->setSize(area.width - 10, area.height - 10 - 30); + } +} + +const std::string WhoIsOnline::prepareNick(std::string nick, int level, + std::string color) const +{ + if (mShowLevel && level > 1) + { + return strprintf("@@%s|##%s%s (%d)@@", nick.c_str(), + color.c_str(), nick.c_str(), level); + } + else + { + return strprintf("@@%s|##%s%s@@", nick.c_str(), + color.c_str(), nick.c_str()); + } +} + +void WhoIsOnline::optionChanged(const std::string &name) +{ + if (name == "updateOnlineList") + mUpdateOnlineList = config.getBoolValue("updateOnlineList"); +}
\ No newline at end of file diff --git a/src/gui/whoisonline.h b/src/gui/whoisonline.h new file mode 100644 index 000000000..d6ac177d6 --- /dev/null +++ b/src/gui/whoisonline.h @@ -0,0 +1,139 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _WHOISONLINE_H +#define _WHOISONLINE_H + +#include <string> +#include <set> + +#include "configlistener.h" + +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/window.h" + +#include "../utils/mutex.h" + +#include <guichan/actionlistener.hpp> + +class BrowserBox; +class ScrollArea; + +struct SDL_Thread; + +/** + * Update progress window GUI + * + * \ingroup GUI + */ +class WhoIsOnline : public Window, + public LinkHandler, + public gcn::ActionListener, + public ConfigListener +{ + public: + /** + * Constructor. + */ + WhoIsOnline(); + + /** + * Destructor + */ + ~WhoIsOnline(); + + /** + * Loads and display online list from the memory buffer. + */ + void loadList(); + + void handleLink(const std::string& link, gcn::MouseEvent *event); + + void logic(); + + void action(const gcn::ActionEvent &event); + + void widgetResized(const gcn::Event &event); + + std::set<std::string> &getOnlinePlayers() + { return mOnlinePlayers; } + + void setAllowUpdate(bool n) + { mAllowUpdate = n; } + + void optionChanged(const std::string &name); + +private: + void download(); + + void updateSize(); + + /** + * The thread function that download the files. + */ + static int downloadThread(void *ptr); + + /** + * A libcurl callback for writing to memory. + */ + static size_t memoryWrite(void *ptr, size_t size, size_t nmemb, + FILE *stream); + + const std::string prepareNick(std::string nick, int level, + std::string color) const; + enum DownloadStatus + { + UPDATE_ERROR = 0, + UPDATE_COMPLETE, + UPDATE_LIST + }; + + /** A thread that use libcurl to download updates. */ + SDL_Thread *mThread; + + /** Status of the current download. */ + DownloadStatus mDownloadStatus; + + /** Flag that show if current download is complete. */ + bool mDownloadComplete; + + /** Byte count currently downloaded in mMemoryBuffer. */ + int mDownloadedBytes; + + /** Buffer for files downloaded to memory. */ + char *mMemoryBuffer; + + /** Buffer to handler human readable error provided by curl. */ + char *mCurlError; + + BrowserBox *mBrowserBox; + ScrollArea *mScrollArea; + time_t mUpdateTimer; + std::set<std::string> mOnlinePlayers; + + gcn::Button *mUpdateButton; + bool mAllowUpdate; + bool mShowLevel; + bool mUpdateOnlineList; +}; + +#endif diff --git a/src/gui/widgets/avatarlistbox.cpp b/src/gui/widgets/avatarlistbox.cpp new file mode 100644 index 000000000..d665c81ce --- /dev/null +++ b/src/gui/widgets/avatarlistbox.cpp @@ -0,0 +1,346 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/avatarlistbox.h" + +#include "actorspritemanager.h" +#include "configuration.h" +#include "graphics.h" +#include "guild.h" +#include "localplayer.h" + +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/viewport.h" +#include "gui/theme.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +#include <guichan/font.hpp> + +int AvatarListBox::instances = 0; +Image *AvatarListBox::onlineIcon = 0; +Image *AvatarListBox::offlineIcon = 0; + +AvatarListBox::AvatarListBox(AvatarListModel *model): + ListBox(model), + mShowGender(false), + mShowLevel(false) +{ + instances++; + + if (instances == 1) + { + onlineIcon = Theme::getImageFromTheme("circle-green.png"); + offlineIcon = Theme::getImageFromTheme("circle-gray.png"); + } + + setWidth(200); + + mShowGender = config.getBoolValue("showgender"); + mShowLevel = config.getBoolValue("showlevel"); + + config.addListener("showgender", this); + config.addListener("showlevel", this); +} + +AvatarListBox::~AvatarListBox() +{ + config.removeListener("showgender", this); + config.removeListener("showlevel", this); + + instances--; + + if (instances == 0) + { + if (onlineIcon) + onlineIcon->decRef(); + if (offlineIcon) + offlineIcon->decRef(); + } +} + +void AvatarListBox::draw(gcn::Graphics *gcnGraphics) +{ + if (!mListModel || !player_node) + return; + + AvatarListModel *model = static_cast<AvatarListModel*>(mListModel); +// Guild *guild = dynamic_cast<Guild*>(model); + + updateAlpha(); + + Graphics *graphics = static_cast<Graphics*>(gcnGraphics); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast<int>(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + const int fontHeight = getFont()->getHeight(); + + Widget *parent = getParent(); + + const std::string name = player_node->getName(); + + // Draw the list elements + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + for (int i = 0, y = 0; + i < model->getNumberOfElements(); + ++i, y += fontHeight) + { + Avatar *a = model->getAvatarAt(i); + if (!a) + continue; + + // Draw online status + Image *icon = a->getOnline() ? onlineIcon : offlineIcon; + if (icon) + graphics->drawImage(icon, 2, y + 1); + + if (a->getDisplayBold()) + graphics->setFont(boldFont); + + std::string text; + + if (a->getMaxHp() > 0) + { + if (mShowLevel && a->getLevel() > 1) + { + text = strprintf("%s %d/%d (%d)", a->getComplexName().c_str(), + a->getHp(), a->getMaxHp(), a->getLevel()); + } + else + { + text = strprintf("%s %d/%d", a->getComplexName().c_str(), + a->getHp(), a->getMaxHp()); + } + if (parent && a->getMaxHp()) + { + gcn::Color color = Theme::getProgressColor( + Theme::PROG_HP, static_cast<float>(a->getHp()) + / static_cast<float>(a->getMaxHp())); + color.a = 80; + graphics->setColor(color); + + graphics->fillRectangle(gcn::Rectangle(0, y, + parent->getWidth() * a->getHp() / a->getMaxHp(), + fontHeight)); + } + } + else if (a->getDamageHp() != 0 && a->getName() != name) + { + if (mShowLevel && a->getLevel() > 1) + { + text = strprintf("%s -%d (%d)", a->getComplexName().c_str(), + a->getDamageHp(), a->getLevel()); + } + else + { + text = strprintf("%s -%d", a->getComplexName().c_str(), + a->getDamageHp()); + } + + if (parent) + { +// int diff; +// if (a->getDamageHp() > 1024) +// diff = 0; +// else +// diff = 1024 - a->getDamageHp(); + gcn::Color color = Theme::getProgressColor(Theme::PROG_HP, + 1); +// 0 / 1024); +/* + if (a->getDamageHp() >= 400) + { + } + else + { +// int intens = 1024/(400 - a->getDamageHp()); + int intens = a->getDamageHp() / 1024; + if (intens > 1) + intens = 1; + color = Theme::getProgressColor(Theme::PROG_HP, + intens); + } +*/ + + color.a = 80; + graphics->setColor(color); + graphics->fillRectangle(gcn::Rectangle(0, y, + parent->getWidth() * a->getDamageHp() / 1024, + fontHeight)); + + if (a->getLevel() > 1) + { + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + int minHp = 40 + ((a->getLevel() - 1) * 5); + if (minHp < 0) + minHp = 40; + + graphics->drawLine(parent->getWidth()*minHp / 1024, y, + parent->getWidth() * minHp / 1024, y + fontHeight); + } + } + } + else + { + if (mShowLevel && a->getLevel() > 1) + { + text = strprintf("%s (%d)", a->getComplexName().c_str(), + a->getLevel()); + } + else + { + text = a->getComplexName(); + } + } + + if (!a->getMap().empty()) + { + if (a->getX() != -1) + { + text += strprintf(" [%d,%d %s]", a->getX(), a->getY(), + a->getMap().c_str()); + } + else + { + text += strprintf(" [%s]", a->getMap().c_str()); + } + } + + if (mShowGender) + { + switch (a->getGender()) + { + case GENDER_FEMALE: + text += strprintf(" \u2640 %s", + a->getAdditionString().c_str()); + break; + case GENDER_MALE: + text += strprintf(" \u2642 %s", + a->getAdditionString().c_str()); + break; + default: + break; + } + } + else + { + text += a->getAdditionString(); + } + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + // Draw Name + graphics->drawText(text, 15, y); + + if (a->getDisplayBold()) + graphics->setFont(getFont()); + } + + setWidth(parent->getWidth() - 10); +} + +void AvatarListBox::mousePressed(gcn::MouseEvent &event) +{ + if (!actorSpriteManager || !player_node || !viewport + || !getFont()->getHeight()) + { + return; + } + + int y = event.getY() / getFont()->getHeight(); + if (!mListModel || y > mListModel->getNumberOfElements()) + return; + + setSelected(y); + distributeActionEvent(); + int selected = getSelected(); + AvatarListModel *model = static_cast<AvatarListModel*>(mListModel); + if (!model) + return; + Avatar *ava = model->getAvatarAt(selected); + if (!ava) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (ava->getType() == AVATAR_PLAYER) + { + Being* being = actorSpriteManager->findBeingByName(ava->getName(), + Being::PLAYER); + if (being) + actorSpriteManager->heal(player_node, being); + } + else + { + player_node->navigateTo(ava->getX(), ava->getY()); + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + if (ava->getType() == AVATAR_PLAYER) + { + Being* being = actorSpriteManager->findBeingByName( + model->getAvatarAt(selected)->getName(), Being::PLAYER); + if (being) + { + viewport->showPopup(event.getX(), event.getY(), being); + } + else + { + viewport->showPlayerPopup( + model->getAvatarAt(selected)->getName()); + } + } + else + { + Map *map = viewport->getMap(); + Avatar *ava = model->getAvatarAt(selected); + if (map && ava) + { + MapItem *mapItem = map->findPortalXY(ava->getX(), ava->getY()); + viewport->showPopup(mapItem); + } + } + } + + else if (event.getButton() == gcn::MouseEvent::MIDDLE) + { + if (ava->getType() == AVATAR_PLAYER && chatWindow) + { + chatWindow->addWhisperTab(model->getAvatarAt(selected) + ->getName(), true); + } + } +} + +void AvatarListBox::optionChanged(const std::string &value) +{ + if (value == "showgender") + mShowGender = config.getBoolValue("showgender"); + else if (value == "showlevel") + mShowLevel = config.getBoolValue("showlevel"); +}
\ No newline at end of file diff --git a/src/gui/widgets/avatarlistbox.h b/src/gui/widgets/avatarlistbox.h new file mode 100644 index 000000000..c7bc11f7c --- /dev/null +++ b/src/gui/widgets/avatarlistbox.h @@ -0,0 +1,70 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_GUILDLISTBOX_H +#define GUI_GUILDLISTBOX_H + +#include "avatar.h" + +#include "configlistener.h" + +#include "gui/widgets/listbox.h" + +#include <map> +#include <string> +#include <vector> + +class Image; + +class AvatarListModel : public gcn::ListModel +{ +public: + virtual Avatar *getAvatarAt(int i) = 0; + + std::string getElementAt(int i) + { return getAvatarAt(i)->getName(); } +}; + +class AvatarListBox : public ListBox, public ConfigListener +{ +public: + AvatarListBox(AvatarListModel *model); + + ~AvatarListBox(); + + /** + * Draws the list box. + */ + void draw(gcn::Graphics *gcnGraphics); + + void mousePressed(gcn::MouseEvent &event); + + void optionChanged(const std::string &value); + +private: + bool mShowGender; + bool mShowLevel; + + static int instances; + static Image *onlineIcon; + static Image *offlineIcon; +}; + +#endif diff --git a/src/gui/widgets/battletab.cpp b/src/gui/widgets/battletab.cpp new file mode 100644 index 000000000..68f1a0453 --- /dev/null +++ b/src/gui/widgets/battletab.cpp @@ -0,0 +1,54 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/battletab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "net/net.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +BattleTab::BattleTab() : + ChatTab(_("Battle")) +{ + loadFromLogFile("#Battle"); +} + +BattleTab::~BattleTab() +{ +} + +void BattleTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log(std::string("#Battle"), std::string(msg)); +} diff --git a/src/gui/widgets/battletab.h b/src/gui/widgets/battletab.h new file mode 100644 index 000000000..fdfe626f0 --- /dev/null +++ b/src/gui/widgets/battletab.h @@ -0,0 +1,47 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BATTLETAB_H +#define BATTLETAB_H + +#include "gui/widgets/chattab.h" + +/** + * A tab for a party chat channel. + */ +class BattleTab : public ChatTab +{ + public: + BattleTab(); + + ~BattleTab(); + + int getType() const + { return ChatTab::TAB_BATTLE; } + + void saveToLogFile(std::string &msg); +}; + +extern BattleTab *battleChatTab; +#endif + + + diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp new file mode 100644 index 000000000..acb182c3c --- /dev/null +++ b/src/gui/widgets/browserbox.cpp @@ -0,0 +1,534 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/browserbox.h" + +#include "client.h" +#include "log.h" + +#include "utils/stringutils.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "gui/widgets/linkhandler.h" + +#include <guichan/graphics.hpp> +#include <guichan/font.hpp> +#include <guichan/cliprectangle.hpp> + +#include <algorithm> + +BrowserBox::BrowserBox(unsigned int mode, bool opaque): + gcn::Widget(), + mMode(mode), mHighMode(UNDERLINE | BACKGROUND), + mOpaque(opaque), + mUseLinksAndUserColors(true), + mSelectedLink(-1), + mMaxRows(0), + mHeight(0), + mWidth(0), + mYStart(0), + mUpdateTime(-1), + mAlwaysUpdate(true) +{ + setFocusable(true); + addMouseListener(this); +} + +BrowserBox::~BrowserBox() +{ +} + +void BrowserBox::setLinkHandler(LinkHandler* linkHandler) +{ + mLinkHandler = linkHandler; +} + +void BrowserBox::setOpaque(bool opaque) +{ + mOpaque = opaque; +} + +void BrowserBox::setHighlightMode(unsigned int highMode) +{ + mHighMode = highMode; +} + +void BrowserBox::addRow(const std::string &row, bool atTop) +{ + std::string tmp = row; + std::string newRow; + std::string::size_type idx1, idx2, idx3; + gcn::Font *font = getFont(); + + // Use links and user defined colors + if (mUseLinksAndUserColors) + { + BROWSER_LINK bLink; + + // Check for links in format "@@link|Caption@@" + idx1 = tmp.find("@@"); + while (idx1 != std::string::npos) + { + idx2 = tmp.find("|", idx1); + idx3 = tmp.find("@@", idx2); + + if (idx2 == std::string::npos || idx3 == std::string::npos) + break; + bLink.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2)); + bLink.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1)); + bLink.y1 = static_cast<int>(mTextRows.size()) * font->getHeight(); + bLink.y2 = bLink.y1 + font->getHeight(); + + newRow += tmp.substr(0, idx1); + + std::string tmp2 = newRow; + idx1 = tmp2.find("##"); + while (idx1 != std::string::npos) + { + tmp2.erase(idx1, 3); + idx1 = tmp2.find("##"); + } + bLink.x1 = font->getWidth(tmp2) - 1; + bLink.x2 = bLink.x1 + font->getWidth(bLink.caption) + 1; + + mLinks.push_back(bLink); + + newRow += "##<" + bLink.caption; + + tmp.erase(0, idx3 + 2); + if (!tmp.empty()) + newRow += "##>"; + + idx1 = tmp.find("@@"); + } + + newRow += tmp; + } + // Don't use links and user defined colors + else + { + newRow = row; + } + + if (atTop) + mTextRows.push_front(newRow); + else + mTextRows.push_back(newRow); + + //discard older rows when a row limit has been set + if (mMaxRows > 0) + { + while (mTextRows.size() > mMaxRows) + { + mTextRows.pop_front(); + for (unsigned int i = 0; i < mLinks.size(); i++) + { + mLinks[i].y1 -= font->getHeight(); + mLinks[i].y2 -= font->getHeight(); + + if (mLinks[i].y1 < 0) + mLinks.erase(mLinks.begin() + i); + } + } + } + + // Auto size mode + if (mMode == AUTO_SIZE) + { + std::string plain = newRow; + for (idx1 = plain.find("##"); + idx1 != std::string::npos; + idx1 = plain.find("##")) + { + plain.erase(idx1, 3); + } + + // Adjust the BrowserBox size + int w = font->getWidth(plain); + if (w > getWidth()) + setWidth(w); + } + + if (mMode == AUTO_WRAP) + { + unsigned int y = 0; + unsigned int nextChar; + const char *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + int x = 0; + + for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + { + std::string row = *i; + for (unsigned int j = 0; j < row.size(); j++) + { + std::string character = row.substr(j, 1); + x += font->getWidth(character); + nextChar = j + 1; + + // Wraping between words (at blank spaces) + if ((nextChar < row.size()) && (row.at(nextChar) == ' ')) + { + int nextSpacePos = static_cast<int>( + row.find(" ", (nextChar + 1))); + if (nextSpacePos <= 0) + nextSpacePos = static_cast<int>(row.size()) - 1; + + int nextWordWidth = font->getWidth( + row.substr(nextChar, + (nextSpacePos - nextChar))); + + if ((x + nextWordWidth + 10) > getWidth()) + { + x = 15; // Ident in new line + y += 1; + j++; + } + } + // Wrapping looong lines (brutal force) + else if ((x + 2 * hyphenWidth) > getWidth()) + { + x = 15; // Ident in new line + y += 1; + } + } + } + + setHeight(font->getHeight() * (static_cast<int>( + mTextRows.size()) + y)); + } + else + { + setHeight(font->getHeight() * static_cast<int>(mTextRows.size())); + } + mUpdateTime = 0; + updateHeight(); +} + +void BrowserBox::clearRows() +{ + mTextRows.clear(); + mLinks.clear(); + setWidth(0); + setHeight(0); + mSelectedLink = -1; + mUpdateTime = 0; + updateHeight(); +} + +struct MouseOverLink +{ + MouseOverLink(int x, int y) : mX(x), mY(y) + { } + + bool operator() (BROWSER_LINK &link) + { + return (mX >= link.x1 && mX < link.x2 && + mY >= link.y1 && mY < link.y2); + } + int mX, mY; +}; + +void BrowserBox::mousePressed(gcn::MouseEvent &event) +{ + if (!mLinkHandler) + return; + + LinkIterator i = find_if(mLinks.begin(), mLinks.end(), + MouseOverLink(event.getX(), event.getY())); + + if (i != mLinks.end()) + mLinkHandler->handleLink(i->link, &event); +} + +void BrowserBox::mouseMoved(gcn::MouseEvent &event) +{ + LinkIterator i = find_if(mLinks.begin(), mLinks.end(), + MouseOverLink(event.getX(), event.getY())); + + mSelectedLink = (i != mLinks.end()) + ? static_cast<int>(i - mLinks.begin()) : -1; +} + +void BrowserBox::draw(gcn::Graphics *graphics) +{ + gcn::ClipRectangle cr = graphics->getCurrentClipArea(); + mYStart = cr.y - cr.yOffset; + int yEnd = mYStart + cr.height; + if (mYStart < 0) + mYStart = 0; + + if (getWidth() != mWidth) + updateHeight(); + + if (mOpaque) + { + graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND)); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (mSelectedLink >= 0 && mSelectedLink < (signed)mLinks.size()) + { + if ((mHighMode & BACKGROUND)) + { + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT)); + graphics->fillRectangle(gcn::Rectangle( + mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y1, + mLinks[mSelectedLink].x2 - mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y2 - mLinks[mSelectedLink].y1 + )); + } + + if ((mHighMode & UNDERLINE)) + { + graphics->setColor(Theme::getThemeColor(Theme::HYPERLINK)); + graphics->drawLine( + mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y2, + mLinks[mSelectedLink].x2, + mLinks[mSelectedLink].y2); + } + } + + gcn::Font *font = getFont(); + + for (LinePartIterator i = mLineParts.begin(); + i != mLineParts.end(); + i ++) + { + const LinePart &part = *i; + if (part.mY + 50 < mYStart) + continue; + if (part.mY > yEnd) + break; + graphics->setColor(part.mColor); + font->drawString(graphics, part.mText, part.mX, part.mY); + } + + return; +} + +int BrowserBox::calcHeight() +{ + int x = 0, y = 0; + int wrappedLines = 0; + int link = 0; + gcn::Font *font = getFont(); + + int fontHeight = font->getHeight(); + int fontWidthMinus = font->getWidth("-"); + char const *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + + gcn::Color selColor = Theme::getThemeColor(Theme::TEXT); + const gcn::Color textColor = Theme::getThemeColor(Theme::TEXT); + + mLineParts.clear(); + + for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + { + const std::string row = *(i); + bool wrapped = false; + x = 0; + + // Check for separator lines + if (row.find("---", 0) == 0) + { + const int dashWidth = fontWidthMinus; + for (x = 0; x < getWidth(); x++) + { + mLineParts.push_back(LinePart(x, y, selColor, "-")); + x += dashWidth - 2; + } + + y += fontHeight; + continue; + } + + gcn::Color prevColor = selColor; + + // TODO: Check if we must take texture size limits into account here + // TODO: Check if some of the O(n) calls can be removed + for (std::string::size_type start = 0, end = std::string::npos; + start != std::string::npos; + start = end, end = std::string::npos) + { + // Wrapped line continuation shall be indented + if (wrapped) + { + y += fontHeight; + x = 15; + wrapped = false; + } + + // "Tokenize" the string at control sequences + if (mUseLinksAndUserColors) + end = row.find("##", start + 1); + + if (mUseLinksAndUserColors || + (!mUseLinksAndUserColors && (start == 0))) + { + // Check for color change in format "##x", x = [L,P,0..9] + if (row.find("##", start) == start && row.size() > start + 2) + { + const char c = row.at(start + 2); + + bool valid; + const gcn::Color col = Theme::getThemeColor(c, valid); + + if (c == '>') + { + selColor = prevColor; + } + else if (c == '<') + { +// link++; + prevColor = selColor; + selColor = col; + } + else if (valid) + { + selColor = col; + } + else + { + + switch (c) + { + case '1': selColor = RED; break; + case '2': selColor = GREEN; break; + case '3': selColor = BLUE; break; + case '4': selColor = ORANGE; break; + case '5': selColor = YELLOW; break; + case '6': selColor = PINK; break; + case '7': selColor = PURPLE; break; + case '8': selColor = GRAY; break; + case '9': selColor = BROWN; break; + case '0': + default: + selColor = textColor; + } + } + + if (c == '<' && link < (signed)mLinks.size()) + { + const int size = + font->getWidth(mLinks[link].caption) + 1; + + mLinks[link].x1 = x; + mLinks[link].y1 = y; + mLinks[link].x2 = mLinks[link].x1 + size; + mLinks[link].y2 = y + fontHeight - 1; + link++; + } + start += 3; + + if (start == row.size()) + break; + } + } + + std::string::size_type len = + end == std::string::npos ? end : end - start; + + if (start >= row.length()) + break; + + std::string part = row.substr(start, len); + + // Auto wrap mode + if (mMode == AUTO_WRAP && getWidth() > 0 + && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()) + { + bool forced = false; + + /* FIXME: This code layout makes it easy to crash remote + clients by talking garbage. Forged long utf-8 characters + will cause either a buffer underflow in substr or an + infinite loop in the main loop. */ + do + { + if (!forced) + end = row.rfind(' ', end); + + // Check if we have to (stupidly) force-wrap + if (end == std::string::npos || end <= start) + { + forced = true; + end = row.size(); + x += hyphenWidth; // Account for the wrap-notifier + continue; + } + + // Skip to the start of the current character + while ((row[end] & 192) == 128) + end--; + end--; // And then to the last byte of the previous one + + part = row.substr(start, end - start + 1); + } + while (end > start && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()); + + if (forced) + { + x -= hyphenWidth; // Remove the wrap-notifier accounting + mLineParts.push_back(LinePart(getWidth() - hyphenWidth, + y, selColor, hyphen)); + end++; // Skip to the next character + } + else + { + end += 2; // Skip to after the space + } + + wrapped = true; + wrappedLines++; + } + + mLineParts.push_back(LinePart(x, y, selColor, part.c_str())); + + if (mMode == AUTO_WRAP && font->getWidth(part) == 0) + break; + + x += font->getWidth(part); + } + y += fontHeight; + } + return (static_cast<int>(mTextRows.size()) + wrappedLines) * fontHeight; +} + +void BrowserBox::updateHeight() +{ + if (mAlwaysUpdate || mUpdateTime != cur_time + || mTextRows.size() < 3 || !mUpdateTime) + { + mWidth = getWidth(); + mHeight = calcHeight(); + setHeight(mHeight); + mUpdateTime = cur_time; + } +} diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h new file mode 100644 index 000000000..cd9cc92de --- /dev/null +++ b/src/gui/widgets/browserbox.h @@ -0,0 +1,205 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BROWSERBOX_H +#define BROWSERBOX_H + +#include <guichan/mouselistener.hpp> +#include <guichan/widget.hpp> + +#include <list> +#include <vector> + +class LinkHandler; + +struct BROWSER_LINK +{ + int x1, x2, y1, y2; /**< Where link is placed */ + std::string link; + std::string caption; +}; + +class LinePart +{ + public: + LinePart(int x, int y, gcn::Color color, std::string text) : + mX(x), mY(y), mColor(color), mText(text) + { + } + + int mX, mY; + gcn::Color mColor; + std::string mText; +}; + +/** + * A simple browser box able to handle links and forward events to the + * parent conteiner. + */ +class BrowserBox : public gcn::Widget, + public gcn::MouseListener +{ + public: + /** + * Constructor. + */ + BrowserBox(unsigned int mode = AUTO_SIZE, bool opaque = true); + + /** + * Destructor. + */ + ~BrowserBox(); + + /** + * Sets the handler for links. + */ + void setLinkHandler(LinkHandler *linkHandler); + + /** + * Sets the BrowserBox opacity. + */ + void setOpaque(bool opaque); + + /** + * Sets the Highlight mode for links. + */ + void setHighlightMode(unsigned int highMode); + + /** + * Sets the maximum numbers of rows in the browser box. 0 = no limit. + */ + void setMaxRow(unsigned max) {mMaxRows = max; }; + + /** + * Disable links & user defined colors to be used in chat input. + */ +/* + void disableLinksAndUserColors(); +*/ + /** + * Adds a text row to the browser. + */ + void addRow(const std::string &row, bool atTop = false); + + /** + * Remove all rows. + */ + void clearRows(); + +// void setSize(int width, int height); + +// void widgetResized(const gcn::Event &event); + + /** + * Handles mouse actions. + */ + void mousePressed(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + /** + * Draws the browser box. + */ + void draw(gcn::Graphics *graphics); + + void updateHeight(); + +// void widgetResized(const gcn::Event &event); + + /** + * BrowserBox modes. + */ + enum + { + AUTO_SIZE = 0, + AUTO_WRAP /**< Maybe it needs a fix or to be redone. */ + }; + + /** + * BrowserBox colors. + * + * NOTES (by Javila): + * - color values is "0x" prefix followed by HTML color style. + * - we can add up to 10 different colors: [0..9]. + * - not all colors will be fine with all backgrounds due transparent + * windows and widgets. So, I think it's better keep BrowserBox + * opaque (white background) by default. + */ + enum + { + RED = 0xff0000, /**< Color 1 */ + GREEN = 0x009000, /**< Color 2 */ + BLUE = 0x0000ff, /**< Color 3 */ + ORANGE = 0xe0980e, /**< Color 4 */ + YELLOW = 0xf1dc27, /**< Color 5 */ + PINK = 0xff00d8, /**< Color 6 */ + PURPLE = 0x8415e2, /**< Color 7 */ + GRAY = 0x919191, /**< Color 8 */ + BROWN = 0x8e4c17 /**< Color 9 */ + }; + + /** + * Highlight modes for links. + * This can be used for a bitmask. + */ + enum + { + UNDERLINE = 1, + BACKGROUND = 2 + }; + + typedef std::list<std::string> TextRows; + + TextRows &getRows() + { return mTextRows; } + + void setAlwaysUpdate(bool n) + { mAlwaysUpdate = n; } + + private: + int calcHeight(); + + typedef TextRows::iterator TextRowIterator; + TextRows mTextRows; + + typedef std::list<LinePart> LinePartList; + typedef LinePartList::iterator LinePartIterator; + LinePartList mLineParts; + + typedef std::vector<BROWSER_LINK> Links; + typedef Links::iterator LinkIterator; + Links mLinks; + + LinkHandler *mLinkHandler; + unsigned int mMode; + unsigned int mHighMode; + bool mOpaque; + bool mUseLinksAndUserColors; + int mSelectedLink; + unsigned int mMaxRows; + int mHeight; + int mWidth; + int mYStart; + int mUpdateTime; + bool mAlwaysUpdate; +}; + +#endif diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp new file mode 100644 index 000000000..3445928a1 --- /dev/null +++ b/src/gui/widgets/button.cpp @@ -0,0 +1,227 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/button.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include <guichan/exception.hpp> +#include <guichan/font.hpp> + +int Button::mInstances = 0; +float Button::mAlpha = 1.0; + +enum +{ + BUTTON_STANDARD = 0, // 0 + BUTTON_HIGHLIGHTED, // 1 + BUTTON_PRESSED, // 2 + BUTTON_DISABLED, // 3 + BUTTON_COUNT // 4 - Must be last. +}; + +struct ButtonData +{ + char const *file; + int gridX; + int gridY; +}; + +static ButtonData const data[BUTTON_COUNT] = +{ + { "button.png", 0, 0 }, + { "buttonhi.png", 9, 4 }, + { "buttonpress.png", 16, 19 }, + { "button_disabled.png", 25, 23 } +}; + +ImageRect Button::button[BUTTON_COUNT]; + +Button::Button(): + mDescription(""), mClickCount(0) +{ + init(); +} + +Button::Button(const std::string &caption, const std::string &actionEventId, + gcn::ActionListener *listener): + gcn::Button(caption), + mDescription(""), mClickCount(0) +{ + init(); + setActionEventId(actionEventId); + + if (listener) + addActionListener(listener); +} + +void Button::init() +{ + setFrameSize(0); + + if (mInstances == 0) + { + // Load the skin + Image *btn[BUTTON_COUNT]; + + int a, x, y, mode; + + for (mode = 0; mode < BUTTON_COUNT; mode++) + { + btn[mode] = Theme::getImageFromTheme(data[mode].file); + if (!btn[mode]) + continue; + + a = 0; + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + button[mode].grid[a] = btn[mode]->getSubImage( + data[x].gridX, data[y].gridY, + data[x + 1].gridX - data[x].gridX + 1, + data[y + 1].gridY - data[y].gridY + 1); + a++; + } + } + if (btn[mode]) + btn[mode]->decRef(); + } + updateAlpha(); + } + mInstances++; +} + +Button::~Button() +{ + mInstances--; + + if (mInstances == 0) + { + for (int mode = 0; mode < BUTTON_COUNT; mode++) + { + if (button[mode].grid) + { + for_each(button[mode].grid, + button[mode].grid + 9, dtor<Image*>()); + } + } + } +} + +void Button::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + if (button[BUTTON_DISABLED].grid[a]) + button[BUTTON_DISABLED].grid[a]->setAlpha(mAlpha); + if (button[BUTTON_PRESSED].grid[a]) + button[BUTTON_PRESSED].grid[a]->setAlpha(mAlpha); + if (button[BUTTON_HIGHLIGHTED].grid[a]) + button[BUTTON_HIGHLIGHTED].grid[a]->setAlpha(mAlpha); + if (button[BUTTON_STANDARD].grid[a]) + button[BUTTON_STANDARD].grid[a]->setAlpha(mAlpha); + } + } +} + +void Button::draw(gcn::Graphics *graphics) +{ + int mode; + + if (!isEnabled()) + mode = BUTTON_DISABLED; + else if (isPressed()) + mode = BUTTON_PRESSED; + else if (mHasMouse || isFocused()) + mode = BUTTON_HIGHLIGHTED; + else + mode = BUTTON_STANDARD; + + updateAlpha(); + + static_cast<Graphics*>(graphics)-> + drawImageRect(0, 0, getWidth(), getHeight(), button[mode]); + + if (mode == BUTTON_DISABLED) + graphics->setColor(Theme::getThemeColor(Theme::BUTTON_DISABLED)); + else + graphics->setColor(Theme::getThemeColor(Theme::BUTTON)); + + int textX; + int textY = getHeight() / 2 - getFont()->getHeight() / 2; + + switch (getAlignment()) + { + default: + case gcn::Graphics::LEFT: + textX = 4; + break; + case gcn::Graphics::CENTER: + textX = getWidth() / 2; + break; + case gcn::Graphics::RIGHT: + textX = getWidth() - 4; + break; +// throw GCN_EXCEPTION("Button::draw. Unknown alignment."); + } + + graphics->setFont(getFont()); + + if (isPressed()) + graphics->drawText(getCaption(), textX + 1, textY + 1, getAlignment()); + else + graphics->drawText(getCaption(), textX, textY, getAlignment()); +} + +void Button::mouseReleased(gcn::MouseEvent& mouseEvent) +{ + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT + && mMousePressed && mHasMouse) + { + mMousePressed = false; + mClickCount = mouseEvent.getClickCount(); + distributeActionEvent(); + mouseEvent.consume(); + } + else if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + mMousePressed = false; + mClickCount = 0; + mouseEvent.consume(); + } +} diff --git a/src/gui/widgets/button.h b/src/gui/widgets/button.h new file mode 100644 index 000000000..301d02fbe --- /dev/null +++ b/src/gui/widgets/button.h @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include <guichan/widgets/button.hpp> +#include <guichan/mouseevent.hpp> + +class ImageRect; + +/** + * Button widget. Same as the Guichan button but with custom look. + * + * \ingroup GUI + */ +class Button : public gcn::Button +{ + public: + /** + * Default constructor. + */ + Button(); + + /** + * Constructor, sets the caption of the button to the given string and + * adds the given action listener. + */ + Button(const std::string &caption, const std::string &actionEventId, + gcn::ActionListener *listener); + + /** + * Destructor. + */ + ~Button(); + + /** + * Draws the button. + */ + void draw(gcn::Graphics *graphics); + + /** + * Update the alpha value to the button components. + */ + void updateAlpha(); + + virtual void mouseReleased(gcn::MouseEvent& mouseEvent); + + void setDescription(std::string text) + { mDescription = text; } + + std::string getDescription() + { return mDescription; } + + unsigned getClickCount() + { return mClickCount; } + + void setTag(int tag) + { mTag = tag; } + + int getTag() + { return mTag; } + + private: + void init(); + + static ImageRect button[4]; /**< Button state graphics */ + static int mInstances; /**< Number of button instances */ + static float mAlpha; + + std::string mDescription; + unsigned mClickCount; + int mTag; +}; + +#endif diff --git a/src/gui/widgets/channeltab.cpp b/src/gui/widgets/channeltab.cpp new file mode 100644 index 000000000..a7370a4c5 --- /dev/null +++ b/src/gui/widgets/channeltab.cpp @@ -0,0 +1,132 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "channeltab.h" + +#include "channel.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "utils/gettext.h" + +ChannelTab::ChannelTab(Channel *channel) : + ChatTab(channel->getName()), + mChannel(channel) +{ + channel->setTab(this); +} + +ChannelTab::~ChannelTab() +{ +} + +void ChannelTab::handleInput(const std::string &msg) +{ + Net::getChatHandler()->sendToChannel(getChannel()->getId(), msg); +} + +void ChannelTab::showHelp() +{ + chatLog(_("/users > Lists the users in the current channel")); + chatLog(_("/topic > Set the topic of the current channel")); + chatLog(_("/quit > Leave a channel")); + chatLog(_("/op > Make a user a channel operator")); + chatLog(_("/kick > Kick a user from the channel")); +} + +bool ChannelTab::handleCommand(const std::string &type, + const std::string &args) +{ + if (type == "help") + { + if (args == "users") + { + chatLog(_("Command: /users")); + chatLog(_("This command shows the users in this channel.")); + } + else if (args == "topic") + { + chatLog(_("Command: /topic <message>")); + chatLog(_("This command sets the topic to <message>.")); + } + else if (args == "quit") + { + chatLog(_("Command: /quit")); + chatLog(_("This command leaves the current channel.")); + chatLog(_("If you're the last person in the channel, " + "it will be deleted.")); + } + else if (args == "op") + { + chatLog(_("Command: /op <nick>")); + chatLog(_("This command makes <nick> a channel operator.")); + chatLog(_("If the <nick> has spaces in it, enclose it in " + "double quotes (\").")); + chatLog(_("Channel operators can kick and op other users " + "from the channel.")); + } + else if (args == "kick") + { + chatLog(_("Command: /kick <nick>")); + chatLog(_("This command makes <nick> leave the channel.")); + chatLog(_("If the <nick> has spaces in it, enclose it in " + "double quotes (\").")); + } + else + return false; + } + else if (type == "users") + { + Net::getChatHandler()->userList(mChannel->getName()); + } + else if (type == "topic") + { + Net::getChatHandler()->setChannelTopic(mChannel->getId(), args); + } + else if (type == "topic") + { + Net::getChatHandler()->setChannelTopic(mChannel->getId(), args); + } + else if (type == "quit") + { + Net::getChatHandler()->quitChannel(mChannel->getId()); + } + else if (type == "op") + { + // set the user mode 'o' to op a user + if (args != "") + Net::getChatHandler()->setUserMode(mChannel->getId(), args, 'o'); + else + chatLog(_("Need a user to op!"), BY_CHANNEL); + } + else if (type == "kick") + { + if (args != "") + Net::getChatHandler()->kickUser(mChannel->getId(), args); + else + chatLog(_("Need a user to kick!"), BY_CHANNEL); + } + else + return false; + + return true; +} diff --git a/src/gui/widgets/channeltab.h b/src/gui/widgets/channeltab.h new file mode 100644 index 000000000..842b80f7d --- /dev/null +++ b/src/gui/widgets/channeltab.h @@ -0,0 +1,62 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CHANNELTAB_H +#define CHANNELTAB_H + +#include "chattab.h" + +class Channel; + +/** + * A tab for a chat channel. + */ +class ChannelTab : public ChatTab +{ + public: + + Channel *getChannel() const { return mChannel; } + + void showHelp(); + + bool handleCommand(const std::string &type, + const std::string &args); + + protected: + friend class Channel; + + /** + * Constructor. + */ + ChannelTab(Channel *channel); + + /** + * Destructor. + */ + ~ChannelTab(); + + void handleInput(const std::string &msg); + + private: + Channel *mChannel; +}; + +#endif // CHANNELTAB_H diff --git a/src/gui/widgets/chattab.cpp b/src/gui/widgets/chattab.cpp new file mode 100644 index 000000000..06ba3d3ed --- /dev/null +++ b/src/gui/widgets/chattab.cpp @@ -0,0 +1,431 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/chattab.h" + +#include "actorspritemanager.h" +#include "chatlog.h" +#include "commandhandler.h" +#include "configuration.h" +#include "localplayer.h" +#include "log.h" +#include "sound.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/itemlinkhandler.h" +#include "gui/widgets/tradetab.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include <guichan/widgets/tabbedarea.hpp> + +#define MAX_WORD_SIZE 50 + +ChatTab::ChatTab(const std::string &name) : + Tab(), + mAllowHightlight(true) +{ + setCaption(name); + + mTextOutput = new BrowserBox(BrowserBox::AUTO_WRAP); + mTextOutput->setOpaque(false); + mTextOutput->setMaxRow((int) config.getIntValue("ChatLogLength")); + if (chatWindow) + mTextOutput->setLinkHandler(chatWindow->mItemLinkHandler); + mTextOutput->setAlwaysUpdate(false); + + mScrollArea = new ScrollArea(mTextOutput); + mScrollArea->setScrollPolicy(gcn::ScrollArea::SHOW_NEVER, + gcn::ScrollArea::SHOW_ALWAYS); + mScrollArea->setScrollAmount(0, 1); + mScrollArea->setOpaque(false); + + if (chatWindow) + chatWindow->addTab(this); +} + +ChatTab::~ChatTab() +{ + if (chatWindow) + chatWindow->removeTab(this); + + delete mTextOutput; + mTextOutput = 0; + delete mScrollArea; + mScrollArea = 0; +} + +void ChatTab::chatLog(std::string line, Own own, + bool ignoreRecord, bool tryRemoveColors) +{ + // Trim whitespace + trim(line); + + if (line.empty()) + return; + + if (tryRemoveColors && own == BY_OTHER && + config.getBoolValue("removeColors")) + { + line = removeColors(line); + if (line.empty()) + return; + } + + unsigned lineLim = config.getIntValue("chatMaxCharLimit"); + if (lineLim > 0 && line.length() > lineLim) + line = line.substr(0, lineLim); + + if (line.empty()) + return; + + CHATLOG tmp; + tmp.own = own; + tmp.nick = ""; + tmp.text = line; + + std::string::size_type pos = line.find(" : "); + if (pos != std::string::npos) + { + if (line.length() <= pos + 3) + return; + + tmp.nick = line.substr(0, pos); + tmp.text = line.substr(pos + 3); + } + else + { + // Fix the owner of welcome message. + if (line.length() > 7 && line.substr(0, 7) == "Welcome") + own = BY_SERVER; + } + + // *implements actions in a backwards compatible way* + if ((own == BY_PLAYER || own == BY_OTHER) && + tmp.text.at(0) == '*' && + tmp.text.at(tmp.text.length()-1) == '*') + { + tmp.text[0] = ' '; + tmp.text.erase(tmp.text.length() - 1); + own = ACT_IS; + } + + std::string lineColor = "##C"; + switch (own) + { + case BY_GM: + if (tmp.nick.empty()) + { + tmp.nick = std::string(_("Global announcement:")); + tmp.nick += " "; + lineColor = "##G"; + } + else + { + tmp.nick = strprintf(_("Global announcement from %s:"), + tmp.nick.c_str()); + tmp.nick += " "; + lineColor = "##1"; // Equiv. to BrowserBox::RED + } + break; + case BY_PLAYER: + tmp.nick += ": "; + lineColor = "##Y"; + break; + case BY_OTHER: + tmp.nick += ": "; + lineColor = "##C"; + break; + case BY_SERVER: + tmp.nick = _("Server:"); + tmp.nick += " "; + tmp.text = line; + lineColor = "##S"; + break; + case BY_CHANNEL: + tmp.nick = ""; + // TODO: Use a predefined color + lineColor = "##2"; // Equiv. to BrowserBox::GREEN + break; + case ACT_WHISPER: + tmp.nick = strprintf(_("%s whispers: %s"), tmp.nick.c_str(), ""); + lineColor = "##W"; + break; + case ACT_IS: + lineColor = "##I"; + break; + case BY_LOGGER: + tmp.nick = ""; + tmp.text = line; + lineColor = "##L"; + break; + default: + logger->log1("ChatTab::chatLog incorrect value in switch"); + break; + } + + if (tmp.nick == ": ") + { + tmp.nick = ""; + lineColor = "##S"; + } + + // if configured, move magic messages log to debug chat tab + if (localChatTab && this == localChatTab + && ((config.getBoolValue("showMagicInDebug") + && own == BY_PLAYER && tmp.text.length() > 1 + && tmp.text.length() > 1 && tmp.text.at(0) == '#' + && tmp.text.at(1) != '#') + || (config.getBoolValue("serverMsgInDebug") && (own == BY_SERVER + || tmp.nick.empty())))) + { + if (debugChatTab) + debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + return; + } + + // Get the current system time + time_t t; + time(&t); + + // Format the time string properly + std::stringstream timeStr; + timeStr << "[" << ((((t / 60) / 60) % 24 < 10) ? "0" : "") + << static_cast<int>(((t / 60) / 60) % 24) + << ":" << (((t / 60) % 60 < 10) ? "0" : "") + << static_cast<int>((t / 60) % 60) + << "] "; + + line = lineColor + timeStr.str() + tmp.nick + tmp.text; + + if (config.getBoolValue("enableChatLog")) + saveToLogFile(line); + + mTextOutput->setMaxRow(config.getIntValue("chatMaxLinesLimit")); + + // We look if the Vertical Scroll Bar is set at the max before + // adding a row, otherwise the max will always be a row higher + // at comparison. + if (mScrollArea->getVerticalScrollAmount() >= + mScrollArea->getVerticalMaxScroll()) + { + addRow(line); + mScrollArea->setVerticalScrollAmount( + mScrollArea->getVerticalMaxScroll()); + } + else + { + addRow(line); + } + + mScrollArea->logic(); + if (own != BY_PLAYER) + { + if (own == BY_SERVER && (getType() == TAB_PARTY + || getType() == TAB_GUILD)) + { + return; + } + + if (!getTabbedArea()) + return; + + if (this != getTabbedArea()->getSelectedTab()) + { + if (getFlash() == 0) + { + if (player_node) + { + std::string::size_type pos + = tmp.text.find(player_node->getName()); + if (pos != std::string::npos) + setFlash(2); + else + setFlash(1); + } + else + { + setFlash(1); + } + } + } + + if (getAllowHighlight() && (this != getTabbedArea()->getSelectedTab() + || (Client::getIsMinimized() || (!Client::getMouseFocused() + && !Client::getInputFocused())))) + { + if (own != BY_SERVER) + sound.playGuiSfx("system/newmessage.ogg"); + } + } +} + +void ChatTab::chatLog(const std::string &nick, std::string msg) +{ + Own byWho = (nick == player_node->getName() ? BY_PLAYER : BY_OTHER); + if (byWho == BY_OTHER && config.getBoolValue("removeColors")) + msg = removeColors(msg); + chatLog(nick + " : " + msg, byWho, false, false); +} + +void ChatTab::chatInput(const std::string &message) +{ + std::string msg = message; + trim(msg); + + if (msg.empty()) + return; + + // Check for item link + std::string::size_type start = msg.find('['); + while (start + 1 < msg.size() && start != std::string::npos + && msg[start + 1] != '@') + { + std::string::size_type end = msg.find(']', start); + if (start + 1 != end && end != std::string::npos) + { + // Catch multiple embeds and ignore them + // so it doesn't crash the client. + while ((msg.find('[', start + 1) != std::string::npos) && + (msg.find('[', start + 1) < end)) + { + start = msg.find('[', start + 1); + } + + std::string temp = ""; + if (start + 1 < msg.length() && end < msg.length() + && end > start + 1) + { + temp = msg.substr(start + 1, end - start - 1); + + const ItemInfo itemInfo = ItemDB::get(temp); + if (itemInfo.getId() != 0) + { + msg.insert(end, "@@"); + msg.insert(start + 1, "|"); + msg.insert(start + 1, toString(itemInfo.getId())); + msg.insert(start + 1, "@@"); + } + } + } + start = msg.find('[', start + 1); + } + + // Prepare ordinary message + if (msg[0] != '/') + handleInput(msg); + else + handleCommand(std::string(msg, 1)); +} + +void ChatTab::scroll(int amount) +{ + int range = mScrollArea->getHeight() / 8 * amount; + gcn::Rectangle scr; + scr.y = mScrollArea->getVerticalScrollAmount() + range; + scr.height = abs(range); + mTextOutput->showPart(scr); +} + +void ChatTab::clearText() +{ + mTextOutput->clearRows(); +} + +void ChatTab::handleInput(const std::string &msg) +{ + if (chatWindow) + Net::getChatHandler()->talk(chatWindow->doReplace(msg)); + else + Net::getChatHandler()->talk(msg); +} + +void ChatTab::handleCommand(const std::string &msg) +{ + if (commandHandler) + commandHandler->handleCommands(msg, this); +} + +bool ChatTab::handleCommands(const std::string &type, const std::string &args) +{ + // need split to commands and call each + + return handleCommand(type, args); +} + +void ChatTab::saveToLogFile(std::string &msg) +{ + if (getType() == TAB_INPUT && chatLogger) + chatLogger->log(msg); +} + +int ChatTab::getType() const +{ + if (getCaption() == "General" || getCaption() == _("General")) + return TAB_INPUT; + else if (getCaption() == "Debug" || getCaption() == _("Debug")) + return TAB_DEBUG; + else + return TAB_UNKNOWN; +} + +void ChatTab::addRow(std::string &line) +{ + std::string::size_type idx = 0; + + for (unsigned int f = 0; f < line.length(); f++) + { + if (line.at(f) == ' ') + { + idx = f; + } + else if (f - idx > MAX_WORD_SIZE) + { + line.insert(f, " "); + idx = f; + } + } + mTextOutput->addRow(line); +} + +void ChatTab::loadFromLogFile(std::string name) +{ + if (chatLogger) + { + std::list<std::string> list; + chatLogger->loadLast(name, list, 5); + std::list<std::string>::iterator i = list.begin(); + while (i != list.end()) + { + std::string line = "##9" + *i; + addRow(line); + ++i; + } + } +} diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h new file mode 100644 index 000000000..ddc36d29c --- /dev/null +++ b/src/gui/widgets/chattab.h @@ -0,0 +1,173 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CHATTAB_H +#define CHATTAB_H + +#include "gui/chat.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/tab.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ScrollArea; + +/** + * A tab for the chat window. This is special to ease chat handling. + */ +class ChatTab : public Tab +{ + public: + enum Type + { + TAB_UNKNOWN = 0, + TAB_INPUT, + TAB_WHISPER, + TAB_PARTY, + TAB_GUILD, + TAB_DEBUG, + TAB_TRADE, + TAB_BATTLE + }; + + /** + * Constructor. + */ + ChatTab(const std::string &name); + ~ChatTab(); + + /** + * Adds a line of text to our message list. Parameters: + * + * @param line Text message. + * @param own Type of message (usually the owner-type). + * @param channelName which channel to send the message to. + * @param ignoreRecord should this not be recorded? + * @param removeColors try remove color if configured + */ + void chatLog(std::string line, Own own = BY_SERVER, + bool ignoreRecord = false, bool tryRemoveColors = true); + + /** + * Adds the text to the message list + * + * @param msg The message text which is to be sent. + */ + void chatLog(const std::string &nick, std::string msg); + + /** + * Determines whether the message is a command or message, then + * sends the given message to the game server to be said, or to the + * command handler + * + * @param msg The message text which is to be sent. + */ + void chatInput(const std::string &msg); + + /** + * Scrolls the chat window + * + * @param amount direction and amount to scroll. Negative numbers scroll + * up, positive numbers scroll down. The absolute amount indicates the + * amount of 1/8ths of chat window real estate that should be scrolled. + */ + void scroll(int amount); + + /** + * Clears the text from the tab + */ + void clearText(); + + /** + * Add any extra help text to the output. Allows tabs to define help + * for commands defined by the tab itself. + */ + virtual void showHelp() {} + + /** + * Handle special commands. Allows a tab to handle commands it + * defines itself. + * + * @returns true if the command was handled + * false if the command was not handled + */ + virtual bool handleCommand(const std::string &type _UNUSED_, + const std::string &args _UNUSED_) + { return false; } + + /** + * Handle special commands. Allows a tab to handle commands it + * defines itself. + * + * @returns true if the command was handled + * false if the command was not handled + */ + virtual bool handleCommands(const std::string &type, + const std::string &args); + + /** + * Returns type of the being. + */ + virtual int getType() const; + + virtual void saveToLogFile(std::string &msg); + + std::list<std::string> &getRows() + { return mTextOutput->getRows(); } + + void loadFromLogFile(std::string name); + + bool getAllowHighlight() + { return mAllowHightlight; } + + void setAllowHighlight(bool n) + { mAllowHightlight = n; } + + protected: + friend class ChatWindow; + friend class WhisperWindow; + + virtual void setCurrent() + { setFlash(false); } + + virtual void handleInput(const std::string &msg); + + virtual void handleCommand(const std::string &msg); + + virtual void getAutoCompleteList(std::vector<std::string>&) const + {} + + void addRow(std::string &line); + + ScrollArea *mScrollArea; + BrowserBox *mTextOutput; + bool mAllowHightlight; +}; + +extern ChatTab *localChatTab; +extern ChatTab *debugChatTab; + +#endif // CHATTAB_H diff --git a/src/gui/widgets/checkbox.cpp b/src/gui/widgets/checkbox.cpp new file mode 100644 index 000000000..01331ddba --- /dev/null +++ b/src/gui/widgets/checkbox.cpp @@ -0,0 +1,187 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/checkbox.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include <guichan/actionlistener.hpp> + +int CheckBox::instances = 0; +float CheckBox::mAlpha = 1.0; +Image *CheckBox::checkBoxNormal; +Image *CheckBox::checkBoxChecked; +Image *CheckBox::checkBoxDisabled; +Image *CheckBox::checkBoxDisabledChecked; +Image *CheckBox::checkBoxNormalHi; +Image *CheckBox::checkBoxCheckedHi; + +CheckBox::CheckBox(const std::string &caption, bool selected, + gcn::ActionListener* listener, std::string eventId): + gcn::CheckBox(caption, selected), + mHasMouse(false) +{ + if (instances == 0) + { + Image *checkBox = Theme::getImageFromTheme("checkbox.png"); + if (checkBox) + { + checkBoxNormal = checkBox->getSubImage(0, 0, 9, 10); + checkBoxChecked = checkBox->getSubImage(9, 0, 9, 10); + checkBoxDisabled = checkBox->getSubImage(18, 0, 9, 10); + checkBoxDisabledChecked = checkBox->getSubImage(27, 0, 9, 10); + checkBoxNormalHi = checkBox->getSubImage(36, 0, 9, 10); + checkBoxCheckedHi = checkBox->getSubImage(45, 0, 9, 10); + checkBoxNormal->setAlpha(mAlpha); + checkBoxChecked->setAlpha(mAlpha); + checkBoxDisabled->setAlpha(mAlpha); + checkBoxDisabledChecked->setAlpha(mAlpha); + checkBoxNormalHi->setAlpha(mAlpha); + checkBoxCheckedHi->setAlpha(mAlpha); + checkBox->decRef(); + } + else + { + checkBoxNormal = 0; + checkBoxChecked = 0; + checkBoxDisabled = 0; + checkBoxDisabledChecked = 0; + checkBoxNormalHi = 0; + checkBoxCheckedHi = 0; + } + } + + instances++; + + if (!eventId.empty()) + setActionEventId(eventId); + + if (listener) + addActionListener(listener); +} + +CheckBox::~CheckBox() +{ + instances--; + + if (instances == 0) + { + delete checkBoxNormal; + checkBoxNormal = 0; + delete checkBoxChecked; + checkBoxChecked = 0; + delete checkBoxDisabled; + checkBoxDisabled = 0; + delete checkBoxDisabledChecked; + checkBoxDisabledChecked = 0; + delete checkBoxNormalHi; + checkBoxNormalHi = 0; + delete checkBoxCheckedHi; + checkBoxCheckedHi = 0; + } +} + +void CheckBox::draw(gcn::Graphics* graphics) +{ + drawBox(graphics); + + graphics->setFont(getFont()); + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + const int h = getHeight() + getHeight() / 2; + + graphics->drawText(getCaption(), h - 2, 0); +} + +void CheckBox::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + if (checkBoxNormal) + checkBoxNormal->setAlpha(mAlpha); + if (checkBoxChecked) + checkBoxChecked->setAlpha(mAlpha); + if (checkBoxDisabled) + checkBoxDisabled->setAlpha(mAlpha); + if (checkBoxDisabledChecked) + checkBoxDisabledChecked->setAlpha(mAlpha); + if (checkBoxNormal) + checkBoxNormal->setAlpha(mAlpha); + if (checkBoxCheckedHi) + checkBoxCheckedHi->setAlpha(mAlpha); + } +} + +void CheckBox::drawBox(gcn::Graphics* graphics) +{ + Image *box; + + if (isEnabled()) + { + if (isSelected()) + { + if (mHasMouse) + box = checkBoxCheckedHi; + else + box = checkBoxChecked; + } + else + { + if (mHasMouse) + box = checkBoxNormalHi; + else + box = checkBoxNormal; + } + } + else + { + if (isSelected()) + box = checkBoxDisabledChecked; + else + box = checkBoxDisabled; + } + + updateAlpha(); + + if (box) + static_cast<Graphics*>(graphics)->drawImage(box, 2, 2); +} + +void CheckBox::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void CheckBox::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} diff --git a/src/gui/widgets/checkbox.h b/src/gui/widgets/checkbox.h new file mode 100644 index 000000000..b885e8922 --- /dev/null +++ b/src/gui/widgets/checkbox.h @@ -0,0 +1,92 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CHECKBOX_H +#define CHECKBOX_H + +#include <guichan/widgets/checkbox.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Check box widget. Same as the Guichan check box but with custom look. + * + * \ingroup GUI + */ +class CheckBox : public gcn::CheckBox +{ + public: + /** + * Constructor. + */ + CheckBox(const std::string &caption, bool selected = false, + gcn::ActionListener* listener = NULL, + std::string eventId = ""); + + /** + * Destructor. + */ + ~CheckBox(); + + /** + * Draws the caption, then calls drawBox to draw the check box. + */ + void draw(gcn::Graphics* graphics); + + /** + * Update the alpha value to the checkbox components. + */ + void updateAlpha(); + + /** + * Draws the check box, not the caption. + */ + void drawBox(gcn::Graphics* graphics); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + private: + static int instances; + static float mAlpha; + bool mHasMouse; + static Image *checkBoxNormal; + static Image *checkBoxChecked; + static Image *checkBoxDisabled; + static Image *checkBoxDisabledChecked; + static Image *checkBoxNormalHi; + static Image *checkBoxCheckedHi; +}; + +#endif diff --git a/src/gui/widgets/container.cpp b/src/gui/widgets/container.cpp new file mode 100644 index 000000000..b788b0610 --- /dev/null +++ b/src/gui/widgets/container.cpp @@ -0,0 +1,33 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/container.h" + +Container::Container() +{ + setOpaque(false); +} + +Container::~Container() +{ + while (!mWidgets.empty()) + delete mWidgets.front(); +} diff --git a/src/gui/widgets/container.h b/src/gui/widgets/container.h new file mode 100644 index 000000000..c2696a65f --- /dev/null +++ b/src/gui/widgets/container.h @@ -0,0 +1,43 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_CONTAINER_H +#define GUI_CONTAINER_H + +#include <guichan/widgets/container.hpp> + +/** + * A widget container. + * + * The main difference between the standard Guichan container and this one is + * that childs added to this container are automatically deleted when the + * container is deleted. + * + * This container is also non-opaque by default. + */ +class Container : public gcn::Container +{ + public: + Container(); + ~Container(); +}; + +#endif diff --git a/src/gui/widgets/desktop.cpp b/src/gui/widgets/desktop.cpp new file mode 100644 index 000000000..fa5b1698a --- /dev/null +++ b/src/gui/widgets/desktop.cpp @@ -0,0 +1,157 @@ +/* + * Desktop widget + * Copyright (c) 2009-2010 The Mana World Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/desktop.h" + +#include "configuration.h" +#include "graphics.h" +#include "log.h" +#include "main.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "gui/widgets/label.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" +#include "resources/wallpaper.h" + +#include "utils/stringutils.h" + +Desktop::Desktop() + : mWallpaper(0) +{ + addWidgetListener(this); + + Wallpaper::loadWallpapers(); + + std::string appName = branding.getValue("appName", std::string("")); + + if (appName.empty()) + mVersionLabel = new Label(FULL_VERSION); + else + mVersionLabel = new Label(strprintf("%s (Mana %s)", appName.c_str(), + FULL_VERSION)); + + mVersionLabel->setBackgroundColor(gcn::Color(255, 255, 255, 128)); + add(mVersionLabel, 25, 2); +} + +Desktop::~Desktop() +{ + if (mWallpaper) + mWallpaper->decRef(); +} + +void Desktop::reloadWallpaper() +{ + Wallpaper::loadWallpapers(); + setBestFittingWallpaper(); +} + +void Desktop::widgetResized(const gcn::Event &event _UNUSED_) +{ + setBestFittingWallpaper(); +} + +void Desktop::draw(gcn::Graphics *graphics) +{ + Graphics *g = static_cast<Graphics *>(graphics); + + if (!mWallpaper || (getWidth() > mWallpaper->getWidth() || + getHeight() > mWallpaper->getHeight())) + { + // TODO: Color from palette + g->setColor(gcn::Color(64, 64, 64)); + g->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (mWallpaper) + { + if (!mWallpaper->useOpenGL()) + { + g->drawImage(mWallpaper, + (getWidth() - mWallpaper->getWidth()) / 2, + (getHeight() - mWallpaper->getHeight()) / 2); + } + else + { + g->drawRescaledImage(mWallpaper, 0, 0, 0, 0, + mWallpaper->getWidth(), mWallpaper->getHeight(), + getWidth(), getHeight(), false); + } + } + + // Draw a thin border under the application version... + g->setColor(gcn::Color(255, 255, 255, 128)); + g->fillRectangle(gcn::Rectangle(mVersionLabel->getDimension())); + + Container::draw(graphics); +} + +void Desktop::setBestFittingWallpaper() +{ + if (!config.getBoolValue("showBackground")) + return; + + const std::string wallpaperName = + Wallpaper::getWallpaper(getWidth(), getHeight()); + + Image *nWallPaper = Theme::getImageFromTheme(wallpaperName); + + if (nWallPaper) + { + if (mWallpaper) + mWallpaper->decRef(); + + if (!nWallPaper->useOpenGL() + && (nWallPaper->getWidth() != getWidth() + || nWallPaper->getHeight() != getHeight())) + { + // We rescale to obtain a fullscreen wallpaper... + Image *newRsclWlPpr = nWallPaper->SDLgetScaledImage( + getWidth(), getHeight()); + std::string idPath = nWallPaper->getIdPath(); + + // We replace the resource in the resource manager + nWallPaper->decRef(); + if (newRsclWlPpr) + { + ResourceManager::getInstance()->addResource( + idPath, newRsclWlPpr); + + mWallpaper = newRsclWlPpr; + } + else + { + mWallpaper = nWallPaper; + } + } + else + { + mWallpaper = nWallPaper; + } + } + else + { + logger->log("Couldn't load %s as wallpaper", wallpaperName.c_str()); + } +} diff --git a/src/gui/widgets/desktop.h b/src/gui/widgets/desktop.h new file mode 100644 index 000000000..83568c66f --- /dev/null +++ b/src/gui/widgets/desktop.h @@ -0,0 +1,73 @@ +/* + * Desktop widget + * Copyright (c) 2009-2010 The Mana World Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DESKTOP_H +#define DESKTOP_H + +#include "guichanfwd.h" + +#include "gui/widgets/container.h" + +#include <guichan/widgetlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Desktop widget, for drawing a background image and color. + * + * It picks the best fitting background image. If the image doesn't fit, a + * background color is drawn and the image is centered. + * + * When the desktop widget is resized, the background image is automatically + * updated. + * + * The desktop also displays the client version in the top-right corner. + * + * \ingroup GUI + */ +class Desktop : public Container, gcn::WidgetListener +{ + public: + Desktop(); + ~Desktop(); + + /** + * Has to be called after updates have been loaded. + */ + void reloadWallpaper(); + + void widgetResized(const gcn::Event &event); + + void draw(gcn::Graphics *graphics); + + private: + void setBestFittingWallpaper(); + + Image *mWallpaper; + gcn::Label *mVersionLabel; +}; + +#endif // DESKTOP_H diff --git a/src/gui/widgets/dropdown.cpp b/src/gui/widgets/dropdown.cpp new file mode 100644 index 000000000..b8616643b --- /dev/null +++ b/src/gui/widgets/dropdown.cpp @@ -0,0 +1,303 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/dropdown.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include <algorithm> + +int DropDown::instances = 0; +Image *DropDown::buttons[2][2]; +ImageRect DropDown::skin; +float DropDown::mAlpha = 1.0; + +DropDown::DropDown(gcn::ListModel *listModel): + gcn::DropDown::DropDown(listModel, + new ScrollArea, + new ListBox(listModel)) +{ + setFrameSize(2); + + // Initialize graphics + if (instances == 0) + { + // Load the background skin + + // Get the button skin + buttons[1][0] = Theme::getImageFromTheme("vscroll_up_default.png"); + buttons[0][0] = Theme::getImageFromTheme("vscroll_down_default.png"); + buttons[1][1] = Theme::getImageFromTheme("vscroll_up_pressed.png"); + buttons[0][1] = Theme::getImageFromTheme("vscroll_down_pressed.png"); + + if (buttons[0][0]) + buttons[0][0]->setAlpha(mAlpha); + if (buttons[0][1]) + buttons[0][1]->setAlpha(mAlpha); + if (buttons[1][0]) + buttons[1][0]->setAlpha(mAlpha); + if (buttons[1][1]) + buttons[1][1]->setAlpha(mAlpha); + + // get the border skin + Image *boxBorder = Theme::getImageFromTheme("deepbox.png"); + if (boxBorder) + { + int gridx[4] = {0, 3, 28, 31}; + int gridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + skin.grid[a] = boxBorder->getSubImage(gridx[x], gridy[y], + gridx[x + 1] - + gridx[x] + 1, + gridy[y + 1] - + gridy[y] + 1); + if (skin.grid[a]) + skin.grid[a]->setAlpha(mAlpha); + a++; + } + } + + boxBorder->decRef(); + } + } + + instances++; +} + +DropDown::~DropDown() +{ + instances--; + // Free images memory + if (instances == 0) + { + if (buttons[0][0]) + buttons[0][0]->decRef(); + if (buttons[0][1]) + buttons[0][1]->decRef(); + if (buttons[1][0]) + buttons[1][0]->decRef(); + if (buttons[1][1]) + buttons[1][1]->decRef(); + + for_each(skin.grid, skin.grid + 9, dtor<Image*>()); + } + + delete mScrollArea; + mScrollArea = 0; +} + +void DropDown::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + + if (buttons[0][0]) + buttons[0][0]->setAlpha(mAlpha); + if (buttons[0][1]) + buttons[0][1]->setAlpha(mAlpha); + if (buttons[1][0]) + buttons[1][0]->setAlpha(mAlpha); + if (buttons[1][1]) + buttons[1][1]->setAlpha(mAlpha); + + for (int a = 0; a < 9; a++) + { + if (skin.grid[a]) + skin.grid[a]->setAlpha(mAlpha); + } + } +} + +void DropDown::draw(gcn::Graphics* graphics) +{ + int h; + + if (mDroppedDown) + h = mFoldedUpHeight; + else + h = getHeight(); + + updateAlpha(); + + const int alpha = static_cast<int>(mAlpha * 255.0f); + gcn::Color faceColor = getBaseColor(); + faceColor.a = alpha; + const gcn::Color *highlightColor = &Theme::getThemeColor(Theme::HIGHLIGHT, + alpha); + gcn::Color shadowColor = faceColor - 0x303030; + shadowColor.a = alpha; + + if (mListBox->getListModel() && mListBox->getSelected() >= 0) + { + graphics->setFont(getFont()); + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->drawText(mListBox->getListModel()->getElementAt( + mListBox->getSelected()), 1, 0); + } + + if (isFocused()) + { + if (highlightColor) + graphics->setColor(*highlightColor); + graphics->drawRectangle(gcn::Rectangle(0, 0, getWidth() - h, h)); + } + + drawButton(graphics); + + if (mDroppedDown) + { + drawChildren(graphics); + + // Draw two lines separating the ListBox with selected + // element view. + if (highlightColor) + graphics->setColor(*highlightColor); + graphics->drawLine(0, h, getWidth(), h); + graphics->setColor(shadowColor); + graphics->drawLine(0, h + 1, getWidth(), h + 1); + } +} + +void DropDown::drawFrame(gcn::Graphics *graphics) +{ + const int bs = getFrameSize(); + const int w = getWidth() + bs * 2; + const int h = getHeight() + bs * 2; + + static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, skin); +} + +void DropDown::drawButton(gcn::Graphics *graphics) +{ + int height = mDroppedDown ? mFoldedUpHeight : getHeight(); + + if (buttons[mDroppedDown][mPushed]) + { + static_cast<Graphics*>(graphics)-> + drawImage(buttons[mDroppedDown][mPushed], + getWidth() - height + 2, 1); + } +} + +// -- KeyListener notifications +void DropDown::keyPressed(gcn::KeyEvent& keyEvent) +{ + if (keyEvent.isConsumed()) + return; + + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE) + dropDown(); + else if (key.getValue() == Key::UP) + setSelected(getSelected() - 1); + else if (key.getValue() == Key::DOWN) + setSelected(getSelected() + 1); + else if (key.getValue() == Key::HOME) + setSelected(0); + else if (key.getValue() == Key::END && mListBox->getListModel()) + setSelected(mListBox->getListModel()->getNumberOfElements() - 1); + else + return; + + keyEvent.consume(); +} + +void DropDown::focusLost(const gcn::Event& event) +{ + gcn::DropDown::focusLost(event); + releaseModalMouseInputFocus(); +} + +void DropDown::mousePressed(gcn::MouseEvent& mouseEvent) +{ + gcn::DropDown::mousePressed(mouseEvent); + + if (0 <= mouseEvent.getY() && mouseEvent.getY() < getHeight() && + mouseEvent.getX() >= 0 && mouseEvent.getX() < getWidth() && + mouseEvent.getButton() == gcn::MouseEvent::LEFT && mDroppedDown && + mouseEvent.getSource() == mListBox) + { + mPushed = false; + foldUp(); + releaseModalMouseInputFocus(); + distributeActionEvent(); + } +} + +void DropDown::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent) +{ + setSelected(getSelected() - 1); + mouseEvent.consume(); +} + +void DropDown::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent) +{ + setSelected(getSelected() + 1); + mouseEvent.consume(); +} + +void DropDown::setSelectedString(std::string str) +{ + gcn::ListModel *listModel = mListBox->getListModel(); + if (!listModel) + return; + + for (int f = 0; f < listModel->getNumberOfElements(); f ++) + { + if (listModel->getElementAt(f) == str) + { + setSelected(f); + break; + } + } +} + +std::string DropDown::getSelectedString() const +{ + gcn::ListModel *listModel = mListBox->getListModel(); + if (!listModel) + return ""; + + return listModel->getElementAt(getSelected()); +} diff --git a/src/gui/widgets/dropdown.h b/src/gui/widgets/dropdown.h new file mode 100644 index 000000000..f6e347b2b --- /dev/null +++ b/src/gui/widgets/dropdown.h @@ -0,0 +1,97 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DROPDOWN_H +#define DROPDOWN_H + +#include <guichan/widgets/dropdown.hpp> + +class Image; +class ImageRect; + +/** + * A drop down box from which you can select different values. + * + * A ListModel provides the contents of the drop down. To be able to use + * DropDown you must give DropDown an implemented ListModel which represents + * your list. + */ +class DropDown : public gcn::DropDown +{ + public: + /** + * Contructor. + * + * @param listModel the ListModel to use. + * @param scrollArea the ScrollArea to use. + * @param listBox the listBox to use. + * @see ListModel, ScrollArea, ListBox. + */ + DropDown(gcn::ListModel *listModel = 0); + + ~DropDown(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + void draw(gcn::Graphics *graphics); + + void drawFrame(gcn::Graphics *graphics); + + // Inherited from FocusListener + + void focusLost(const gcn::Event& event); + + // Inherited from KeyListener + + void keyPressed(gcn::KeyEvent& keyEvent); + + // Inherited from MouseListener + + void mousePressed(gcn::MouseEvent& mouseEvent); + + void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent); + + void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent); + + void setSelectedString(std::string str); + + std::string getSelectedString() const; + + protected: + /** + * Draws the button with the little down arrow. + * + * @param graphics a Graphics object to draw with. + */ + void drawButton(gcn::Graphics *graphics); + + // Add own Images. + static int instances; + static Image *buttons[2][2]; + static ImageRect skin; + static float mAlpha; +}; + +#endif // end DROPDOWN_H + diff --git a/src/gui/widgets/dropshortcutcontainer.cpp b/src/gui/widgets/dropshortcutcontainer.cpp new file mode 100644 index 000000000..c3aaed829 --- /dev/null +++ b/src/gui/widgets/dropshortcutcontainer.cpp @@ -0,0 +1,303 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/widgets/dropshortcutcontainer.h" + +#include "gui/inventorywindow.h" +#include "gui/itempopup.h" +#include "gui/palette.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "configuration.h" +#include "dropshortcut.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +DropShortcutContainer::DropShortcutContainer(): + ShortcutContainer(), + mItemClicked(false), + mItemMoved(NULL) +{ + addMouseListener(this); + addWidgetListener(this); + + mItemPopup = new ItemPopup; + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + if (dropShortcut) + mMaxItems = dropShortcut->getItemCount(); + else + mMaxItems = 0; + + if (mBackgroundImg) + { + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +DropShortcutContainer::~DropShortcutContainer() +{ + if (mBackgroundImg) + mBackgroundImg->decRef(); + delete mItemPopup; + mItemPopup = 0; +} + +void DropShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!dropShortcut) + return; + + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + if (mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + } + + Graphics *g = static_cast<Graphics*>(graphics); + + graphics->setFont(getFont()); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int itemX = (i % mGridWidth) * mBoxWidth; + const int itemY = (i / mGridWidth) * mBoxHeight; + + if (mBackgroundImg) + g->drawImage(mBackgroundImg, itemX, itemY); + +/* // Draw item keyboard shortcut. + const char *key = SDL_GetKeyName( + (SDLKey) keyboard.getKeyValue(keyboard.KEY_SHORTCUT_1 + i)); + graphics->setColor(guiPalette->getColor(Palette::TEXT)); + g->drawText(key, itemX + 2, itemY + 2, gcn::Graphics::LEFT); +*/ + if (dropShortcut->getItem(i) < 0) + continue; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(dropShortcut->getItem(i)); + + if (item) + { + // Draw item icon. + Image* image = item->getImage(); + + if (image) + { + std::string caption; + if (item->getQuantity() > 1) + caption = toString(item->getQuantity()); + else if (item->isEquipped()) + caption = "Eq."; + + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + if (item->isEquipped()) + g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED)); + else + g->setColor(Theme::getThemeColor(Theme::TEXT)); + g->drawText(caption, itemX + mBoxWidth / 2, + itemY + mBoxHeight - 14, gcn::Graphics::CENTER); + } + } + } + + if (mItemMoved) + { + // Draw the item image being dragged by the cursor. + Image* image = mItemMoved->getImage(); + if (image) + { + const int tPosX = mCursorPosX - (image->getWidth() / 2); + const int tPosY = mCursorPosY - (image->getHeight() / 2); + + g->drawImage(image, tPosX, tPosY); + g->drawText(toString(mItemMoved->getQuantity()), + tPosX + mBoxWidth / 2, tPosY + mBoxHeight - 14, + gcn::Graphics::CENTER); + } + } +} + +void DropShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (!dropShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mItemMoved && mItemClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = dropShortcut->getItem(index); + + if (itemId < 0) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(itemId); + + if (item) + { + mItemMoved = item; + dropShortcut->removeItem(index); + } + } + if (mItemMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void DropShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!dropShortcut || !inventoryWindow) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Stores the selected item if theirs one. + if (dropShortcut->isItemSelected() && inventoryWindow->isVisible()) + { + dropShortcut->setItem(index); + dropShortcut->setItemSelected(-1); + } + else if (dropShortcut->getItem(index)) + { + mItemClicked = true; + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(dropShortcut->getItem(index)); + + if (viewport) + viewport->showDropPopup(item); + } +} + +void DropShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!dropShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (dropShortcut->isItemSelected()) + dropShortcut->setItemSelected(-1); + + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + mItemMoved = NULL; + return; + } + if (mItemMoved) + { + dropShortcut->setItems(index, mItemMoved->getId()); + mItemMoved = NULL; + } + + if (mItemClicked) + mItemClicked = false; + } +} + +// Show ItemTooltip +void DropShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!dropShortcut) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = dropShortcut->getItem(index); + + if (itemId < 0) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(itemId); + + if (item && viewport) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } +} + +// Hide ItemTooltip +void DropShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mItemPopup->setVisible(false); +} diff --git a/src/gui/widgets/dropshortcutcontainer.h b/src/gui/widgets/dropshortcutcontainer.h new file mode 100644 index 000000000..c072f0613 --- /dev/null +++ b/src/gui/widgets/dropshortcutcontainer.h @@ -0,0 +1,88 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DROPSHORTCUTCONTAINER_H +#define DROPSHORTCUTCONTAINER_H + +#include <guichan/mouselistener.hpp> + +#include "gui/widgets/shortcutcontainer.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class Item; +class ItemPopup; + +/** + * An item shortcut container. Used to quickly use items. + * + * \ingroup GUI + */ +class DropShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + DropShortcutContainer(); + + /** + * Destructor. + */ + virtual ~DropShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + bool mItemClicked; + Item *mItemMoved; + + ItemPopup *mItemPopup; +}; + +#endif diff --git a/src/gui/widgets/emoteshortcutcontainer.cpp b/src/gui/widgets/emoteshortcutcontainer.cpp new file mode 100644 index 000000000..a9e435540 --- /dev/null +++ b/src/gui/widgets/emoteshortcutcontainer.cpp @@ -0,0 +1,259 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/emoteshortcutcontainer.h" + +#include "animatedsprite.h" +#include "configuration.h" +#include "emoteshortcut.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/textpopup.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "resources/emotedb.h" +#include "resources/image.h" + +#include "utils/dtor.h" + +static const int MAX_ITEMS = 42; + +EmoteShortcutContainer::EmoteShortcutContainer(): + ShortcutContainer(), + mEmoteClicked(false), + mEmoteMoved(0), + mEmotePopup(new TextPopup) +{ + addMouseListener(this); + addWidgetListener(this); + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + + if (mBackgroundImg) + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + + // Setup emote sprites + for (int i = 0; i <= EmoteDB::getLast(); i++) + { + const EmoteSprite* sprite = EmoteDB::getSprite(i, true); + if (sprite && sprite->sprite) + mEmoteImg.push_back(sprite); + } + +// mMaxItems = EmoteDB::getLast() < MAX_ITEMS ? EmoteDB::getLast() : MAX_ITEMS; + mMaxItems = MAX_ITEMS; + + if (mBackgroundImg) + { + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +EmoteShortcutContainer::~EmoteShortcutContainer() +{ + delete mEmotePopup; + + if (mBackgroundImg) + mBackgroundImg->decRef(); +} + +void EmoteShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!emoteShortcut) + return; + + mAlpha = Client::getGuiAlpha(); + if (Client::getGuiAlpha() != mAlpha && mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + + Graphics *g = static_cast<Graphics*>(graphics); + + graphics->setFont(getFont()); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int emoteX = (i % mGridWidth) * mBoxWidth; + const int emoteY = (i / mGridWidth) * mBoxHeight; + + if (mBackgroundImg) + g->drawImage(mBackgroundImg, emoteX, emoteY); + + // Draw emote keyboard shortcut. + std::string key = keyboard.getKeyValueString( + keyboard.KEY_EMOTE_1 + i); + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + g->drawText(key, emoteX + 2, emoteY + 2, gcn::Graphics::LEFT); + +/* + if (emoteShortcut->getEmote(i) + && static_cast<unsigned>(emoteShortcut->getEmote(i)) - 1 + < mEmoteImg.size() + && mEmoteImg[emoteShortcut->getEmote(i) - 1]) + { + mEmoteImg[emoteShortcut->getEmote(i) - 1]->draw(g, emoteX + 2, + emoteY + 10); + } +*/ + + if (i < mEmoteImg.size() && mEmoteImg[i] && mEmoteImg[i]->sprite) + mEmoteImg[i]->sprite->draw(g, emoteX + 2, emoteY + 10); + } + + if (mEmoteMoved && mEmoteMoved < (unsigned)mEmoteImg.size() + 1 + && mEmoteMoved > 0) + { + // Draw the emote image being dragged by the cursor. + const EmoteSprite* sprite = mEmoteImg[mEmoteMoved - 1]; + if (sprite && sprite->sprite) + { + const AnimatedSprite *spr = sprite->sprite; + const int tPosX = mCursorPosX - (spr->getWidth() / 2); + const int tPosY = mCursorPosY - (spr->getHeight() / 2); + + spr->draw(g, tPosX, tPosY); + } + } +} + +void EmoteShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (!emoteShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mEmoteMoved && mEmoteClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + +// const unsigned char emoteId = emoteShortcut->getEmote(index); + const unsigned char emoteId = index + 1; + + if (emoteId) + { + mEmoteMoved = emoteId; + emoteShortcut->removeEmote(index); + } + } + if (mEmoteMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void EmoteShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!emoteShortcut) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + // Stores the selected emote if there is one. + if (emoteShortcut->isEmoteSelected()) + { + emoteShortcut->setEmote(index); + emoteShortcut->setEmoteSelected(0); + } + else if (emoteShortcut->getEmote(index)) + { + mEmoteClicked = true; + } +} + +void EmoteShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!emoteShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (emoteShortcut->isEmoteSelected()) + emoteShortcut->setEmoteSelected(0); + + if (index == -1) + { + mEmoteMoved = 0; + return; + } + + if (mEmoteMoved) + { + emoteShortcut->setEmotes(index, mEmoteMoved); + mEmoteMoved = 0; + } + else if (emoteShortcut->getEmote(index) && mEmoteClicked) + { + emoteShortcut->useEmote(index + 1); + } + + if (mEmoteClicked) + mEmoteClicked = false; + } +} + +void EmoteShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!emoteShortcut || !mEmotePopup) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + mEmotePopup->setVisible(false); + + if ((unsigned)index < mEmoteImg.size() && mEmoteImg[index]) + { + mEmotePopup->show(viewport->getMouseX(), viewport->getMouseY(), + mEmoteImg[index]->name); + } +} + +void EmoteShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mEmotePopup) + mEmotePopup->setVisible(false); +}
\ No newline at end of file diff --git a/src/gui/widgets/emoteshortcutcontainer.h b/src/gui/widgets/emoteshortcutcontainer.h new file mode 100644 index 000000000..e841e6dfb --- /dev/null +++ b/src/gui/widgets/emoteshortcutcontainer.h @@ -0,0 +1,84 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef EMOTESHORTCUTCONTAINER_H +#define EMOTESHORTCUTCONTAINER_H + +#include "gui/widgets/shortcutcontainer.h" + +#include "resources/emotedb.h" + +#include <vector> + +class AnimatedSprite; +class Image; +class TextPopup; + +/** + * An emote shortcut container. Used to quickly use emoticons. + * + * \ingroup GUI + */ +class EmoteShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + EmoteShortcutContainer(); + + /** + * Destructor. + */ + virtual ~EmoteShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + void mouseMoved(gcn::MouseEvent &event); + + void mouseExited(gcn::MouseEvent &event); + + private: + std::vector<const EmoteSprite*> mEmoteImg; + + bool mEmoteClicked; + unsigned char mEmoteMoved; + TextPopup *mEmotePopup; +}; + +#endif diff --git a/src/gui/widgets/flowcontainer.cpp b/src/gui/widgets/flowcontainer.cpp new file mode 100644 index 000000000..a93818abc --- /dev/null +++ b/src/gui/widgets/flowcontainer.cpp @@ -0,0 +1,88 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "flowcontainer.h" + +FlowContainer::FlowContainer(int boxWidth, int boxHeight): + mBoxWidth(boxWidth), mBoxHeight(boxHeight), + mGridWidth(1), mGridHeight(1) +{ + addWidgetListener(this); + if (!mBoxWidth) + mBoxWidth = 1; + if (!mBoxHeight) + mBoxHeight = 1; +} + +void FlowContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + if (getWidth() < mBoxWidth) + { + setWidth(mBoxWidth); + return; + } + + int itemCount = static_cast<int>(mWidgets.size()); + + if (!mBoxWidth) + mGridWidth = getWidth(); + else + mGridWidth = getWidth() / mBoxWidth; + + if (mGridWidth < 1) + mGridWidth = 1; + + mGridHeight = itemCount / mGridWidth; + + if (itemCount % mGridWidth != 0 || mGridHeight < 1) + ++mGridHeight; + + int height = mGridHeight * mBoxHeight; + + if (getHeight() != height) + { + setHeight(height); + return; + } + + int i = 0; + height = 0; + for (WidgetList::iterator it = mWidgets.begin(); + it != mWidgets.end(); it++) + { + int x = i % mGridWidth * mBoxWidth; + (*it)->setPosition(x, height); + + i++; + + if (i % mGridWidth == 0) + height += mBoxHeight; + } +} + +void FlowContainer::add(gcn::Widget *widget) +{ + if (!widget) + return; + + Container::add(widget); + widget->setSize(mBoxWidth, mBoxHeight); + widgetResized(NULL); +} diff --git a/src/gui/widgets/flowcontainer.h b/src/gui/widgets/flowcontainer.h new file mode 100644 index 000000000..f738be209 --- /dev/null +++ b/src/gui/widgets/flowcontainer.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef FLOWCONTAINER_H +#define FLOWCONTAINER_H + +#include "container.h" + +#include <guichan/widgetlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * A container that arranges its contents like words on a page. + * + * \ingroup GUI + */ +class FlowContainer : public Container, + public gcn::WidgetListener +{ + public: + /** + * Constructor. Initializes the shortcut container. + */ + FlowContainer(int boxWidth, int boxHeight); + + /** + * Destructor. + */ + ~FlowContainer() {} + + /** + * Invoked when a widget changes its size. This is used to determine + * the new height of the container. + */ + void widgetResized(const gcn::Event &event); + + int getBoxWidth() const + { return mBoxWidth; } + + int getBoxHeight() const + { return mBoxHeight; } + + void add(gcn::Widget *widget); + + private: + int mBoxWidth; + int mBoxHeight; + int mGridWidth, mGridHeight; +}; + +#endif diff --git a/src/gui/widgets/icon.cpp b/src/gui/widgets/icon.cpp new file mode 100644 index 000000000..4e5902128 --- /dev/null +++ b/src/gui/widgets/icon.cpp @@ -0,0 +1,60 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/icon.h" + +#include "graphics.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +Icon::Icon(const std::string &file) + : mImage(0) +{ + mImage = ResourceManager::getInstance()->getImage(file); + if (mImage) + setSize(mImage->getWidth(), mImage->getHeight()); +} + +Icon::Icon(Image *image) + : mImage(image) +{ + if (mImage) + setSize(mImage->getWidth(), mImage->getHeight()); +} + +void Icon::setImage(Image *image) +{ + mImage = image; + if (mImage) + setSize(mImage->getWidth(), mImage->getHeight()); +} + +void Icon::draw(gcn::Graphics *g) +{ + if (mImage) + { + Graphics *graphics = static_cast<Graphics*>(g); + const int x = (getWidth() - mImage->getWidth()) / 2; + const int y = (getHeight() - mImage->getHeight()) / 2; + graphics->drawImage(mImage, x, y); + } +} diff --git a/src/gui/widgets/icon.h b/src/gui/widgets/icon.h new file mode 100644 index 000000000..27ed0db8e --- /dev/null +++ b/src/gui/widgets/icon.h @@ -0,0 +1,66 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ICON_H +#define ICON_H + +#include <guichan/widget.hpp> + +class Image; + +/** + * An icon. + * + * \ingroup GUI + */ +class Icon : public gcn::Widget +{ + public: + /** + * Constructor. + */ + Icon(const std::string &filename); + + /** + * Constructor, uses an existing Image. + */ + Icon(Image *image); + + /** + * Gets the current Image. + */ + Image *getImage() const { return mImage; } + + /** + * Sets the image to display. + */ + void setImage(Image *image); + + /** + * Draws the Icon. + */ + void draw(gcn::Graphics *g); + + private: + Image *mImage; +}; + +#endif // ICON_H diff --git a/src/gui/widgets/inttextfield.cpp b/src/gui/widgets/inttextfield.cpp new file mode 100644 index 000000000..d0ffaebc5 --- /dev/null +++ b/src/gui/widgets/inttextfield.cpp @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/inttextfield.h" + +#include "gui/sdlinput.h" + +#include "utils/stringutils.h" + +IntTextField::IntTextField(int def, int min, int max, + bool enabled, int width): + TextField(toString(def)), + mDefault(def), + mValue(def) +{ + if (min != 0 || max != 0) + setRange(min, max); + + setEnabled(enabled); + if (width != 0) + setWidth(width); +} + +void IntTextField::keyPressed(gcn::KeyEvent &event) +{ + const gcn::Key &key = event.getKey(); + + if (key.getValue() == Key::BACKSPACE || + key.getValue() == Key::DELETE) + { + setText(std::string()); + event.consume(); + } + + if (!key.isNumber()) + return; + + TextField::keyPressed(event); + + std::istringstream s(getText()); + int i; + s >> i; + setValue(i); +} + +void IntTextField::setRange(int min, int max) +{ + mMin = min; + mMax = max; + + if (mValue < mMin) + mValue = mMin; + else if (mValue > mMax) + mValue = mMax; + + if (mDefault < mMin) + mDefault = mMin; + else if (mDefault > mMax) + mDefault = mMax; +} + +int IntTextField::getValue() +{ + return getText().empty() ? mMin : mValue; +} + +void IntTextField::setValue(int i) +{ + if (i < mMin) + mValue = mMin; + else if (i > mMax) + mValue = mMax; + else + mValue = i; + + const std::string valStr = toString(mValue); + setText(valStr); + setCaretPosition(static_cast<unsigned>(valStr.length()) + 1); +} + +void IntTextField::setDefaultValue(int value) +{ + if (value < mMin) + mDefault = mMin; + else if (value > mMax) + mDefault = mMax; + else + mDefault = value; +} + +void IntTextField::reset() +{ + setValue(mDefault); +} diff --git a/src/gui/widgets/inttextfield.h b/src/gui/widgets/inttextfield.h new file mode 100644 index 000000000..69c8a74d7 --- /dev/null +++ b/src/gui/widgets/inttextfield.h @@ -0,0 +1,76 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef INTTEXTFIELD_H +#define INTTEXTFIELD_H + +#include "textfield.h" + +/** + * TextBox which only accepts numbers as input. + */ +class IntTextField : public TextField +{ + public: + /** + * Constructor, sets default value. + */ + IntTextField(int def = 0, int min = 0, int max = 0, + bool enabled = true, int width = 0); + + /** + * Sets the minimum and maximum values of the text box. + */ + void setRange(int minimum, int maximum); + + /** + * Returns the value in the text box. + */ + int getValue(); + + /** + * Reset the field to the default value. + */ + void reset(); + + /** + * Set the value of the text box to the specified value. + */ + void setValue(int value); + + /** + * Set the default value of the text box to the specified value. + */ + void setDefaultValue(int value); + + /** + * Responds to key presses. + */ + void keyPressed(gcn::KeyEvent &event); + + private: + int mMin; /**< Minimum value */ + int mMax; /**< Maximum value */ + int mDefault; /**< Default value */ + int mValue; /**< Current value */ +}; + +#endif diff --git a/src/gui/widgets/itemcontainer.cpp b/src/gui/widgets/itemcontainer.cpp new file mode 100644 index 000000000..8b5b1914a --- /dev/null +++ b/src/gui/widgets/itemcontainer.cpp @@ -0,0 +1,475 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/itemcontainer.h" + +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "dropshortcut.h" +#include "log.h" + +#include "gui/chat.h" +#include "gui/itempopup.h" +#include "gui/outfitwindow.h" +#include "gui/palette.h" +#include "gui/shopwindow.h" +#include "gui/shortcutwindow.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "net/net.h" +#include "net/inventoryhandler.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" + +#include "utils/stringutils.h" + +#include <guichan/mouseinput.hpp> +#include <guichan/selectionlistener.hpp> + +// TODO: Add support for adding items to the item shortcut window (global +// itemShortcut). + +static const int BOX_WIDTH = 35; +static const int BOX_HEIGHT = 43; + +ItemContainer::ItemContainer(Inventory *inventory, bool forceQuantity): + mInventory(inventory), + mGridColumns(1), + mGridRows(1), + mSelectedIndex(-1), + mHighlightedIndex(-1), + mLastUsedSlot(-1), + mSelectionStatus(SEL_NONE), + mForceQuantity(forceQuantity), + mSwapItems(false), + mDescItems(false) +{ + mItemPopup = new ItemPopup; + setFocusable(true); + + mSelImg = Theme::getImageFromTheme("selection.png"); + if (!mSelImg) + logger->log1("Error: Unable to load selection.png"); + + addKeyListener(this); + addMouseListener(this); + addWidgetListener(this); +} + +ItemContainer::~ItemContainer() +{ + if (mSelImg) + { + mSelImg->decRef(); + mSelImg = 0; + } + delete mItemPopup; + mItemPopup = 0; +} + +void ItemContainer::logic() +{ + gcn::Widget::logic(); + + if (!mInventory) + return; + + const int lastUsedSlot = mInventory->getLastUsedSlot(); + + if (lastUsedSlot != mLastUsedSlot) + { + mLastUsedSlot = lastUsedSlot; + adjustHeight(); + } +} + +void ItemContainer::draw(gcn::Graphics *graphics) +{ + if (!mInventory) + return; + + Graphics *g = static_cast<Graphics*>(graphics); + + g->setFont(getFont()); + + for (int i = 0; i < mGridColumns; i++) + { + for (int j = 0; j < mGridRows; j++) + { + int itemX = i * BOX_WIDTH; + int itemY = j * BOX_HEIGHT; + int itemIndex = (j * mGridColumns) + i; + Item *item = mInventory->getItem(itemIndex); + + if (!item || item->getId() == 0) + continue; + + Image *image = item->getImage(); + if (image) + { + if (itemIndex == mSelectedIndex) + { + if (mSelectionStatus == SEL_DRAGGING) + { + // Reposition the coords to that of the cursor. + itemX = mDragPosX - (BOX_WIDTH / 2); + itemY = mDragPosY - (BOX_HEIGHT / 2); + } + else + { + // Draw selection border image. + if (mSelImg) + g->drawImage(mSelImg, itemX, itemY); + } + } + image->setAlpha(1.0f); // ensure the image if fully drawn... + g->drawImage(image, itemX, itemY); + } + // Draw item caption + std::string caption; + if (item->getQuantity() > 1 || mForceQuantity) + caption = toString(item->getQuantity()); + else if (item->isEquipped()) + caption = "Eq."; + + if (item->isEquipped()) + g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED)); + else + g->setColor(gcn::Color(0, 0, 0)); + + g->drawText(caption, itemX + BOX_WIDTH / 2, + itemY + BOX_HEIGHT - 14, gcn::Graphics::CENTER); + } + } + + // Draw an orange box around the selected item + if (isFocused() && mHighlightedIndex != -1 && mGridColumns) + { + const int itemX = (mHighlightedIndex % mGridColumns) * BOX_WIDTH; + const int itemY = (mHighlightedIndex / mGridColumns) * BOX_HEIGHT; + g->setColor(gcn::Color(255, 128, 0)); + g->drawRectangle(gcn::Rectangle(itemX, itemY, BOX_WIDTH, BOX_HEIGHT)); + } +} + +void ItemContainer::selectNone() +{ + setSelectedIndex(-1); + mSelectionStatus = SEL_NONE; + if (outfitWindow) + outfitWindow->setItemSelected(-1); + if (shopWindow) + shopWindow->setItemSelected(-1); +} + +void ItemContainer::setSelectedIndex(int newIndex) +{ + if (mSelectedIndex != newIndex) + { + mSelectedIndex = newIndex; + distributeValueChangedEvent(); + } +} + +Item *ItemContainer::getSelectedItem() const +{ + if (mInventory) + return mInventory->getItem(mSelectedIndex); + else + return 0; +} + +void ItemContainer::distributeValueChangedEvent() +{ + SelectionListenerIterator i, i_end; + + for (i = mSelectionListeners.begin(), i_end = mSelectionListeners.end(); + i != i_end; ++i) + { + if (*i) + { + gcn::SelectionEvent event(this); + (*i)->valueChanged(event); + } + } +} + +void ItemContainer::keyPressed(gcn::KeyEvent &event _UNUSED_) +{ + /*switch (event.getKey().getValue()) + { + case Key::LEFT: + moveHighlight(Left); + break; + case Key::RIGHT: + moveHighlight(Right); + break; + case Key::UP: + moveHighlight(Up); + break; + case Key::DOWN: + moveHighlight(Down); + break; + case Key::SPACE: + keyAction(); + break; + case Key::LEFT_ALT: + case Key::RIGHT_ALT: + mSwapItems = true; + break; + case Key::RIGHT_CONTROL: + mDescItems = true; + break; + }*/ +} + +void ItemContainer::keyReleased(gcn::KeyEvent &event _UNUSED_) +{ + /*switch (event.getKey().getValue()) + { + case Key::LEFT_ALT: + case Key::RIGHT_ALT: + mSwapItems = false; + break; + case Key::RIGHT_CONTROL: + mDescItems = false; + break; + }*/ +} + +void ItemContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!mInventory) + return; + + const int button = event.getButton(); + if (button == gcn::MouseEvent::LEFT || button == gcn::MouseEvent::RIGHT) + { + const int index = getSlotIndex(event.getX(), event.getY()); + if (index == Inventory::NO_SLOT_INDEX) + return; + + Item *item = mInventory->getItem(index); + + // put item name into chat window + if (item && mDescItems && chatWindow) + chatWindow->addItemText(item->getInfo().getName()); + + if (mSelectedIndex == index) + { + mSelectionStatus = SEL_DESELECTING; + } + else if (item && item->getId()) + { + setSelectedIndex(index); + mSelectionStatus = SEL_SELECTING; + + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS) + { + if (itemShortcut[num]) + itemShortcut[num]->setItemSelected(item->getId()); + } + if (dropShortcut) + dropShortcut->setItemSelected(item->getId()); + if (item->isEquipment() && outfitWindow) + outfitWindow->setItemSelected(item->getId()); + if (shopWindow) + shopWindow->setItemSelected(item->getId()); + } + else + { + selectNone(); + } + } +} + +void ItemContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (mSelectionStatus != SEL_NONE) + { + mSelectionStatus = SEL_DRAGGING; + mDragPosX = event.getX(); + mDragPosY = event.getY(); + } +} + +void ItemContainer::mouseReleased(gcn::MouseEvent &event) +{ + switch (mSelectionStatus) + { + case SEL_SELECTING: + mSelectionStatus = SEL_SELECTED; + return; + case SEL_DESELECTING: + selectNone(); + return; + case SEL_DRAGGING: + mSelectionStatus = SEL_SELECTED; + break; + default: + return; + }; + + int index = getSlotIndex(event.getX(), event.getY()); + if (index == Inventory::NO_SLOT_INDEX) + return; + if (index == mSelectedIndex || mSelectedIndex == -1) + return; + Net::getInventoryHandler()->moveItem(mSelectedIndex, index); + selectNone(); +} + + +// Show ItemTooltip +void ItemContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!mInventory) + return; + + Item *item = mInventory->getItem(getSlotIndex(event.getX(), event.getY())); + + if (item && viewport) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } +} + +// Hide ItemTooltip +void ItemContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mItemPopup->setVisible(false); +} + +void ItemContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + mGridColumns = std::max(1, getWidth() / BOX_WIDTH); + adjustHeight(); +} + +void ItemContainer::adjustHeight() +{ + if (!mGridColumns) + return; + + mGridRows = (mLastUsedSlot + 1) / mGridColumns; + if (mGridRows == 0 || (mLastUsedSlot + 1) % mGridColumns > 0) + ++mGridRows; + + setHeight(mGridRows * BOX_HEIGHT); +} + +int ItemContainer::getSlotIndex(int x, int y) const +{ + if (x < getWidth() && y < getHeight()) + return (y / BOX_HEIGHT) * mGridColumns + (x / BOX_WIDTH); + + return Inventory::NO_SLOT_INDEX; +} + +void ItemContainer::keyAction() +{ + // If there is no highlight then return. + if (mHighlightedIndex == -1) + return; + + // If the highlight is on the selected item, then deselect it. + if (mHighlightedIndex == mSelectedIndex) + { + selectNone(); + } + // Check and swap items if necessary. + else if (mSwapItems && mSelectedIndex != -1 && mHighlightedIndex != -1) + { + Net::getInventoryHandler()->moveItem( + mSelectedIndex, mHighlightedIndex); + setSelectedIndex(mHighlightedIndex); + } + // If the highlight is on an item then select it. + else if (mHighlightedIndex != -1) + { + setSelectedIndex(mHighlightedIndex); + mSelectionStatus = SEL_SELECTED; + } + // If the highlight is on a blank space then move it. + else if (mSelectedIndex != -1) + { + Net::getInventoryHandler()->moveItem( + mSelectedIndex, mHighlightedIndex); + selectNone(); + } +} + +void ItemContainer::moveHighlight(Direction direction) +{ + if (mHighlightedIndex == -1) + { + if (mSelectedIndex != -1) + mHighlightedIndex = mSelectedIndex; + else + mHighlightedIndex = 0; + return; + } + + switch (direction) + { + case Left: + if (mHighlightedIndex % mGridColumns == 0) + mHighlightedIndex += mGridColumns; + mHighlightedIndex--; + break; + case Right: + if ((mHighlightedIndex % mGridColumns) == + (mGridColumns - 1)) + { + mHighlightedIndex -= mGridColumns; + } + mHighlightedIndex++; + break; + case Up: + if (mHighlightedIndex / mGridColumns == 0) + mHighlightedIndex += (mGridColumns * mGridRows); + mHighlightedIndex -= mGridColumns; + break; + case Down: + if ((mHighlightedIndex / mGridColumns) == + (mGridRows - 1)) + { + mHighlightedIndex -= (mGridColumns * mGridRows); + } + mHighlightedIndex += mGridColumns; + break; + default: + logger->log("warning moveHighlight unknown direction:" + + toString(static_cast<unsigned>(direction))); + break; + } +} diff --git a/src/gui/widgets/itemcontainer.h b/src/gui/widgets/itemcontainer.h new file mode 100644 index 000000000..8aaa236b4 --- /dev/null +++ b/src/gui/widgets/itemcontainer.h @@ -0,0 +1,195 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ITEMCONTAINER_H +#define ITEMCONTAINER_H + +#include <guichan/keylistener.hpp> +#include <guichan/mouselistener.hpp> +#include <guichan/widget.hpp> +#include <guichan/widgetlistener.hpp> + +#include <list> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class Inventory; +class Item; +class ItemPopup; + +namespace gcn +{ + class SelectionListener; +} + +/** + * An item container. Used to show items in inventory and trade dialog. + * + * \ingroup GUI + */ +class ItemContainer : public gcn::Widget, + public gcn::KeyListener, + public gcn::MouseListener, + public gcn::WidgetListener +{ + public: + /** + * Constructor. Initializes the graphic. + * + * @param inventory + * @param gridColumns Amount of columns in grid. + * @param gridRows Amount of rows in grid. + * @param offset Index offset + */ + ItemContainer(Inventory *inventory, bool forceQuantity = false); + + /** + * Destructor. + */ + virtual ~ItemContainer(); + + /** + * Necessary for checking how full the inventory is. + */ + void logic(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + // KeyListener + void keyPressed(gcn::KeyEvent &event); + void keyReleased(gcn::KeyEvent &event); + + // MouseListener + void mousePressed(gcn::MouseEvent &event); + void mouseDragged(gcn::MouseEvent &event); + void mouseReleased(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + void mouseExited(gcn::MouseEvent &event); + + // WidgetListener + void widgetResized(const gcn::Event &event); + + /** + * Returns the selected item. + */ + Item *getSelectedItem() const; + + /** + * Sets selected item to NULL. + */ + void selectNone(); + + /** + * Adds a listener to the list that's notified each time a change to + * the selection occurs. + */ + void addSelectionListener(gcn::SelectionListener *listener) + { mSelectionListeners.push_back(listener); } + + /** + * Removes a listener from the list that's notified each time a change + * to the selection occurs. + */ + void removeSelectionListener(gcn::SelectionListener *listener) + { mSelectionListeners.remove(listener); } + + private: + enum Direction + { + Left = 0, + Right, + Up, + Down + }; + + enum SelectionState + { + SEL_NONE = 0, + SEL_SELECTED, + SEL_SELECTING, + SEL_DESELECTING, + SEL_DRAGGING + }; + + /** + * Execute all the functionality associated with the action key. + */ + void keyAction(); + + /** + * Moves the highlight in the direction specified. + * + * @param direction The move direction of the highlighter. + */ + void moveHighlight(Direction direction); + + /** + * Sets the currently selected item. + */ + void setSelectedIndex(int index); + + /** + * Determine and set the height of the container. + */ + void adjustHeight(); + + /** + * Sends out selection events to the list of selection listeners. + */ + void distributeValueChangedEvent(); + + /** + * Gets the slot index based on the cursor position. + * + * @param x The X coordinate position. + * @param y The Y coordinate position. + * @return The slot index on success, -1 on failure. + */ + int getSlotIndex(int x, int y) const; + + Inventory *mInventory; + int mGridColumns, mGridRows; + Image *mSelImg; + int mSelectedIndex, mHighlightedIndex; + int mLastUsedSlot; + SelectionState mSelectionStatus; + bool mForceQuantity; + bool mSwapItems; + bool mDescItems; + int mDragPosX, mDragPosY; + + ItemPopup *mItemPopup; + + typedef std::list<gcn::SelectionListener*> SelectionListenerList; + typedef SelectionListenerList::iterator SelectionListenerIterator; + + SelectionListenerList mSelectionListeners; +}; + +#endif diff --git a/src/gui/widgets/itemlinkhandler.cpp b/src/gui/widgets/itemlinkhandler.cpp new file mode 100644 index 000000000..7c0a65bcb --- /dev/null +++ b/src/gui/widgets/itemlinkhandler.cpp @@ -0,0 +1,66 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sstream> +#include <string> + +#include "item.h" + +#include "gui/itempopup.h" +#include "gui/viewport.h" + +#include "gui/widgets/itemlinkhandler.h" + +#include "resources/itemdb.h" + +ItemLinkHandler::ItemLinkHandler() +{ + mItemPopup = new ItemPopup; +} + +ItemLinkHandler::~ItemLinkHandler() +{ + delete mItemPopup; + mItemPopup = 0; +} + +void ItemLinkHandler::handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_) +{ + if (!mItemPopup) + return; + + int id = 0; + std::stringstream stream; + stream << link; + stream >> id; + + if (id > 0) + { + const ItemInfo &itemInfo = ItemDB::get(id); + mItemPopup->setItem(itemInfo, true); + + if (mItemPopup->isVisible()) + mItemPopup->setVisible(false); + else if (viewport) + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } +} diff --git a/src/gui/widgets/itemlinkhandler.h b/src/gui/widgets/itemlinkhandler.h new file mode 100644 index 000000000..57e135fed --- /dev/null +++ b/src/gui/widgets/itemlinkhandler.h @@ -0,0 +1,47 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ITEM_LINK_HANDLER_H +#define ITEM_LINK_HANDLER_H + +#include "gui/widgets/linkhandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ItemPopup; + +class ItemLinkHandler : public LinkHandler +{ + public: + ItemLinkHandler(); + ~ItemLinkHandler(); + void handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_); + + private: + ItemPopup *mItemPopup; +}; + +#endif diff --git a/src/gui/widgets/itemshortcutcontainer.cpp b/src/gui/widgets/itemshortcutcontainer.cpp new file mode 100644 index 000000000..2ab4a7e19 --- /dev/null +++ b/src/gui/widgets/itemshortcutcontainer.cpp @@ -0,0 +1,375 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/itemshortcutcontainer.h" + +#include "configuration.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "spellshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "spellmanager.h" +#include "textcommand.h" + +#include "gui/inventorywindow.h" +#include "gui/itempopup.h" +#include "gui/palette.h" +#include "gui/spellpopup.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" + +#include "utils/stringutils.h" + +ItemShortcutContainer::ItemShortcutContainer(unsigned number): + ShortcutContainer(), + mItemClicked(false), + mItemMoved(NULL), + mNumber(number) +{ + addMouseListener(this); + addWidgetListener(this); + + mItemPopup = new ItemPopup; + mSpellPopup = new SpellPopup; + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + if (itemShortcut[mNumber]) + mMaxItems = itemShortcut[mNumber]->getItemCount(); + else + mMaxItems = 0; + + if (mBackgroundImg) + { + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +ItemShortcutContainer::~ItemShortcutContainer() +{ + if (mBackgroundImg) + { + mBackgroundImg->decRef(); + mBackgroundImg = 0; + } + delete mItemPopup; + mItemPopup = 0; + delete mSpellPopup; + mSpellPopup = 0; +} + +void ItemShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!itemShortcut[mNumber]) + return; + + mAlpha = Client::getGuiAlpha(); + if (Client::getGuiAlpha() != mAlpha) + { + if (mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + } + + Graphics *g = static_cast<Graphics*>(graphics); + + graphics->setFont(getFont()); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int itemX = (i % mGridWidth) * mBoxWidth; + const int itemY = (i / mGridWidth) * mBoxHeight; + + if (mBackgroundImg) + g->drawImage(mBackgroundImg, itemX, itemY); + + // Draw item keyboard shortcut. + std::string key = keyboard.getKeyValueString( + keyboard.KEY_SHORTCUT_1 + i); + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + g->drawText(key, itemX + 2, itemY + 2, gcn::Graphics::LEFT); + + int itemId = itemShortcut[mNumber]->getItem(i); + + if (itemId < 0) + continue; + + // this is item + if (itemId < SPELL_MIN_ID) + { + if (!PlayerInfo::getInventory()) + continue; + + Item *item = PlayerInfo::getInventory()->findItem(itemId); + + if (item) + { + // Draw item icon. + Image* image = item->getImage(); + + if (image) + { + std::string caption; + if (item->getQuantity() > 1) + caption = toString(item->getQuantity()); + else if (item->isEquipped()) + caption = "Eq."; + + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + if (item->isEquipped()) + { + g->setColor(Theme::getThemeColor( + Theme::ITEM_EQUIPPED)); + } + else + { + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + } + g->drawText(caption, itemX + mBoxWidth / 2, + itemY + mBoxHeight - 14, gcn::Graphics::CENTER); + } + } + } + else if (spellManager) // this is magic shortcut + { + TextCommand *spell = spellManager->getSpellByItem(itemId); + if (spell) + { + if (!spell->isEmpty()) + { + Image* image = spell->getImage(); + + if (image) + { + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + } + } + + g->drawText(spell->getSymbol(), itemX + 2, + itemY + mBoxHeight / 2, gcn::Graphics::LEFT); + } + } + + } + + if (mItemMoved) + { + // Draw the item image being dragged by the cursor. + Image* image = mItemMoved->getImage(); + if (image) + { + const int tPosX = mCursorPosX - (image->getWidth() / 2); + const int tPosY = mCursorPosY - (image->getHeight() / 2); + + g->drawImage(image, tPosX, tPosY); + g->drawText(toString(mItemMoved->getQuantity()), + tPosX + mBoxWidth / 2, tPosY + mBoxHeight - 14, + gcn::Graphics::CENTER); + } + } +} + +void ItemShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mItemMoved && mItemClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = itemShortcut[mNumber]->getItem(index); + + if (itemId < 0) + return; + + if (itemId < SPELL_MIN_ID) + { + if (!PlayerInfo::getInventory()) + return; + + Item *item = PlayerInfo::getInventory()->findItem(itemId); + + if (item) + { + mItemMoved = item; + itemShortcut[mNumber]->removeItem(index); + } + } + else if (spellManager) + { + TextCommand *spell = spellManager->getSpellByItem(itemId); + if (spell) + itemShortcut[mNumber]->removeItem(index); + } + } + if (mItemMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void ItemShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Stores the selected item if theirs one. + if (itemShortcut[mNumber]->isItemSelected() && + (inventoryWindow && (inventoryWindow->isVisible() || + itemShortcut[mNumber]->getSelectedItem() >= SPELL_MIN_ID))) + { + itemShortcut[mNumber]->setItem(index); + itemShortcut[mNumber]->setItemSelected(-1); + if (spellShortcut) + spellShortcut->setItemSelected(-1); + } + else if (itemShortcut[mNumber]->getItem(index)) + { + mItemClicked = true; + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { +// Item *item = PlayerInfo::getInventory()->findItem(id); + + if (viewport && itemShortcut[mNumber]) + viewport->showItemPopup(itemShortcut[mNumber]->getItem(index)); + } +} + +void ItemShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (itemShortcut[mNumber]->isItemSelected()) + itemShortcut[mNumber]->setItemSelected(-1); + + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + mItemMoved = NULL; + return; + } + if (mItemMoved) + { + itemShortcut[mNumber]->setItems(index, mItemMoved->getId()); + mItemMoved = NULL; + } + else if (itemShortcut[mNumber]->getItem(index) && mItemClicked) + { + itemShortcut[mNumber]->useItem(index); + } + + if (mItemClicked) + mItemClicked = false; + } +} + +// Show ItemTooltip +void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = itemShortcut[mNumber]->getItem(index); + + if (itemId < 0) + return; + + if (itemId < SPELL_MIN_ID) + { + mSpellPopup->setVisible(false); + + if (!PlayerInfo::getInventory()) + return; + + Item *item = PlayerInfo::getInventory()->findItem(itemId); + + if (item && viewport) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } + } + else if (spellManager) + { + mItemPopup->setVisible(false); + TextCommand *spell = spellManager->getSpellByItem(itemId); + if (spell && viewport) + { + mSpellPopup->setItem(spell); + mSpellPopup->view(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mSpellPopup->setVisible(false); + } + } +} + +// Hide ItemTooltip +void ItemShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mItemPopup->setVisible(false); + mSpellPopup->setVisible(false); +} diff --git a/src/gui/widgets/itemshortcutcontainer.h b/src/gui/widgets/itemshortcutcontainer.h new file mode 100644 index 000000000..0f7067e38 --- /dev/null +++ b/src/gui/widgets/itemshortcutcontainer.h @@ -0,0 +1,93 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ITEMSHORTCUTCONTAINER_H +#define ITEMSHORTCUTCONTAINER_H + +#include "spellmanager.h" + +#include "gui/widgets/shortcutcontainer.h" + +#include <guichan/mouselistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class Item; +class ItemPopup; +class SpellPopup; + +/** + * An item shortcut container. Used to quickly use items. + * + * \ingroup GUI + */ +class ItemShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + ItemShortcutContainer(unsigned number); + + /** + * Destructor. + */ + virtual ~ItemShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + bool mItemClicked; + Item *mItemMoved; + unsigned mNumber; + + ItemPopup *mItemPopup; + SpellPopup *mSpellPopup; +}; + +//extern SpellManager *spellManager; +#endif diff --git a/src/gui/widgets/label.cpp b/src/gui/widgets/label.cpp new file mode 100644 index 000000000..e9b4cb0f6 --- /dev/null +++ b/src/gui/widgets/label.cpp @@ -0,0 +1,38 @@ +/* + * The Mana Client + * Copyright (c) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/label.h" + +#include "gui/theme.h" + +Label::Label() +{ +} + +Label::Label(const std::string &caption) : + gcn::Label(caption) +{ + setForegroundColor(Theme::getThemeColor(Theme::TEXT)); +} + +void Label::draw(gcn::Graphics *graphics) +{ + gcn::Label::draw(static_cast<gcn::Graphics*>(graphics)); +} diff --git a/src/gui/widgets/label.h b/src/gui/widgets/label.h new file mode 100644 index 000000000..2dfa254c4 --- /dev/null +++ b/src/gui/widgets/label.h @@ -0,0 +1,52 @@ +/* + * The Mana Client + * Copyright (c) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LABEL_H +#define LABEL_H + +#include <guichan/widgets/label.hpp> + +/** + * Label widget. Same as the Guichan label but modified to use the palette + * system. + * + * \ingroup GUI + */ +class Label : public gcn::Label +{ + public: + /** + * Constructor. + */ + Label(); + + /** + * Constructor. This version of the constructor sets the label with an + * inintialization string. + */ + Label(const std::string &caption); + + /** + * Draws the label. + */ + void draw(gcn::Graphics *graphics); +}; + +#endif diff --git a/src/gui/widgets/layout.cpp b/src/gui/widgets/layout.cpp new file mode 100644 index 000000000..38c6bb471 --- /dev/null +++ b/src/gui/widgets/layout.cpp @@ -0,0 +1,362 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/layout.h" + +#include "log.h" + +#include <cassert> + +ContainerPlacer ContainerPlacer::at(int x, int y) +{ + return ContainerPlacer(mContainer, &mCell->at(x, y)); +} + +LayoutCell &ContainerPlacer::operator() + (int x, int y, gcn::Widget *wg, int w, int h) +{ + mContainer->add(wg); + return mCell->place(wg, x, y, w, h); +} + +LayoutCell::~LayoutCell() +{ + if (mType == ARRAY) + { + delete mArray; + mArray = 0; + } +} + +LayoutArray &LayoutCell::getArray() +{ + assert(mType != WIDGET); + if (mType == ARRAY) + return *mArray; + + mArray = new LayoutArray; + mType = ARRAY; + mExtent[0] = 1; + mExtent[1] = 1; + mPadding = 0; + mAlign[0] = FILL; + mAlign[1] = FILL; + return *mArray; +} + +void LayoutCell::reflow(int nx, int ny, int nw, int nh) +{ + assert(mType != NONE); + nx += mPadding; + ny += mPadding; + nw -= 2 * mPadding; + nh -= 2 * mPadding; + if (mType == ARRAY) + mArray->reflow(nx, ny, nw, nh); + else + mWidget->setDimension(gcn::Rectangle(nx, ny, nw, nh)); +} + +void LayoutCell::computeSizes() +{ + assert(mType == ARRAY); + + std::vector< std::vector< LayoutCell * > >::iterator + i = mArray->mCells.begin(); + + while (i != mArray->mCells.end()) + { + std::vector< LayoutCell * >::iterator j = i->begin(); + while (j != i->end()) + { + LayoutCell *cell = *j; + if (cell && cell->mType == ARRAY) + cell->computeSizes(); + + ++j; + } + ++i; + } + + mSize[0] = mArray->getSize(0); + mSize[1] = mArray->getSize(1); +} + +LayoutArray::LayoutArray(): mSpacing(4) +{ +} + +LayoutArray::~LayoutArray() +{ + std::vector< std::vector< LayoutCell * > >::iterator i = mCells.begin(); + while (i != mCells.end()) + { + std::vector< LayoutCell * >::iterator j = i->begin(); + while (j != i->end()) + { + delete *j; + ++j; + } + ++i; + } +} + +LayoutCell &LayoutArray::at(int x, int y, int w, int h) +{ + resizeGrid(x + w, y + h); + LayoutCell *&cell = mCells[y][x]; + if (!cell) + cell = new LayoutCell; + return *cell; +} + +void LayoutArray::resizeGrid(int w, int h) +{ + bool extW = w && w > static_cast<int>(mSizes[0].size()), + extH = h && h > static_cast<int>(mSizes[1].size()); + + if (!extW && !extH) + return; + + if (extH) + { + mSizes[1].resize(h, Layout::AUTO_DEF); + mCells.resize(h); + if (!extW) + w = static_cast<int>(mSizes[0].size()); + } + + if (extW) + mSizes[0].resize(w, Layout::AUTO_DEF); + + std::vector< std::vector< LayoutCell * > >::iterator i = mCells.begin(); + while (i != mCells.end()) + { + i->resize(w, 0); + ++i; + } +} + +void LayoutArray::setColWidth(int n, int w) +{ + resizeGrid(n + 1, 0); + mSizes[0][n] = w; +} + +void LayoutArray::setRowHeight(int n, int h) +{ + resizeGrid(0, n + 1); + mSizes[1][n] = h; +} + +void LayoutArray::matchColWidth(int n1, int n2) +{ + resizeGrid(std::max(n1, n2) + 1, 0); + std::vector<int> widths = getSizes(0, Layout::AUTO_DEF); + int s = std::max(widths[n1], widths[n2]); + mSizes[0][n1] = s; + mSizes[0][n2] = s; +} + +void LayoutArray::extend(int x, int y, int w, int h) +{ + LayoutCell &cell = at(x, y, w, h); + cell.mExtent[0] = w; + cell.mExtent[1] = h; +} + +LayoutCell &LayoutArray::place(gcn::Widget *widget, int x, int y, int w, int h) +{ + LayoutCell &cell = at(x, y, w, h); + assert(cell.mType == LayoutCell::NONE); + cell.mType = LayoutCell::WIDGET; + cell.mWidget = widget; + if (widget) + { + cell.mSize[0] = w == 1 ? widget->getWidth() : 0; + cell.mSize[1] = h == 1 ? widget->getHeight() : 0; + } + else + { + cell.mSize[0] = 1; + cell.mSize[1] = 1; + } + cell.mExtent[0] = w; + cell.mExtent[1] = h; + cell.mPadding = 0; + cell.mAlign[0] = LayoutCell::FILL; + cell.mAlign[1] = LayoutCell::FILL; + int &cs = mSizes[0][x], &rs = mSizes[1][y]; + if (cs == Layout::AUTO_DEF && w == 1) + cs = 0; + if (rs == Layout::AUTO_DEF && h == 1) + rs = 0; + return cell; +} + +void LayoutArray::align(int &pos, int &size, int dim, + LayoutCell const &cell, int *sizes) const +{ + int size_max = sizes[0]; + for (int i = 1; i < cell.mExtent[dim]; ++i) + size_max += sizes[i] + mSpacing; + size = std::min<int>(cell.mSize[dim], size_max); + + switch (cell.mAlign[dim]) + { + case LayoutCell::LEFT: + return; + case LayoutCell::RIGHT: + pos += size_max - size; + return; + case LayoutCell::CENTER: + pos += (size_max - size) / 2; + return; + case LayoutCell::FILL: + size = size_max; + return; + default: + logger->log1("LayoutArray::align unknown layout"); + return; + } +} + +std::vector<int> LayoutArray::getSizes(int dim, int upp) const +{ + int gridW = static_cast<int>(mSizes[0].size()), + gridH = static_cast<int>(mSizes[1].size()); + std::vector<int> sizes = mSizes[dim]; + + // Compute minimum sizes. + for (int gridY = 0; gridY < gridH; ++gridY) + { + for (int gridX = 0; gridX < gridW; ++gridX) + { + LayoutCell const *cell = mCells[gridY][gridX]; + if (!cell || cell->mType == LayoutCell::NONE) + continue; + + if (cell->mExtent[dim] == 1) + { + int n = (dim == 0 ? gridX : gridY); + int s = cell->mSize[dim] + cell->mPadding * 2; + if (s > sizes[n]) sizes[n] = s; + } + } + } + + if (upp == Layout::AUTO_DEF) return sizes; + + // Compute the FILL sizes. + int nb = static_cast<int>(sizes.size()); + int nbFill = 0; + for (int i = 0; i < nb; ++i) + { + if (mSizes[dim][i] <= Layout::AUTO_DEF) + { + ++nbFill; + if (mSizes[dim][i] == Layout::AUTO_SET || + sizes[i] <= Layout::AUTO_DEF) + { + sizes[i] = 0; + } + } + upp -= sizes[i] + mSpacing; + } + upp = upp + mSpacing; + + if (nbFill == 0) + return sizes; + + for (int i = 0; i < nb; ++i) + { + if (mSizes[dim][i] > Layout::AUTO_DEF) + continue; + + int s = upp / nbFill; + sizes[i] += s; + upp -= s; + --nbFill; + } + + return sizes; +} + +int LayoutArray::getSize(int dim) const +{ + std::vector<int> sizes = getSizes(dim, Layout::AUTO_DEF); + int size = 0; + int nb = static_cast<int>(sizes.size()); + for (int i = 0; i < nb; ++i) + { + if (sizes[i] > Layout::AUTO_DEF) + size += sizes[i]; + size += mSpacing; + } + return size - mSpacing; +} + +void LayoutArray::reflow(int nx, int ny, int nw, int nh) +{ + int gridW = static_cast<int>(mSizes[0].size()), + gridH = static_cast<int>(mSizes[1].size()); + + std::vector<int> widths = getSizes(0, nw); + std::vector<int> heights = getSizes(1, nh); + + int y = ny; + for (int gridY = 0; gridY < gridH; ++gridY) + { + int x = nx; + for (int gridX = 0; gridX < gridW; ++gridX) + { + LayoutCell *cell = mCells[gridY][gridX]; + if (cell && cell->mType != LayoutCell::NONE) + { + int dx = x, dy = y, dw, dh; + align(dx, dw, 0, *cell, &widths[gridX]); + align(dy, dh, 1, *cell, &heights[gridY]); + cell->reflow(dx, dy, dw, dh); + } + x += widths[gridX] + mSpacing; + } + y += heights[gridY] + mSpacing; + } +} + +Layout::Layout(): mComputed(false) +{ + getArray(); + setPadding(6); +} + +void Layout::reflow(int &nw, int &nh) +{ + if (!mComputed) + { + computeSizes(); + mComputed = true; + } + + nw = (nw == 0 ? mSize[0] + 2 * mPadding : nw); + nh = (nh == 0 ? mSize[1] + 2 * mPadding : nh); + LayoutCell::reflow(0, 0, nw, nh); +} diff --git a/src/gui/widgets/layout.h b/src/gui/widgets/layout.h new file mode 100644 index 000000000..4c1e40bb9 --- /dev/null +++ b/src/gui/widgets/layout.h @@ -0,0 +1,319 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef WIDGET_LAYOUT_H +#define WIDGET_LAYOUT_H + +#include <guichan/widgets/container.hpp> + +#include <vector> + +class LayoutCell; + +/** + * This class is a helper for adding widgets to nested tables in a window. + */ +class ContainerPlacer +{ + public: + ContainerPlacer(gcn::Container *c = NULL, LayoutCell *l = NULL): + mContainer(c), mCell(l) + {} + + /** + * Gets the pointed cell. + */ + LayoutCell &getCell() + { return *mCell; } + + /** + * Returns a placer for the same container but to an inner cell. + */ + ContainerPlacer at(int x, int y); + + /** + * Adds the given widget to the container and places it in the layout. + * @see LayoutArray::place + */ + LayoutCell &operator() + (int x, int y, gcn::Widget *, int w = 1, int h = 1); + + private: + gcn::Container *mContainer; + LayoutCell *mCell; +}; + +/** + * This class contains a rectangular array of cells. + */ +class LayoutArray +{ + friend class LayoutCell; + + public: + + LayoutArray(); + + ~LayoutArray(); + + /** + * Returns a reference on the cell at given position. + */ + LayoutCell &at(int x, int y, int w = 1, int h = 1); + + /** + * Places a widget in a given cell. + * @param w number of columns the widget spawns. + * @param h number of rows the widget spawns. + * @note When @a w is 1, the width of column @a x is reset to zero if + * it was AUTO_DEF. Similarly for @a h. + */ + LayoutCell &place(gcn::Widget *, int x, int y, int w = 1, int h = 1); + + /** + * Sets the minimum width of a column. + */ + void setColWidth(int n, int w); + + /** + * Sets the minimum height of a row. + */ + void setRowHeight(int n, int h); + + /** + * Sets the widths of two columns to the maximum of their widths. + */ + void matchColWidth(int n1, int n2); + + /** + * Spawns a cell over several columns/rows. + */ + void extend(int x, int y, int w, int h); + + /** + * Computes and sets the positions of all the widgets. + * @param nW width of the array, used to resize the AUTO_ columns. + * @param nH height of the array, used to resize the AUTO_ rows. + */ + void reflow(int nX, int nY, int nW, int nH); + + private: + + // Copy not allowed, as the array owns all its cells. + LayoutArray(LayoutArray const &); + LayoutArray &operator=(LayoutArray const &); + + /** + * Gets the position and size of a widget along a given axis + */ + void align(int &pos, int &size, int dim, + LayoutCell const &cell, int *sizes) const; + + /** + * Ensures the private vectors are large enough. + */ + void resizeGrid(int w, int h); + + /** + * Gets the column/row sizes along a given axis. + * @param upp target size for the array. Ignored if AUTO_DEF. + */ + std::vector<int> getSizes(int dim, int upp) const; + + /** + * Gets the total size along a given axis. + */ + int getSize(int dim) const; + + std::vector<int> mSizes[2]; + std::vector< std::vector < LayoutCell * > > mCells; + + int mSpacing; +}; + +/** + * This class describes the formatting of a widget in the cell of a layout + * table. Horizontally, a widget can either fill the width of the cell (minus + * the cell padding), or it can retain its size and be flushed left, or flush + * right, or centered in the cell. The process is similar for the vertical + * alignment, except that top is represented by LEFT and bottom by RIGHT. + */ +class LayoutCell +{ + friend class Layout; + friend class LayoutArray; + + public: + enum Alignment + { + LEFT = 0, + RIGHT, + CENTER, + FILL + }; + + LayoutCell(): mType(NONE) {} + + ~LayoutCell(); + + /** + * Sets the padding around the cell content. + */ + LayoutCell &setPadding(int p) + { mPadding = p; return *this; } + + /** + * Sets the horizontal alignment of the cell content. + */ + LayoutCell &setHAlign(Alignment a) + { mAlign[0] = a; return *this; } + + /** + * Sets the vertical alignment of the cell content. + */ + LayoutCell &setVAlign(Alignment a) + { mAlign[1] = a; return *this; } + + /** + * @see LayoutArray::at + */ + LayoutCell &at(int x, int y) + { return getArray().at(x, y); } + + /** + * @see LayoutArray::place + */ + LayoutCell &place(gcn::Widget *wg, int x, int y, int w = 1, int h = 1) + { return getArray().place(wg, x, y, w, h); } + + /** + * @see LayoutArray::matchColWidth + */ + void matchColWidth(int n1, int n2) + { getArray().matchColWidth(n1, n2); } + + /** + * @see LayoutArray::setColWidth + */ + void setColWidth(int n, int w) + { getArray().setColWidth(n, w); } + + /** + * @see LayoutArray::setRowHeight + */ + void setRowHeight(int n, int h) + { getArray().setRowHeight(n, h); } + + /** + * @see LayoutArray::extend. + */ + void extend(int x, int y, int w, int h) + { getArray().extend(x, y, w, h); } + + /** + * Sets the minimum widths and heights of this cell and of all the + * inner cells. + */ + void computeSizes(); + + private: + // Copy not allowed, as the cell may own an array. + LayoutCell(LayoutCell const &); + LayoutCell &operator=(LayoutCell const &); + + union + { + gcn::Widget *mWidget; + LayoutArray *mArray; + }; + + enum + { + NONE = 0, + WIDGET, + ARRAY + }; + + /** + * Returns the embedded array. Creates it if the cell does not contain + * anything yet. Aborts if it contains a widget. + */ + LayoutArray &getArray(); + + /** + * @see LayoutArray::reflow + */ + void reflow(int nx, int ny, int nw, int nh); + + int mSize[2]; + int mPadding; + int mExtent[2]; + int mAlign[2]; + int mNbFill[2]; + int mType; +}; + +/** + * This class is an helper for setting the position of widgets. They are + * positioned along the cells of some rectangular tables. The layout may either + * be a single table or a tree of nested tables. + * + * The size of a given table column can either be set manually or be chosen + * from the widest widget of the column. An empty column has a AUTO_DEF width, + * which means it will be extended so that the layout fits its minimum width. + * + * The process is similar for table rows. By default, there is a spacing of 4 + * pixels between rows and between columns, and a margin of 6 pixels around the + * whole layout. + */ +class Layout : public LayoutCell +{ + public: + Layout(); + + /** + * Sets the margin around the layout. + */ + void setMargin(int m) + { setPadding(m); } + + /** + * Sets the positions of all the widgets. + * @see LayoutArray::reflow + */ + void reflow(int &nW, int &nH); + + /** + * When the minimum size of the layout is less than the available size, + * the remaining pixels are equally split amongst the FILL items. + */ + enum + { + AUTO_DEF = -42, /**< Default value, behaves like AUTO_ADD. */ + AUTO_SET = -43, /**< Uses the share as the new size. */ + AUTO_ADD = -44 /**< Adds the share to the current size. */ + }; + + private: + bool mComputed; +}; + +#endif // WIDGET_LAYOUT_H diff --git a/src/gui/widgets/layouthelper.cpp b/src/gui/widgets/layouthelper.cpp new file mode 100644 index 000000000..a12de9bff --- /dev/null +++ b/src/gui/widgets/layouthelper.cpp @@ -0,0 +1,63 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/layouthelper.h" + +LayoutHelper::LayoutHelper(gcn::Container *container): + mContainer(container) +{ + mContainer->addWidgetListener(this); +} + +LayoutHelper::~LayoutHelper() +{ + mContainer->removeWidgetListener(this); +} + +Layout &LayoutHelper::getLayout() +{ + return mLayout; +} + +LayoutCell &LayoutHelper::place(int x, int y, gcn::Widget *wg, int w, int h) +{ + mContainer->add(wg); + return mLayout.place(wg, x, y, w, h); +} + +ContainerPlacer LayoutHelper::getPlacer(int x, int y) +{ + return ContainerPlacer(mContainer, &mLayout.at(x, y)); +} + +void LayoutHelper::reflowLayout(int w, int h) +{ + mLayout.reflow(w, h); + mContainer->setSize(w, h); +} + +void LayoutHelper::widgetResized(const gcn::Event &event _UNUSED_) +{ + const gcn::Rectangle area = mContainer->getChildrenArea(); + int w = area.width; + int h = area.height; + mLayout.reflow(w, h); +} diff --git a/src/gui/widgets/layouthelper.h b/src/gui/widgets/layouthelper.h new file mode 100644 index 000000000..033457429 --- /dev/null +++ b/src/gui/widgets/layouthelper.h @@ -0,0 +1,90 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LAYOUTHELPER_H +#define LAYOUTHELPER_H + +#include "gui/widgets/layout.h" + +#include <guichan/widgetlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * A helper class for adding a layout to a Guichan container widget. The layout + * will register itself as a widget listener and relayout the widgets in the + * container dynamically on resize. + */ +class LayoutHelper : public gcn::WidgetListener +{ + public: + /** + * Constructor. + */ + LayoutHelper(gcn::Container *container); + + /** + * Destructor. + */ + ~LayoutHelper(); + + /** + * Gets the layout handler. + */ + Layout &getLayout(); + + /** + * Computes the position of the widgets according to the current + * layout. Resizes the managed container so that the layout fits. + * + * @note This function is meant to be called with fixed-size + * containers. + * + * @param w if non-zero, force the container to this width. + * @param h if non-zero, force the container to this height. + */ + void reflowLayout(int w = 0, int h = 0); + + /** + * Adds a widget to the container and sets it at given cell. + */ + LayoutCell &place(int x, int y, gcn::Widget *, int w = 1, int h = 1); + + /** + * Returns a proxy for adding widgets in an inner table of the layout. + */ + ContainerPlacer getPlacer(int x, int y); + + /** + * Called whenever the managed container changes size. + */ + void widgetResized(const gcn::Event &event); + + private: + Layout mLayout; /**< Layout handler */ + gcn::Container *mContainer; /**< Managed container */ +}; + +#endif // LAYOUTHELPER_H diff --git a/src/gui/widgets/linkhandler.h b/src/gui/widgets/linkhandler.h new file mode 100644 index 000000000..d9d0f1161 --- /dev/null +++ b/src/gui/widgets/linkhandler.h @@ -0,0 +1,42 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LINK_HANDLER_H +#define LINK_HANDLER_H + +#include <string> + +#include <guichan/mouselistener.hpp> + +/** + * A simple interface to windows that need to handle links from BrowserBox + * widget. + */ +class LinkHandler +{ + public: + virtual ~LinkHandler() { } + + virtual void handleLink(const std::string &link, + gcn::MouseEvent *event) = 0; +}; + +#endif diff --git a/src/gui/widgets/listbox.cpp b/src/gui/widgets/listbox.cpp new file mode 100644 index 000000000..8dcc4ae67 --- /dev/null +++ b/src/gui/widgets/listbox.cpp @@ -0,0 +1,146 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/listbox.h" + +#include "client.h" +#include "configuration.h" + +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include <guichan/font.hpp> +#include <guichan/graphics.hpp> +#include <guichan/key.hpp> +#include <guichan/listmodel.hpp> + +float ListBox::mAlpha = 1.0; + +ListBox::ListBox(gcn::ListModel *listModel): + gcn::ListBox(listModel) +{ +} + +ListBox::~ListBox() +{ +} + +void ListBox::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + mAlpha = alpha; +} + +void ListBox::draw(gcn::Graphics *graphics) +{ + if (!mListModel) + return; + + updateAlpha(); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast<int>(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + const int height = getRowHeight(); + + // Draw filled rectangle around the selected list element + if (mSelected >= 0) + { + graphics->fillRectangle(gcn::Rectangle(0, height * mSelected, + getWidth(), height)); + } + + // Draw the list elements + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + for (int i = 0, y = 0; i < mListModel->getNumberOfElements(); + ++i, y += height) + { + graphics->drawText(mListModel->getElementAt(i), 1, y); + } +} + +void ListBox::keyPressed(gcn::KeyEvent& keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE) + { + distributeActionEvent(); + keyEvent.consume(); + } + else if (key.getValue() == Key::UP) + { + if (getSelected() > 0) + setSelected(mSelected - 1); + else if (getSelected() == 0 && mWrappingEnabled && getListModel()) + setSelected(getListModel()->getNumberOfElements() - 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::DOWN) + { + if (getSelected() < (getListModel()->getNumberOfElements() - 1)) + { + setSelected(mSelected + 1); + } + else if (getSelected() == (getListModel()->getNumberOfElements() - 1) + && mWrappingEnabled) + { + setSelected(0); + } + keyEvent.consume(); + } + else if (key.getValue() == Key::HOME) + { + setSelected(0); + keyEvent.consume(); + } + else if (key.getValue() == Key::END && getListModel()) + { + setSelected(getListModel()->getNumberOfElements() - 1); + keyEvent.consume(); + } +} + +// Don't do anything on scrollwheel. ScrollArea will deal with that. + +void ListBox::mouseWheelMovedUp(gcn::MouseEvent &mouseEvent _UNUSED_) +{ +} + +void ListBox::mouseWheelMovedDown(gcn::MouseEvent &mouseEvent _UNUSED_) +{ +} + +void ListBox::mouseDragged(gcn::MouseEvent &event) +{ + if (event.getButton() != gcn::MouseEvent::LEFT || getRowHeight() == 0) + return; + + // Make list selection update on drag, but guard against negative y + int y = std::max(0, event.getY()); + if (getRowHeight()) + setSelected(y / getRowHeight()); +} diff --git a/src/gui/widgets/listbox.h b/src/gui/widgets/listbox.h new file mode 100644 index 000000000..721e1bd36 --- /dev/null +++ b/src/gui/widgets/listbox.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LISTBOX_H +#define LISTBOX_H + +#include <guichan/widgets/listbox.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class SelectionListener; + +/** + * A list box, meant to be used inside a scroll area. Same as the Guichan list + * box except this one doesn't have a background, instead completely relying + * on the scroll area. It also adds selection listener functionality. + * + * \ingroup GUI + */ +class ListBox : public gcn::ListBox +{ + public: + /** + * Constructor. + */ + ListBox(gcn::ListModel *listModel); + + ~ListBox(); + + /** + * Draws the list box. + */ + void draw(gcn::Graphics *graphics); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + // Inherited from KeyListener + + void keyPressed(gcn::KeyEvent& keyEvent); + + // Inherited from MouseListener + + void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent); + + void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent); + + void mouseDragged(gcn::MouseEvent &event); + + protected: + static float mAlpha; +}; + +#endif diff --git a/src/gui/widgets/passwordfield.cpp b/src/gui/widgets/passwordfield.cpp new file mode 100644 index 000000000..14b924bbd --- /dev/null +++ b/src/gui/widgets/passwordfield.cpp @@ -0,0 +1,36 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "passwordfield.h" + +PasswordField::PasswordField(const std::string &text): + TextField(text) +{ +} + +void PasswordField::draw(gcn::Graphics *graphics) +{ + // std::string uses cow, thus cheap copy + const std::string original = mText; + mText.assign(mText.length(), '*'); + TextField::draw(graphics); + mText = original; +} diff --git a/src/gui/widgets/passwordfield.h b/src/gui/widgets/passwordfield.h new file mode 100644 index 000000000..619cd8420 --- /dev/null +++ b/src/gui/widgets/passwordfield.h @@ -0,0 +1,46 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PASSWORDFIELD_H +#define PASSWORDFIELD_H + +#include "textfield.h" + +/** + * A password field. + * + * \ingroup GUI + */ +class PasswordField : public TextField +{ + public: + /** + * Constructor, initializes the password field with the given string. + */ + PasswordField(const std::string &text = ""); + + /** + * Draws the password field. + */ + void draw(gcn::Graphics *graphics); +}; + +#endif diff --git a/src/gui/widgets/playerbox.cpp b/src/gui/widgets/playerbox.cpp new file mode 100644 index 000000000..e3a660120 --- /dev/null +++ b/src/gui/widgets/playerbox.cpp @@ -0,0 +1,120 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/playerbox.h" + +#include "animatedsprite.h" +#include "being.h" +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +int PlayerBox::instances = 0; +float PlayerBox::mAlpha = 1.0; +ImageRect PlayerBox::background; + +PlayerBox::PlayerBox(const Being *being): + mBeing(being) +{ + setFrameSize(2); + + if (instances == 0) + { + // Load the background skin + Image *textbox = Theme::getImageFromTheme("deepbox.png"); + int bggridx[4] = {0, 3, 28, 31}; + int bggridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (textbox) + { + background.grid[a] = textbox->getSubImage( + bggridx[x], bggridy[y], + bggridx[x + 1] - bggridx[x] + 1, + bggridy[y + 1] - bggridy[y] + 1); + if (background.grid[a]) + background.grid[a]->setAlpha(Client::getGuiAlpha()); + } + else + { + background.grid[a] = 0; + } + a++; + } + } + + if (textbox) + textbox->decRef(); + } + + instances++; +} + +PlayerBox::~PlayerBox() +{ + instances--; + + mBeing = 0; + + if (instances == 0) + for_each(background.grid, background.grid + 9, dtor<Image*>()); +} + +void PlayerBox::draw(gcn::Graphics *graphics) +{ + if (mBeing) + { + // Draw character + const int bs = getFrameSize(); + const int x = getWidth() / 2 + bs - 16; + const int y = getHeight() - bs - 32; + mBeing->drawSpriteAt(static_cast<Graphics*>(graphics), x, y); + } + + if (Client::getGuiAlpha() != mAlpha) + { + for (int a = 0; a < 9; a++) + { + if (background.grid[a]) + background.grid[a]->setAlpha(Client::getGuiAlpha()); + } + } +} + +void PlayerBox::drawFrame(gcn::Graphics *graphics) +{ + int w, h, bs; + bs = getFrameSize(); + w = getWidth() + bs * 2; + h = getHeight() + bs * 2; + + static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, background); +} diff --git a/src/gui/widgets/playerbox.h b/src/gui/widgets/playerbox.h new file mode 100644 index 000000000..4505367f8 --- /dev/null +++ b/src/gui/widgets/playerbox.h @@ -0,0 +1,74 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PLAYERBOX_H +#define PLAYERBOX_H + +#include <guichan/widgets/scrollarea.hpp> + +class Being; +class ImageRect; + +/** + * A box showing a player character. + * + * \ingroup GUI + */ +class PlayerBox : public gcn::ScrollArea +{ + public: + /** + * Constructor. Takes the initial player character that this box should + * display, which defaults to <code>NULL</code>. + */ + PlayerBox(const Being *being = 0); + + /** + * Destructor. + */ + ~PlayerBox(); + + /** + * Sets a new player character to be displayed by this box. Setting the + * player to <code>NULL</code> causes the box not to draw any + * character. + */ + void setPlayer(const Being *being) { mBeing = being; } + + /** + * Draws the scroll area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Draws the background and border of the scroll area. + */ + void drawFrame(gcn::Graphics *graphics); + + private: + const Being *mBeing; /**< The character used for display */ + + static float mAlpha; + static int instances; + static ImageRect background; +}; + +#endif diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp new file mode 100644 index 000000000..fa955c8f3 --- /dev/null +++ b/src/gui/widgets/popup.cpp @@ -0,0 +1,174 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/popup.h" + +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/windowcontainer.h" + +#include "resources/image.h" + +#include <guichan/exception.hpp> + +Popup::Popup(const std::string &name, const std::string &skin): + mPopupName(name), + mMinWidth(100), + mMinHeight(40), + mMaxWidth(graphics->getWidth()), + mMaxHeight(graphics->getHeight()) +{ + logger->log("Popup::Popup(\"%s\")", name.c_str()); + + if (!windowContainer) + throw GCN_EXCEPTION("Popup::Popup(): no windowContainer set"); + + setPadding(3); + + // Loads the skin + mSkin = Theme::instance()->load(skin); + + // Add this window to the window container + windowContainer->add(this); + + // Popups are invisible by default + setVisible(false); +} + +Popup::~Popup() +{ + logger->log("Popup::~Popup(\"%s\")", mPopupName.c_str()); + + if (mSkin) + mSkin->instances--; +} + +void Popup::setWindowContainer(WindowContainer *wc) +{ + windowContainer = wc; +} + +void Popup::draw(gcn::Graphics *graphics) +{ + Graphics *g = static_cast<Graphics*>(graphics); + + g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); + + drawChildren(graphics); +} + +gcn::Rectangle Popup::getChildrenArea() +{ + return gcn::Rectangle(getPadding(), 0, getWidth() - getPadding() * 2, + getHeight() - getPadding() * 2); +} + +void Popup::setContentSize(int width, int height) +{ + width += 2 * getPadding(); + height += 2 * getPadding(); + + if (getMinWidth() > width) + width = getMinWidth(); + else if (getMaxWidth() < width) + width = getMaxWidth(); + if (getMinHeight() > height) + height = getMinHeight(); + else if (getMaxHeight() < height) + height = getMaxHeight(); + + setSize(width, height); +} + +void Popup::setLocationRelativeTo(gcn::Widget *widget) +{ + if (!widget) + return; + + int wx, wy; + int x, y; + + widget->getAbsolutePosition(wx, wy); + getAbsolutePosition(x, y); + + setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x), + getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y)); +} + +void Popup::setMinWidth(int width) +{ + mMinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth(); +} + +void Popup::setMinHeight(int height) +{ + mMinHeight = height > mSkin->getMinHeight() ? + height : mSkin->getMinHeight(); +} + +void Popup::setMaxWidth(int width) +{ + mMaxWidth = width; +} + +void Popup::setMaxHeight(int height) +{ + mMaxHeight = height; +} + +void Popup::scheduleDelete() +{ + windowContainer->scheduleDelete(this); +} + +void Popup::position(int x, int y) +{ + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + posX = graphics->getWidth() - getWidth(); + if (posY + getHeight() > graphics->getHeight()) + posY = y - getHeight() - distance; + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void Popup::mouseMoved(gcn::MouseEvent &event _UNUSED_) +{ + if (viewport) + viewport->hideBeingPopup(); +} + +void Popup::hide() +{ + setVisible(false); +}
\ No newline at end of file diff --git a/src/gui/widgets/popup.h b/src/gui/widgets/popup.h new file mode 100644 index 000000000..c83368e52 --- /dev/null +++ b/src/gui/widgets/popup.h @@ -0,0 +1,174 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef POPUP_H +#define POPUP_H + +#include "configuration.h" +#include "guichanfwd.h" + +#include "gui/widgets/container.h" + +#include <guichan/mouselistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Skin; +class WindowContainer; + +/** + * A light version of the Window class. Particularly suited for popup type + * functionality that doesn't need to be resized or moved around by the mouse + * once created, but only needs to display some simple content, like a static + * message. + * + * Popups, in general, shouldn't also need to update their content once + * created, although this is not an explicit requirement to use the popup + * class. + * + * \ingroup GUI + */ +class Popup : public Container, public gcn::MouseListener +{ + public: + /** + * Constructor. Initializes the title to the given text and hooks + * itself into the popup container. + * + * @param name A human readable name for the popup. Only useful for + * debugging purposes. + * @param skin The location where the Popup's skin XML can be found. + */ + Popup(const std::string &name = "", + const std::string &skin = "window.xml"); + + /** + * Destructor. Deletes all the added widgets. + */ + ~Popup(); + + /** + * Sets the window container to be used by new popups. + */ + static void setWindowContainer(WindowContainer *windowContainer); + + /** + * Draws the popup. + */ + void draw(gcn::Graphics *graphics); + + /** + * Sets the size of this popup. + */ + void setContentSize(int width, int height); + + /** + * Sets the location relative to the given widget. + */ + void setLocationRelativeTo(gcn::Widget *widget); + + void mouseMoved(gcn::MouseEvent &event); + + /** + * Sets the minimum width of the popup. + */ + void setMinWidth(int width); + + int getMinWidth() const { return mMinWidth; } + + /** + * Sets the minimum height of the popup. + */ + void setMinHeight(int height); + + int getMinHeight() const { return mMinHeight; } + + /** + * Sets the maximum width of the popup. + */ + void setMaxWidth(int width); + + int getMaxWidth() const { return mMaxWidth; } + + /** + * Sets the minimum height of the popup. + */ + void setMaxHeight(int height); + + int getMaxHeight() const { return mMaxHeight; } + + /** + * Gets the padding of the popup. The padding is the distance between + * the popup border and the content. + * + * @return The padding of the popup. + * @see setPadding + */ + int getPadding() const { return mPadding; } + + void setPadding(int padding) { mPadding = padding; } + + /** + * Sets the name of the popup. This is only useful for debug purposes. + */ + void setPopupName(const std::string &name) + { mPopupName = name; } + + const std::string &getPopupName() const + { return mPopupName; } + + /** + * Schedule this popup for deletion. It will be deleted at the start + * of the next logic update. + */ + void scheduleDelete(); + + // Inherited from BasicContainer + + virtual gcn::Rectangle getChildrenArea(); + + /** + * Sets the location to display the popup. Tries to horizontally center + * the popup and provide a vertical buffer between the given point and + * the popup. Prevents the popup from extending off-screen, if + * possible. + */ + void position(int x, int y); + + void hide(); + + private: + std::string mPopupName; /**< Name of the popup */ + int mMinWidth; /**< Minimum popup width */ + int mMinHeight; /**< Minimum popup height */ + int mMaxWidth; /**< Maximum popup width */ + int mMaxHeight; /**< Maximum popup height */ + int mPadding; /**< Holds the padding of the popup. */ + + Skin *mSkin; /**< Skin in use by this popup */ +}; + +#endif diff --git a/src/gui/widgets/progressbar.cpp b/src/gui/widgets/progressbar.cpp new file mode 100644 index 000000000..5275aacf6 --- /dev/null +++ b/src/gui/widgets/progressbar.cpp @@ -0,0 +1,225 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/progressbar.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "textrenderer.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include <guichan/font.hpp> + +ImageRect ProgressBar::mBorder; +int ProgressBar::mInstances = 0; +float ProgressBar::mAlpha = 1.0; + +ProgressBar::ProgressBar(float progress, + int width, int height, + int color): + gcn::Widget(), + mSmoothProgress(true), + mProgressPalette(color), + mSmoothColorChange(true) +{ + // The progress value is directly set at load time: + if (progress > 1.0f || progress < 0.0f) + progress = 1.0f; + + mProgress = progress; + mProgressToGo = progress; + + mColor = Theme::getProgressColor(color >= 0 ? color : 0, mProgress); + mColorToGo = mColor; + + setSize(width, height); + + if (mInstances == 0) + { + Image *dBorders = Theme::getImageFromTheme("vscroll_grey.png"); + if (dBorders) + { + mBorder.grid[0] = dBorders->getSubImage(0, 0, 4, 4); + mBorder.grid[1] = dBorders->getSubImage(4, 0, 3, 4); + mBorder.grid[2] = dBorders->getSubImage(7, 0, 4, 4); + mBorder.grid[3] = dBorders->getSubImage(0, 4, 4, 10); + mBorder.grid[4] = dBorders->getSubImage(4, 4, 3, 10); + mBorder.grid[5] = dBorders->getSubImage(7, 4, 4, 10); + mBorder.grid[6] = dBorders->getSubImage(0, 15, 4, 4); + mBorder.grid[7] = dBorders->getSubImage(4, 15, 3, 4); + mBorder.grid[8] = dBorders->getSubImage(7, 15, 4, 4); + + for (int i = 0; i < 9; i++) + mBorder.grid[i]->setAlpha(mAlpha); + + dBorders->decRef(); + } + else + { + for (int f = 0; f < 9; f ++) + mBorder.grid[f] = 0; + } + + } + + mInstances++; +} + +ProgressBar::~ProgressBar() +{ + mInstances--; + + if (mInstances == 0) + for_each(mBorder.grid, mBorder.grid + 9, dtor<Image*>()); +} + +void ProgressBar::logic() +{ + if (mSmoothColorChange && mColorToGo != mColor) + { + // Smoothly changing the color for a nicer effect. + if (mColorToGo.r > mColor.r) + mColor.r++; + if (mColorToGo.g > mColor.g) + mColor.g++; + if (mColorToGo.b > mColor.b) + mColor.b++; + if (mColorToGo.r < mColor.r) + mColor.r--; + if (mColorToGo.g < mColor.g) + mColor.g--; + if (mColorToGo.b < mColor.b) + mColor.b--; + } + + if (mSmoothProgress && mProgressToGo != mProgress) + { + // Smoothly showing the progressbar changes. + if (mProgressToGo > mProgress) + mProgress = std::min(1.0f, mProgress + 0.005f); + if (mProgressToGo < mProgress) + mProgress = std::max(0.0f, mProgress - 0.005f); + } +} + +void ProgressBar::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + for (int i = 0; i < 9; i++) + { + if (mBorder.grid[i]) + mBorder.grid[i]->setAlpha(mAlpha); + } + } + +} + +void ProgressBar::draw(gcn::Graphics *graphics) +{ + updateAlpha(); + + mColor.a = static_cast<int>(mAlpha * 255); + + gcn::Rectangle rect = getDimension(); + rect.x = 0; + rect.y = 0; + + render(static_cast<Graphics*>(graphics), rect, mColor, + mProgress, mText); +} + +void ProgressBar::setProgress(float progress) +{ + const float p = std::min(1.0f, std::max(0.0f, progress)); + mProgressToGo = p; + + if (!mSmoothProgress) + mProgress = p; + + if (mProgressPalette >= 0) + mColorToGo = Theme::getProgressColor(mProgressPalette, progress); +} + +void ProgressBar::setProgressPalette(int progressPalette) +{ + int oldPalette = mProgressPalette; + mProgressPalette = progressPalette; + + if (mProgressPalette != oldPalette && mProgressPalette >= 0) + mColorToGo = Theme::getProgressColor(mProgressPalette, mProgressToGo); +} + +void ProgressBar::setColor(const gcn::Color &color) +{ + mColorToGo = color; + + if (!mSmoothColorChange) + mColor = color; +} + +void ProgressBar::render(Graphics *graphics, const gcn::Rectangle &area, + const gcn::Color &color, float progress, + const std::string &text) +{ + gcn::Font *oldFont = graphics->getFont(); + gcn::Color oldColor = graphics->getColor(); + + graphics->drawImageRect(area, mBorder); + + // The bar + if (progress > 0) + { + graphics->setColor(color); + graphics->fillRectangle(gcn::Rectangle(static_cast<int>(area.x + 4), + static_cast<int>(area.y + 4), + static_cast<int>(static_cast<float>(progress) + * static_cast<float>(area.width - 8)), + static_cast<int>(area.height - 8))); + } + + // The label + if (!text.empty()) + { + const int textX = area.x + area.width / 2; + const int textY = area.y + (area.height - boldFont->getHeight()) / 2; + + TextRenderer::renderText(graphics, text, textX, textY, + gcn::Graphics::CENTER, + Theme::getThemeColor(Theme::PROGRESS_BAR), + gui->getFont(), true, false); + } + + graphics->setFont(oldFont); + graphics->setColor(oldColor); +} diff --git a/src/gui/widgets/progressbar.h b/src/gui/widgets/progressbar.h new file mode 100644 index 000000000..56bcb0a0f --- /dev/null +++ b/src/gui/widgets/progressbar.h @@ -0,0 +1,139 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PROGRESSBAR_H +#define PROGRESSBAR_H + +#include <guichan/widget.hpp> + +#include <string> + +class Graphics; +class ImageRect; + +/** + * A progress bar. + * + * \ingroup GUI + */ +class ProgressBar : public gcn::Widget +{ + public: + /** + * Constructor, initializes the progress with the given value. + */ + ProgressBar(float progress = 0.0f, + int width = 40, int height = 7, + int color = -1); + + ~ProgressBar(); + + /** + * Performs progress bar logic (fading colors) + */ + void logic(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the progress bar. + */ + void draw(gcn::Graphics *graphics); + + /** + * Sets the current progress. + */ + void setProgress(float progress); + + /** + * Returns the current progress. + */ + float getProgress() const { return mProgress; } + + /** + * Change the ProgressPalette for this ProgressBar to follow or -1 to + * disable this and manage color manually. + */ + void setProgressPalette(int progressPalette); + + /** + * Change the color of the progress bar. + */ + void setColor(const gcn::Color &color); + + /** + * Returns the color of the progress bar. + */ + const gcn::Color &getColor() const { return mColor; } + + /** + * Sets the text shown on the progress bar. + */ + void setText(const std::string &text) + { mText = text; } + + /** + * Returns the text shown on the progress bar. + */ + const std::string &text() const + { return mText; } + + /** + * Set whether the progress is moved smoothly. + */ + void setSmoothProgress(bool smoothProgress) + { mSmoothProgress = smoothProgress; } + + /** + * Set whether the color changing is made smoothly. + */ + void setSmoothColorChange(bool smoothColorChange) + { mSmoothColorChange = smoothColorChange; } + + /** + * Renders a progressbar with the given properties. + */ + static void render(Graphics *graphics, const gcn::Rectangle &area, + const gcn::Color &color, float progress, + const std::string &text = ""); + + private: + float mProgress, mProgressToGo; + bool mSmoothProgress; + + int mProgressPalette; /** < Entry in ProgressPalette or -1 for none. */ + gcn::Color mColor; + gcn::Color mColorToGo; + bool mSmoothColorChange; + + std::string mText; + + static ImageRect mBorder; + static int mInstances; + static float mAlpha; + + static const gcn::Color TEXT_COLOR; +}; + +#endif diff --git a/src/gui/widgets/progressindicator.cpp b/src/gui/widgets/progressindicator.cpp new file mode 100644 index 000000000..9f1a8f032 --- /dev/null +++ b/src/gui/widgets/progressindicator.cpp @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "progressindicator.h" + +#include "graphics.h" +#include "simpleanimation.h" + +#include "gui/theme.h" + +#include "resources/animation.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include <guichan/widgets/label.hpp> + +ProgressIndicator::ProgressIndicator() +{ + ImageSet *images = Theme::getImageSetFromTheme("progress-indicator.png", + 32, 32); + + Animation *anim = new Animation; + if (images) + { + for (ImageSet::size_type i = 0; i < images->size(); ++i) + anim->addFrame(images->get(i), 100, 0, 0); + + mIndicator = new SimpleAnimation(anim); + + images->decRef(); + } + else + { + mIndicator = 0; + } + + setSize(32, 32); +} + +ProgressIndicator::~ProgressIndicator() +{ + delete mIndicator; + mIndicator = 0; +} + +void ProgressIndicator::logic() +{ + if (mIndicator) + mIndicator->update(10); +} + +void ProgressIndicator::draw(gcn::Graphics *graphics) +{ + if (mIndicator) + { + // Draw the indicator centered on the widget + const int x = (getWidth() - 32) / 2; + const int y = (getHeight() - 32) / 2; + mIndicator->draw(static_cast<Graphics*>(graphics), x, y); + } +} diff --git a/src/gui/widgets/progressindicator.h b/src/gui/widgets/progressindicator.h new file mode 100644 index 000000000..b990d8bef --- /dev/null +++ b/src/gui/widgets/progressindicator.h @@ -0,0 +1,45 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PROGRESSINDICATOR_H +#define PROGRESSINDICATOR_H + +#include <guichan/widget.hpp> + +class SimpleAnimation; + +/** + * A widget that indicates progress. Suitable to use instead of a progress bar + * in cases where it is unknown how long something is going to take. + */ +class ProgressIndicator : public gcn::Widget +{ +public: + ProgressIndicator(); + ~ProgressIndicator(); + + void logic(); + void draw(gcn::Graphics *graphics); + +private: + SimpleAnimation *mIndicator; +}; + +#endif // PROGRESSINDICATOR_H diff --git a/src/gui/widgets/radiobutton.cpp b/src/gui/widgets/radiobutton.cpp new file mode 100644 index 000000000..c9738e3cd --- /dev/null +++ b/src/gui/widgets/radiobutton.cpp @@ -0,0 +1,163 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/radiobutton.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +int RadioButton::instances = 0; +float RadioButton::mAlpha = 1.0; +Image *RadioButton::radioNormal; +Image *RadioButton::radioChecked; +Image *RadioButton::radioDisabled; +Image *RadioButton::radioDisabledChecked; +Image *RadioButton::radioNormalHi; +Image *RadioButton::radioCheckedHi; + +RadioButton::RadioButton(const std::string &caption, const std::string &group, + bool marked): + gcn::RadioButton(caption, group, marked), + mHasMouse(false) +{ + if (instances == 0) + { + radioNormal = Theme::getImageFromTheme("radioout.png"); + radioChecked = Theme::getImageFromTheme("radioin.png"); + radioDisabled = Theme::getImageFromTheme("radioout.png"); + radioDisabledChecked = Theme::getImageFromTheme("radioin.png"); + radioNormalHi = Theme::getImageFromTheme("radioout_highlight.png"); + radioCheckedHi = Theme::getImageFromTheme("radioin_highlight.png"); + if (radioNormal) + radioNormal->setAlpha(mAlpha); + if (radioChecked) + radioChecked->setAlpha(mAlpha); + if (radioDisabled) + radioDisabled->setAlpha(mAlpha); + if (radioDisabledChecked) + radioDisabledChecked->setAlpha(mAlpha); + if (radioNormalHi) + radioNormalHi->setAlpha(mAlpha); + if (radioCheckedHi) + radioCheckedHi->setAlpha(mAlpha); + } + + instances++; +} + +RadioButton::~RadioButton() +{ + instances--; + + if (instances == 0) + { + if (radioNormal) + radioNormal->decRef(); + if (radioChecked) + radioChecked->decRef(); + if (radioDisabled) + radioDisabled->decRef(); + if (radioDisabledChecked) + radioDisabledChecked->decRef(); + if (radioNormalHi) + radioNormalHi->decRef(); + if (radioCheckedHi) + radioCheckedHi->decRef(); + } +} + +void RadioButton::drawBox(gcn::Graphics* graphics) +{ + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + if (radioNormal) + radioNormal->setAlpha(mAlpha); + if (radioChecked) + radioChecked->setAlpha(mAlpha); + if (radioDisabled) + radioDisabled->setAlpha(mAlpha); + if (radioDisabledChecked) + radioDisabledChecked->setAlpha(mAlpha); + if (radioNormalHi) + radioNormalHi->setAlpha(mAlpha); + if (radioCheckedHi) + radioCheckedHi->setAlpha(mAlpha); + } + + Image *box = NULL; + + if (isEnabled()) + { + if (isSelected()) + if (mHasMouse) + box = radioCheckedHi; + else + box = radioChecked; + else + if (mHasMouse) + box = radioNormalHi; + else + box = radioNormal; + } + else + { + if (isSelected()) + box = radioDisabledChecked; + else + box = radioDisabled; + } + + if (box) + static_cast<Graphics*>(graphics)->drawImage(box, 2, 2); +} + +void RadioButton::draw(gcn::Graphics* graphics) +{ + graphics->pushClipArea(gcn::Rectangle(1, 1, getWidth() - 1, + getHeight() - 1)); + + drawBox(graphics); + + graphics->popClipArea(); + + graphics->setFont(getFont()); + graphics->setColor(getForegroundColor()); + + int h = getHeight() + getHeight() / 2; + graphics->drawText(getCaption(), h - 2, 0); +} + +void RadioButton::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void RadioButton::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} + diff --git a/src/gui/widgets/radiobutton.h b/src/gui/widgets/radiobutton.h new file mode 100644 index 000000000..b7383aa7c --- /dev/null +++ b/src/gui/widgets/radiobutton.h @@ -0,0 +1,85 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RADIOBUTTON_H +#define RADIOBUTTON_H + +#include <guichan/widgets/radiobutton.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Guichan based RadioButton with custom look + */ +class RadioButton : public gcn::RadioButton +{ + public: + /** + * Constructor. + */ + RadioButton(const std::string &caption, const std::string &group, + bool marked = false); + + /** + * Destructor. + */ + ~RadioButton(); + + /** + * Draws the radiobutton, not the caption. + */ + void drawBox(gcn::Graphics* graphics); + + /** + * Implementation of the draw methods. + * Thus, avoiding the rhomb around the radio button. + */ + void draw(gcn::Graphics* graphics); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + private: + static int instances; + static float mAlpha; + bool mHasMouse; + static Image *radioNormal; + static Image *radioChecked; + static Image *radioDisabled; + static Image *radioDisabledChecked; + static Image *radioNormalHi; + static Image *radioCheckedHi; +}; + +#endif // RADIOBUTTON_H diff --git a/src/gui/widgets/resizegrip.cpp b/src/gui/widgets/resizegrip.cpp new file mode 100644 index 000000000..e8ccd0740 --- /dev/null +++ b/src/gui/widgets/resizegrip.cpp @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/resizegrip.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +#include <guichan/graphics.hpp> + +Image *ResizeGrip::gripImage = 0; +int ResizeGrip::mInstances = 0; +float ResizeGrip::mAlpha = 1.0; + +ResizeGrip::ResizeGrip(const std::string &image) +{ + if (mInstances == 0) + { + // Load the grip image + gripImage = Theme::getImageFromTheme(image); + if (gripImage) + gripImage->setAlpha(mAlpha); + } + + mInstances++; + + if (gripImage) + { + setWidth(gripImage->getWidth() + 2); + setHeight(gripImage->getHeight() + 2); + } + else + { + setWidth(2); + setHeight(2); + } +} + +ResizeGrip::~ResizeGrip() +{ + mInstances--; + + if (mInstances == 0 && gripImage) + gripImage->decRef(); +} + +void ResizeGrip::draw(gcn::Graphics *graphics) +{ + if (!gripImage) + return; + + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + gripImage->setAlpha(mAlpha); + } + + static_cast<Graphics*>(graphics)->drawImage(gripImage, 0, 0); +} diff --git a/src/gui/widgets/resizegrip.h b/src/gui/widgets/resizegrip.h new file mode 100644 index 000000000..5ef93f297 --- /dev/null +++ b/src/gui/widgets/resizegrip.h @@ -0,0 +1,60 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RESIZEGRIP_H +#define RESIZEGRIP_H + +#include <guichan/widget.hpp> + +class Image; + +/** + * Resize grip. The resize grip is part of a resizable Window. It relies on the + * fact that uncaught mouse events are automatically routed to the parent + * window. + * + * \ingroup GUI + */ +class ResizeGrip : public gcn::Widget +{ + public: + /** + * Constructor. + */ + ResizeGrip(const std::string &image = "resize.png"); + + /** + * Destructor. + */ + ~ResizeGrip(); + + /** + * Draws the resize grip. + */ + void draw(gcn::Graphics *graphics); + + private: + static Image *gripImage; /**< Resize grip image */ + static int mInstances; /**< Number of resize grip instances */ + static float mAlpha; +}; + +#endif diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp new file mode 100644 index 000000000..187794b1d --- /dev/null +++ b/src/gui/widgets/scrollarea.cpp @@ -0,0 +1,445 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/scrollarea.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +int ScrollArea::instances = 0; +float ScrollArea::mAlpha = 1.0; +ImageRect ScrollArea::background; +ImageRect ScrollArea::vMarker; +ImageRect ScrollArea::vMarkerHi; +Image *ScrollArea::buttons[4][2]; + +ScrollArea::ScrollArea(): + gcn::ScrollArea(), + mX(0), + mY(0), + mHasMouse(false), + mOpaque(true) +{ + addWidgetListener(this); + init(); +} + +ScrollArea::ScrollArea(gcn::Widget *widget): + gcn::ScrollArea(widget), + mX(0), + mY(0), + mHasMouse(false), + mOpaque(true) +{ + init(); +} + +ScrollArea::~ScrollArea() +{ + // Garbage collection + delete getContent(); + + instances--; + + if (instances == 0) + { + for_each(background.grid, background.grid + 9, dtor<Image*>()); + for_each(vMarker.grid, vMarker.grid + 9, dtor<Image*>()); + for_each(vMarkerHi.grid, vMarkerHi.grid + 9, dtor<Image*>()); + + if (buttons[UP][0]) + buttons[UP][0]->decRef(); + if (buttons[UP][1]) + buttons[UP][1]->decRef(); + if (buttons[DOWN][0]) + buttons[DOWN][0]->decRef(); + if (buttons[DOWN][1]) + buttons[DOWN][1]->decRef(); + if (buttons[LEFT][0]) + buttons[LEFT][0]->decRef(); + if (buttons[LEFT][1]) + buttons[LEFT][1]->decRef(); + if (buttons[RIGHT][0]) + buttons[RIGHT][0]->decRef(); + if (buttons[RIGHT][1]) + buttons[RIGHT][1]->decRef(); + } +} + +void ScrollArea::init() +{ + // Draw background by default + setOpaque(true); + + setUpButtonScrollAmount(2); + setDownButtonScrollAmount(2); + setLeftButtonScrollAmount(2); + setRightButtonScrollAmount(2); + + if (instances == 0) + { + // Load the background skin + Image *textbox = Theme::getImageFromTheme("deepbox.png"); + const int bggridx[4] = {0, 3, 28, 31}; + const int bggridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (textbox) + { + background.grid[a] = textbox->getSubImage( + bggridx[x], bggridy[y], + bggridx[x + 1] - bggridx[x] + 1, + bggridy[y + 1] - bggridy[y] + 1); + background.grid[a]->setAlpha( + Client::getGuiAlpha()); + } + else + { + background.grid[a] = 0; + } + a++; + } + } + + textbox->decRef(); + + // Load vertical scrollbar skin + Image *vscroll = Theme::getImageFromTheme("vscroll_grey.png"); + Image *vscrollHi = Theme::getImageFromTheme("vscroll_highlight.png"); + + int vsgridx[4] = {0, 4, 7, 11}; + int vsgridy[4] = {0, 4, 15, 19}; + a = 0; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (vscroll) + { + vMarker.grid[a] = vscroll->getSubImage( + vsgridx[x], vsgridy[y], + vsgridx[x + 1] - vsgridx[x], + vsgridy[y + 1] - vsgridy[y]); + vMarker.grid[a]->setAlpha( + Client::getGuiAlpha()); + } + else + { + vMarker.grid[a] = 0; + } + if (vscrollHi) + { + vMarkerHi.grid[a] = vscrollHi->getSubImage( + vsgridx[x], vsgridy[y], + vsgridx[x + 1] - vsgridx[x], + vsgridy[y + 1] - vsgridy[y]); + vMarkerHi.grid[a]->setAlpha( + Client::getGuiAlpha()); + } + else + { + vMarkerHi.grid[a] = 0; + } + a++; + } + } + + if (vscroll) + vscroll->decRef(); + if (vscrollHi) + vscrollHi->decRef(); + + buttons[UP][0] = + Theme::getImageFromTheme("vscroll_up_default.png"); + buttons[DOWN][0] = + Theme::getImageFromTheme("vscroll_down_default.png"); + buttons[LEFT][0] = + Theme::getImageFromTheme("hscroll_left_default.png"); + buttons[RIGHT][0] = + Theme::getImageFromTheme("hscroll_right_default.png"); + buttons[UP][1] = + Theme::getImageFromTheme("vscroll_up_pressed.png"); + buttons[DOWN][1] = + Theme::getImageFromTheme("vscroll_down_pressed.png"); + buttons[LEFT][1] = + Theme::getImageFromTheme("hscroll_left_pressed.png"); + buttons[RIGHT][1] = + Theme::getImageFromTheme("hscroll_right_pressed.png"); + } + + instances++; +} + +void ScrollArea::logic() +{ + if (!isVisible()) + return; + + gcn::ScrollArea::logic(); + gcn::Widget *content = getContent(); + + // When no scrollbar in a certain direction, adapt content size to match + // the content dimension exactly. + if (content) + { + if (getHorizontalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER) + { + content->setWidth(getChildrenArea().width - + 2 * content->getFrameSize()); + } + if (getVerticalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER) + { + content->setHeight(getChildrenArea().height - + 2 * content->getFrameSize()); + } + } + + if (mUpButtonPressed) + { + setVerticalScrollAmount(getVerticalScrollAmount() - + mUpButtonScrollAmount); + } + else if (mDownButtonPressed) + { + setVerticalScrollAmount(getVerticalScrollAmount() + + mDownButtonScrollAmount); + } + else if (mLeftButtonPressed) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() - + mLeftButtonScrollAmount); + } + else if (mRightButtonPressed) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + + mRightButtonScrollAmount); + } +} + +void ScrollArea::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (alpha != mAlpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + if (background.grid[a]) + background.grid[a]->setAlpha(mAlpha); + if (vMarker.grid[a]) + vMarker.grid[a]->setAlpha(mAlpha); + if (vMarkerHi.grid[a]) + vMarkerHi.grid[a]->setAlpha(mAlpha); + } + } +} + +void ScrollArea::draw(gcn::Graphics *graphics) +{ + if (mVBarVisible) + { + drawUpButton(graphics); + drawDownButton(graphics); + drawVBar(graphics); + drawVMarker(graphics); + } + + if (mHBarVisible) + { + drawLeftButton(graphics); + drawRightButton(graphics); + drawHBar(graphics); + drawHMarker(graphics); + } + + if (mHBarVisible && mVBarVisible) + { + graphics->setColor(getBaseColor()); + graphics->fillRectangle(gcn::Rectangle(getWidth() - mScrollbarWidth, + getHeight() - mScrollbarWidth, + mScrollbarWidth, + mScrollbarWidth)); + } + + updateAlpha(); + + drawChildren(graphics); +} + +void ScrollArea::drawFrame(gcn::Graphics *graphics) +{ + if (mOpaque) + { + const int bs = getFrameSize(); + const int w = getWidth() + bs * 2; + const int h = getHeight() + bs * 2; + + static_cast<Graphics*>(graphics)-> + drawImageRect(0, 0, w, h, background); + } +} + +void ScrollArea::setOpaque(bool opaque) +{ + mOpaque = opaque; + setFrameSize(mOpaque ? 2 : 0); +} + +void ScrollArea::drawButton(gcn::Graphics *graphics, BUTTON_DIR dir) +{ + int state = 0; + gcn::Rectangle dim; + + switch (dir) + { + case UP: + state = mUpButtonPressed ? 1 : 0; + dim = getUpButtonDimension(); + break; + case DOWN: + state = mDownButtonPressed ? 1 : 0; + dim = getDownButtonDimension(); + break; + case LEFT: + state = mLeftButtonPressed ? 1 : 0; + dim = getLeftButtonDimension(); + break; + case RIGHT: + state = mRightButtonPressed ? 1 : 0; + dim = getRightButtonDimension(); + break; + default: + logger->log("ScrollArea::drawButton unknown dir: " + + toString(static_cast<unsigned>(dir))); + break; + } + + if (buttons[dir][state]) + { + static_cast<Graphics*>(graphics)-> + drawImage(buttons[dir][state], dim.x, dim.y); + } +} + +void ScrollArea::drawUpButton(gcn::Graphics *graphics) +{ + drawButton(graphics, UP); +} + +void ScrollArea::drawDownButton(gcn::Graphics *graphics) +{ + drawButton(graphics, DOWN); +} + +void ScrollArea::drawLeftButton(gcn::Graphics *graphics) +{ + drawButton(graphics, LEFT); +} + +void ScrollArea::drawRightButton(gcn::Graphics *graphics) +{ + drawButton(graphics, RIGHT); +} + +void ScrollArea::drawVBar(gcn::Graphics *graphics) +{ + const gcn::Rectangle dim = getVerticalBarDimension(); + graphics->setColor(gcn::Color(0, 0, 0, 32)); + graphics->fillRectangle(dim); + graphics->setColor(gcn::Color(255, 255, 255)); +} + +void ScrollArea::drawHBar(gcn::Graphics *graphics) +{ + const gcn::Rectangle dim = getHorizontalBarDimension(); + graphics->setColor(gcn::Color(0, 0, 0, 32)); + graphics->fillRectangle(dim); + graphics->setColor(gcn::Color(255, 255, 255)); +} + +void ScrollArea::drawVMarker(gcn::Graphics *graphics) +{ + gcn::Rectangle dim = getVerticalMarkerDimension(); + + if ((mHasMouse) && (mX > (getWidth() - getScrollbarWidth()))) + { + static_cast<Graphics*>(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi); + } + else + { + static_cast<Graphics*>(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarker); + } +} + +void ScrollArea::drawHMarker(gcn::Graphics *graphics) +{ + gcn::Rectangle dim = getHorizontalMarkerDimension(); + + if ((mHasMouse) && (mY > (getHeight() - getScrollbarWidth()))) + { + static_cast<Graphics*>(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi); + } + else + { + static_cast<Graphics*>(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarker); + } +} + +void ScrollArea::mouseMoved(gcn::MouseEvent& event) +{ + mX = event.getX(); + mY = event.getY(); +} + +void ScrollArea::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void ScrollArea::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} + +void ScrollArea::widgetResized(const gcn::Event &event _UNUSED_) +{ + getContent()->setSize(getWidth() - 2 * getFrameSize(), + getHeight() - 2 * getFrameSize()); +} diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h new file mode 100644 index 000000000..4f6a07f82 --- /dev/null +++ b/src/gui/widgets/scrollarea.h @@ -0,0 +1,151 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SCROLLAREA_H +#define SCROLLAREA_H + +#include <guichan/widgets/scrollarea.hpp> +#include <guichan/widgetlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class ImageRect; + +/** + * A scroll area. + * + * Contrary to Guichan's scroll area, this scroll area takes ownership over its + * content. However, it won't delete a previously set content widget when + * setContent is called! + * + * \ingroup GUI + */ +class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener +{ + public: + /** + * Constructor that takes no content. Needed for use with the DropDown + * class. + */ + ScrollArea(); + + /** + * Constructor. + * + * @param content the initial content to show in the scroll area + */ + ScrollArea(gcn::Widget *content); + + /** + * Destructor. Also deletes the content. + */ + ~ScrollArea(); + + /** + * Logic function optionally adapts width or height of contents. This + * depends on the scrollbar settings. + */ + void logic(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the scroll area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Draws the background and border of the scroll area. + */ + void drawFrame(gcn::Graphics *graphics); + + /** + * Sets whether the widget should draw its background or not. + */ + void setOpaque(bool opaque); + + /** + * Returns whether the widget draws its background or not. + */ + bool isOpaque() const { return mOpaque; } + + /** + * Called when the mouse moves in the widget area. + */ + void mouseMoved(gcn::MouseEvent& event); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + void widgetResized(const gcn::Event &event); + + protected: + enum BUTTON_DIR + { + UP = 0, + DOWN, + LEFT, + RIGHT + }; + + /** + * Initializes the scroll area. + */ + void init(); + + void drawButton(gcn::Graphics *graphics, BUTTON_DIR dir); + void drawUpButton(gcn::Graphics *graphics); + void drawDownButton(gcn::Graphics *graphics); + void drawLeftButton(gcn::Graphics *graphics); + void drawRightButton(gcn::Graphics *graphics); + void drawVBar(gcn::Graphics *graphics); + void drawHBar(gcn::Graphics *graphics); + void drawVMarker(gcn::Graphics *graphics); + void drawHMarker(gcn::Graphics *graphics); + + static int instances; + static float mAlpha; + static ImageRect background; + static ImageRect vMarker; + static ImageRect vMarkerHi; + static Image *buttons[4][2]; + + int mX, mY; + bool mHasMouse; + bool mOpaque; +}; + +#endif diff --git a/src/gui/widgets/setuptab.cpp b/src/gui/widgets/setuptab.cpp new file mode 100644 index 000000000..3c10e6d93 --- /dev/null +++ b/src/gui/widgets/setuptab.cpp @@ -0,0 +1,31 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/setuptab.h" + +SetupTab::SetupTab() +{ + setOpaque(false); +} + +void SetupTab::externalUpdated() +{ +}
\ No newline at end of file diff --git a/src/gui/widgets/setuptab.h b/src/gui/widgets/setuptab.h new file mode 100644 index 000000000..2d8742123 --- /dev/null +++ b/src/gui/widgets/setuptab.h @@ -0,0 +1,64 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_SETUPTAB_H +#define GUI_SETUPTAB_H + +#include "gui/widgets/container.h" + +#include <string> + +/** + * A container for the contents of a tab in the setup window. + */ +class SetupTab : public Container +{ +public: + SetupTab(); + + const std::string &getName() const + { return mName; } + + /** + * Called when the Apply button is pressed in the setup window. + */ + virtual void apply() = 0; + + /** + * Called when the Cancel button is pressed in the setup window. + */ + virtual void cancel() = 0; + + virtual void externalUpdated(); + +protected: + /** + * Sets the name displayed on the tab. Should be set in the + * constructor of a subclass. + */ + void setName(const std::string &name) + { mName = name; } + +private: + std::string mName; +}; + +#endif diff --git a/src/gui/widgets/shopitems.cpp b/src/gui/widgets/shopitems.cpp new file mode 100644 index 000000000..0aff3d5b9 --- /dev/null +++ b/src/gui/widgets/shopitems.cpp @@ -0,0 +1,118 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/shopitems.h" + +#include "shopitem.h" + +#include "utils/dtor.h" + +ShopItems::ShopItems(bool mergeDuplicates) : + mMergeDuplicates(mergeDuplicates) +{ +} + +ShopItems::~ShopItems() +{ + clear(); +} + +int ShopItems::getNumberOfElements() +{ + return static_cast<int>(mShopItems.size()); +} + +std::string ShopItems::getElementAt(int i) +{ + if (i < 0 || (unsigned)i >= mShopItems.size() || !mShopItems.at(i)) + return ""; + + return mShopItems.at(i)->getDisplayName(); +} + +void ShopItems::addItem(int id, int amount, int price) +{ + mShopItems.push_back(new ShopItem(-1, id, amount, price)); +} + +void ShopItems::addItemNoDup(int id, int amount, int price) +{ + ShopItem *item = findItem(id); + if (!item) + mShopItems.push_back(new ShopItem(-1, id, amount, price)); +} + +void ShopItems::addItem(int inventoryIndex, int id, int quantity, int price) +{ + ShopItem *item = 0; + if (mMergeDuplicates) + item = findItem(id); + + if (item) + { + item->addDuplicate (inventoryIndex, quantity); + } + else + { + item = new ShopItem(inventoryIndex, id, quantity, price); + mShopItems.push_back(item); + } +} + +ShopItem *ShopItems::at(unsigned int i) const +{ + if (i >= mShopItems.size()) + return 0; + + return mShopItems.at(i); +} + +void ShopItems::erase(unsigned int i) +{ + if (i >= mShopItems.size()) + return; + + mShopItems.erase(mShopItems.begin() + i); +} + +void ShopItems::clear() +{ + delete_all(mShopItems); + mShopItems.clear(); +} + +ShopItem *ShopItems::findItem(int id) +{ + ShopItem *item; + + std::vector<ShopItem*>::iterator it = mShopItems.begin(); + std::vector<ShopItem*>::iterator e = mShopItems.end(); + while (it != e) + { + item = *(it); + if (item->getId() == id) + return item; + + ++it; + } + + return 0; +} diff --git a/src/gui/widgets/shopitems.h b/src/gui/widgets/shopitems.h new file mode 100644 index 000000000..ba325bfa5 --- /dev/null +++ b/src/gui/widgets/shopitems.h @@ -0,0 +1,120 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SHOPITEMS_H +#define SHOPITEMS_H + +#include <guichan/listmodel.hpp> + +#include <string> +#include <vector> + +class ShopItem; + +/** + * This class handles the list of items available in a shop. + * + * The addItem routine can automatically check, if an item already exists and + * only adds duplicates to the old item, if one is found. The original + * distribution of the duplicates can be retrieved from the item. + * + * This functionality can be enabled in the constructor. + */ +class ShopItems : public gcn::ListModel +{ + public: + /** + * Constructor. + * + * @param mergeDuplicates lets the Shop look for duplicate entries and + * merges them to one item. + */ + ShopItems(bool mergeDuplicates = false); + + ~ShopItems(); + + /** + * Adds an item to the list. + */ + void addItem(int id, int amount, int price); + + /** + * Adds an item to the list (used by sell dialog). Looks for + * duplicate entries, if mergeDuplicates was turned on. + * + * @param inventoryIndex the inventory index of the item + * @param id the id of the item + * @param quantity number of available copies of the item + * @param price price of the item + */ + void addItem(int inventoryIndex, int id, int amount, int price); + + void addItemNoDup(int id, int amount, int price); + + /** + * Returns the number of items in the shop. + */ + int getNumberOfElements(); + + /** + * Returns the name of item number i in the shop. + * + * @param i the index to retrieve + */ + std::string getElementAt(int i); + + /** + * Returns the item number i in the shop. + */ + ShopItem *at(unsigned int i) const; + + /** + * Removes an element from the shop. + * + * @param i index to remove + */ + void erase(unsigned int i); + + /** + * Clears the list of items in the shop. + */ + void clear(); + + std::vector<ShopItem*> &items() + { return mShopItems; } + + private: + /** + * Searches the current items in the shop for the specified + * id and returns the item if found, or 0 else. + * + * @return the item found or 0 + */ + ShopItem *findItem(int id); + + /** The list of items in the shop. */ + std::vector<ShopItem*> mShopItems; + + /** Look for duplicate entries on addition. */ + bool mMergeDuplicates; +}; + +#endif // SHOPITEMS_H diff --git a/src/gui/widgets/shoplistbox.cpp b/src/gui/widgets/shoplistbox.cpp new file mode 100644 index 000000000..a0577db03 --- /dev/null +++ b/src/gui/widgets/shoplistbox.cpp @@ -0,0 +1,185 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/shoplistbox.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "shopitem.h" + +#include "gui/itempopup.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/shopitems.h" + +#include "resources/image.h" + +#include <guichan/font.hpp> +#include <guichan/listmodel.hpp> + +const int ITEM_ICON_SIZE = 32; + +float ShopListBox::mAlpha = 1.0; + +ShopListBox::ShopListBox(gcn::ListModel *listModel): + ListBox(listModel), + mPlayerMoney(0), + mShopItems(0) +{ + mRowHeight = getFont()->getHeight(); + mPriceCheck = true; + + mItemPopup = new ItemPopup; +} + +ShopListBox::ShopListBox(gcn::ListModel *listModel, ShopItems *shopListModel): + ListBox(listModel), + mPlayerMoney(0), + mShopItems(shopListModel) +{ + mRowHeight = std::max(getFont()->getHeight(), ITEM_ICON_SIZE); + mPriceCheck = true; + + mItemPopup = new ItemPopup; +} + +void ShopListBox::setPlayersMoney(int money) +{ + mPlayerMoney = money; +} + +void ShopListBox::draw(gcn::Graphics *gcnGraphics) +{ + if (!mListModel || !mShopItems) + return; + + if (Client::getGuiAlpha() != mAlpha) + mAlpha = Client::getGuiAlpha(); + + int alpha = static_cast<int>(mAlpha * 255.0f); + const gcn::Color* highlightColor = + &Theme::getThemeColor(Theme::HIGHLIGHT, alpha); + + Graphics *graphics = static_cast<Graphics*>(gcnGraphics); + + graphics->setFont(getFont()); + + // Draw the list elements + for (int i = 0, y = 0; + i < mListModel->getNumberOfElements(); + ++i, y += mRowHeight) + { + gcn::Color temp; + const gcn::Color* backgroundColor = + &Theme::getThemeColor(Theme::BACKGROUND, alpha); + + if (mShopItems && mShopItems->at(i) && + mPlayerMoney < mShopItems->at(i)->getPrice() && mPriceCheck) + { + if (i != mSelected) + { + backgroundColor = &Theme::getThemeColor(Theme::SHOP_WARNING, + alpha); + } + else + { + temp = Theme::getThemeColor(Theme::SHOP_WARNING, alpha); + temp.r = (temp.r + highlightColor->r) / 2; + temp.g = (temp.g + highlightColor->g) / 2; + temp.b = (temp.g + highlightColor->b) / 2; + backgroundColor = &temp; + } + } + else if (i == mSelected) + { + backgroundColor = highlightColor; + } + + graphics->setColor(*backgroundColor); + graphics->fillRectangle(gcn::Rectangle(0, y, getWidth(), mRowHeight)); + + if (mShopItems) + { + Image *icon = mShopItems->at(i)->getImage(); + if (icon) + { + icon->setAlpha(1.0f); + graphics->drawImage(icon, 1, y); + } + } + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->drawText(mListModel->getElementAt(i), ITEM_ICON_SIZE + 5, + y + (ITEM_ICON_SIZE - getFont()->getHeight()) / 2); + } +} + +void ShopListBox::adjustSize() +{ + if (mListModel) + setHeight(mRowHeight * mListModel->getNumberOfElements()); +} + +void ShopListBox::setPriceCheck(bool check) +{ + mPriceCheck = check; +} + +void ShopListBox::mouseMoved(gcn::MouseEvent &event) +{ + if (!mItemPopup) + return; + + if (!mShopItems) + { + mItemPopup->hide(); + return; + } + + int index = event.getY() / mRowHeight; + + if (index < 0 || index >= mShopItems->getNumberOfElements()) + { + mItemPopup->hide(); + } + else + { + Item *item = mShopItems->at(index); + if (item) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } + } +} + +void ShopListBox::mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_) +{ + if (!mItemPopup) + return; + + mItemPopup->hide(); +}
\ No newline at end of file diff --git a/src/gui/widgets/shoplistbox.h b/src/gui/widgets/shoplistbox.h new file mode 100644 index 000000000..ab77c5969 --- /dev/null +++ b/src/gui/widgets/shoplistbox.h @@ -0,0 +1,104 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SHOPLISTBOX_H +#define SHOPLISTBOX_H + +#include "gui/widgets/listbox.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ShopItems; +class ItemPopup; + +/** + * A list box, meant to be used inside a scroll area. Same as the Guichan list + * box except this one doesn't have a background, instead completely relying + * on the scroll area. It also adds selection listener functionality. + * + * \ingroup GUI + */ +class ShopListBox : public ListBox +{ + public: + /** + * Constructor. + */ + ShopListBox(gcn::ListModel *listModel); + + /** + * Constructor with shopitems + */ + ShopListBox(gcn::ListModel *listModel, ShopItems *shopListModel); + + /** + * Draws the list box. + */ + void draw(gcn::Graphics *graphics); + + /** + * Returns the height of a row. + */ + unsigned int getRowHeight() const { return mRowHeight; } + + /** + * gives information about the current player's money + */ + void setPlayersMoney(int money); + + /** + * Adjust List draw size + */ + void adjustSize(); + + /** + * Set on/off the disabling of too expensive items. + * (Good for selling mode.) + */ + void setPriceCheck(bool check); + + void mouseMoved(gcn::MouseEvent &event); + + void mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_); + + private: + int mPlayerMoney; + + /** + * Keeps another pointer to the same listModel, permitting to + * use the ShopItems specific functions. + */ + ShopItems *mShopItems; + + ItemPopup *mItemPopup; + + unsigned int mRowHeight; /**< Row Height */ + + static float mAlpha; + + bool mPriceCheck; +}; + +#endif // SHOPLISTBOX_H diff --git a/src/gui/widgets/shortcutcontainer.cpp b/src/gui/widgets/shortcutcontainer.cpp new file mode 100644 index 000000000..167296410 --- /dev/null +++ b/src/gui/widgets/shortcutcontainer.cpp @@ -0,0 +1,67 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/shortcutcontainer.h" + +#include "configuration.h" + +#include "resources/image.h" + +#include "utils/stringutils.h" + +float ShortcutContainer::mAlpha = 1.0; + +ShortcutContainer::ShortcutContainer(): + mGridWidth(1), + mGridHeight(1) +{ +} + +void ShortcutContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + mGridWidth = getWidth() / mBoxWidth; + + if (mGridWidth < 1) + mGridWidth = 1; + + mGridHeight = mMaxItems / mGridWidth; + + if (mMaxItems % mGridWidth != 0 || mGridHeight < 1) + ++mGridHeight; + + setHeight(mGridHeight * mBoxHeight); +} + +int ShortcutContainer::getIndexFromGrid(int pointX, int pointY) const +{ + const gcn::Rectangle tRect = gcn::Rectangle(0, 0, mGridWidth * mBoxWidth, + mGridHeight * mBoxHeight); + + int index = ((pointY / mBoxHeight) * mGridWidth) + pointX / mBoxWidth; + + if (!tRect.isPointInRect(pointX, pointY) || + index >= (int)mMaxItems || index < 0) + { + index = -1; + } + + return index; +} diff --git a/src/gui/widgets/shortcutcontainer.h b/src/gui/widgets/shortcutcontainer.h new file mode 100644 index 000000000..85d08d0b4 --- /dev/null +++ b/src/gui/widgets/shortcutcontainer.h @@ -0,0 +1,115 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SHORTCUTCONTAINER_H +#define SHORTCUTCONTAINER_H + +#include <guichan/mouselistener.hpp> +#include <guichan/widget.hpp> +#include <guichan/widgetlistener.hpp> + +#include "gui/widgets/tab.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * A generic shortcut container. + * + * \ingroup GUI + */ +class ShortcutContainer : public gcn::Widget, + public gcn::WidgetListener, + public gcn::MouseListener +{ + public: + /** + * Constructor. Initializes the shortcut container. + */ + ShortcutContainer(); + + /** + * Destructor. + */ + ~ShortcutContainer() {} + + /** + * Draws the shortcuts + */ + virtual void draw(gcn::Graphics *graphics) = 0; + + /** + * Invoked when a widget changes its size. This is used to determine + * the new height of the container. + */ + virtual void widgetResized(const gcn::Event &event); + + /** + * Handles mouse when dragged. + */ + virtual void mouseDragged(gcn::MouseEvent &event) = 0; + + /** + * Handles mouse when pressed. + */ + virtual void mousePressed(gcn::MouseEvent &event) = 0; + + /** + * Handles mouse release. + */ + virtual void mouseReleased(gcn::MouseEvent &event) = 0; + + int getMaxItems() const + { return mMaxItems; } + + int getBoxWidth() const + { return mBoxWidth; } + + int getBoxHeight() const + { return mBoxHeight; } + + protected: + /** + * Gets the index from the grid provided the point is in an item box. + * + * @param pointX X coordinate of the point. + * @param pointY Y coordinate of the point. + * @return index on success, -1 on failure. + */ + int getIndexFromGrid(int pointX, int pointY) const; + + Image *mBackgroundImg; + + static float mAlpha; + + unsigned mMaxItems; + int mBoxWidth; + int mBoxHeight; + int mCursorPosX, mCursorPosY; + int mGridWidth, mGridHeight; +}; + +#endif diff --git a/src/gui/widgets/slider.cpp b/src/gui/widgets/slider.cpp new file mode 100644 index 000000000..9513d5308 --- /dev/null +++ b/src/gui/widgets/slider.cpp @@ -0,0 +1,298 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/slider.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +Image *Slider::hStart, *Slider::hMid, *Slider::hEnd, *Slider::hGrip; +Image *Slider::vStart, *Slider::vMid, *Slider::vEnd, *Slider::vGrip; +Image *Slider::hStartHi, *Slider::hMidHi, *Slider::hEndHi, *Slider::hGripHi; +Image *Slider::vStartHi, *Slider::vMidHi, *Slider::vEndHi, *Slider::vGripHi; +float Slider::mAlpha = 1.0; +int Slider::mInstances = 0; + +Slider::Slider(double scaleEnd): + gcn::Slider(scaleEnd), + mHasMouse(false) +{ + init(); +} + +Slider::Slider(double scaleStart, double scaleEnd): + gcn::Slider(scaleStart, scaleEnd), + mHasMouse(false) +{ + init(); +} + +Slider::~Slider() +{ + mInstances--; + + if (mInstances == 0) + { + delete hStart; + delete hMid; + delete hEnd; + delete hGrip; + delete vStart; + delete vMid; + delete vEnd; + delete vGrip; + delete hStartHi; + delete hMidHi; + delete hEndHi; + delete hGripHi; + delete vStartHi; + delete vMidHi; + delete vEndHi; + delete vGripHi; + } +} + +void Slider::init() +{ + setFrameSize(0); + + // Load resources + if (mInstances == 0) + { + int x, y, w, h, o1, o2; + + Image *slider = Theme::getImageFromTheme("slider.png"); + Image *sliderHi = Theme::getImageFromTheme("slider_hilight.png"); + + x = 0; y = 0; + w = 15; h = 6; + o1 = 4; o2 = 11; + if (slider) + { + hStart = slider->getSubImage(x, y, o1 - x, h); + hMid = slider->getSubImage(o1, y, o2 - o1, h); + hEnd = slider->getSubImage(o2, y, w - o2 + x, h); + } + else + { + hStart = 0; + hMid = 0; + hEnd = 0; + } + if (sliderHi) + { + hStartHi = sliderHi->getSubImage(x, y, o1 - x, h); + hMidHi = sliderHi->getSubImage(o1, y, o2 - o1, h); + hEndHi = sliderHi->getSubImage(o2, y, w - o2 + x, h); + } + else + { + hStartHi = 0; + hMidHi = 0; + hEndHi = 0; + } + + x = 6; y = 8; + w = 9; h = 10; + if (slider) + hGrip = slider->getSubImage(x, y, w, h); + else + hGrip = 0; + if (sliderHi) + hGripHi = sliderHi->getSubImage(x, y, w, h); + else + hGripHi = 0; + + x = 0; y = 6; + w = 6; h = 21; + o1 = 10; o2 = 18; + if (slider) + { + vStart = slider->getSubImage(x, y, w, o1 - y); + vMid = slider->getSubImage(x, o1, w, o2 - o1); + vEnd = slider->getSubImage(x, o2, w, h - o2 + y); + } + else + { + vStart = 0; + vMid = 0; + vEnd = 0; + } + if (sliderHi) + { + vStartHi = sliderHi->getSubImage(x, y, w, o1 - y); + vMidHi = sliderHi->getSubImage(x, o1, w, o2 - o1); + vEndHi = sliderHi->getSubImage(x, o2, w, h - o2 + y); + } + else + { + vStartHi = 0; + vMidHi = 0; + vEndHi = 0; + } + + x = 6; y = 8; + w = 9; h = 10; + if (slider) + vGrip = slider->getSubImage(x, y, w, h); + else + vGrip = 0; + + if (sliderHi) + vGripHi = sliderHi->getSubImage(x, y, w, h); + else + vGripHi = 0; + + if (slider) + slider->decRef(); + if (sliderHi) + sliderHi->decRef(); + } + + mInstances++; + + if (hGrip) + setMarkerLength(hGrip->getWidth()); +} + +void Slider::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (alpha != mAlpha) + { + mAlpha = alpha; + if (hStart) + hStart->setAlpha(mAlpha); + if (hMid) + hMid->setAlpha(mAlpha); + if (hEnd) + hEnd->setAlpha(mAlpha); + if (hGrip) + hGrip->setAlpha(mAlpha); + if (hStartHi) + hStartHi->setAlpha(mAlpha); + if (hMidHi) + hMidHi->setAlpha(mAlpha); + if (hEndHi) + hEndHi->setAlpha(mAlpha); + if (hGripHi) + hGripHi->setAlpha(mAlpha); + + if (vStart) + vStart->setAlpha(mAlpha); + if (vMid) + vMid->setAlpha(mAlpha); + if (vEnd) + vEnd->setAlpha(mAlpha); + if (vGrip) + vGrip->setAlpha(mAlpha); + if (vStartHi) + vStartHi->setAlpha(mAlpha); + if (vMidHi) + vMidHi->setAlpha(mAlpha); + if (vEndHi) + vEndHi->setAlpha(mAlpha); + if (vGripHi) + vGripHi->setAlpha(mAlpha); + } + +} + +void Slider::draw(gcn::Graphics *graphics) +{ + if (!hStart || !hStartHi) + return; + + int w = getWidth(); + int h = getHeight(); + int x = 0; + int y = mHasMouse ? (h - hStartHi->getHeight()) / 2 : + (h - hStart->getHeight()) / 2; + + updateAlpha(); + + if (!mHasMouse) + { + static_cast<Graphics*>(graphics)->drawImage(hStart, x, y); + + w -= hStart->getWidth() + hEnd->getWidth(); + x += hStart->getWidth(); + + if (hMid) + { + static_cast<Graphics*>(graphics)-> + drawImagePattern(hMid, x, y, w, hMid->getHeight()); + } + + x += w; + if (hEnd) + static_cast<Graphics*>(graphics)->drawImage(hEnd, x, y); + } + else + { + static_cast<Graphics*>(graphics)->drawImage(hStartHi, x, y); + + w -= hStartHi->getWidth(); + if (hEndHi) + w -= hEndHi->getWidth(); + x += hStartHi->getWidth(); + + if (hMidHi) + { + static_cast<Graphics*>(graphics)-> + drawImagePattern(hMidHi, x, y, w, hMidHi->getHeight()); + } + + x += w; + if (hEndHi) + static_cast<Graphics*>(graphics)->drawImage(hEndHi, x, y); + } + + drawMarker(graphics); +} + +void Slider::drawMarker(gcn::Graphics *graphics) +{ + if (!(mHasMouse?hGripHi:hGrip)) + return; + + static_cast<Graphics*>(graphics)-> + drawImage(mHasMouse?hGripHi:hGrip, getMarkerPosition(), + (getHeight() - (mHasMouse?hGripHi:hGrip)->getHeight()) / 2); +} + +void Slider::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void Slider::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} + diff --git a/src/gui/widgets/slider.h b/src/gui/widgets/slider.h new file mode 100644 index 000000000..be27b73f1 --- /dev/null +++ b/src/gui/widgets/slider.h @@ -0,0 +1,98 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SLIDER_H +#define SLIDER_H + +#include <guichan/widgets/slider.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Slider widget. Same as the Guichan slider but with custom look. + * + * \ingroup GUI + */ +class Slider : public gcn::Slider +{ + public: + /** + * Constructor with scale start equal to 0. + */ + Slider(double scaleEnd = 1.0); + + /** + * Constructor. + */ + Slider(double scaleStart, double scaleEnd); + + /** + * Destructor. + */ + ~Slider(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the slider. + */ + void draw(gcn::Graphics *graphics); + + /** + * Draws the marker. + */ + void drawMarker(gcn::Graphics *graphics); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + private: + /** + * Used to initialize instances. + */ + void init(); + + static Image *hStart, *hMid, *hEnd, *hGrip; + static Image *vStart, *vMid, *vEnd, *vGrip; + static Image *hStartHi, *hMidHi, *hEndHi, *hGripHi; + static Image *vStartHi, *vMidHi, *vEndHi, *vGripHi; + bool mHasMouse; + static float mAlpha; + static int mInstances; +}; + +#endif diff --git a/src/gui/widgets/spellshortcutcontainer.cpp b/src/gui/widgets/spellshortcutcontainer.cpp new file mode 100644 index 000000000..18482369d --- /dev/null +++ b/src/gui/widgets/spellshortcutcontainer.cpp @@ -0,0 +1,285 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/widgets/spellshortcutcontainer.h" + +#include "gui/inventorywindow.h" +#include "gui/okdialog.h" +#include "gui/palette.h" +#include "gui/shortcutwindow.h" +#include "gui/spellpopup.h" +#include "gui/viewport.h" +#include "gui/textcommandeditor.h" +#include "gui/theme.h" + +#include "configuration.h" +#include "graphics.h" +#include "inventory.h" +#include "spellshortcut.h" +#include "itemshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "spellmanager.h" +#include "log.h" + +#include "resources/image.h" +#include "textcommand.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +SpellShortcutContainer::SpellShortcutContainer(): + ShortcutContainer(), + mSpellClicked(false), + mSpellMoved(NULL) +{ + mBoxWidth = mBoxWidth; + + addMouseListener(this); + addWidgetListener(this); + + mSpellPopup = new SpellPopup; + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + if (spellShortcut) + mMaxItems = spellShortcut->getSpellsCount(); + else + mMaxItems = 0; + + if (mBackgroundImg) + { + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +SpellShortcutContainer::~SpellShortcutContainer() +{ + if (mBackgroundImg) + mBackgroundImg->decRef(); + mBackgroundImg = 0; + delete mSpellPopup; + mSpellPopup = 0; +} + +void SpellShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!spellShortcut) + return; + + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + if (mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + } + + Graphics *g = static_cast<Graphics*>(graphics); + + graphics->setColor(gcn::Color(0, 0, 0, 255)); + graphics->setFont(getFont()); + + int selectedId = spellShortcut->getSelectedItem(); + g->setColor(Theme::getThemeColor(Theme::TEXT)); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int itemX = (i % mGridWidth) * mBoxWidth; + const int itemY = (i / mGridWidth) * mBoxHeight; + + g->drawImage(mBackgroundImg, itemX, itemY); + + int itemId = spellShortcut->getItem(i); + if (selectedId >= 0 && itemId == selectedId) + { + g->drawRectangle(gcn::Rectangle( + itemX + 1, itemY + 1, + mBoxWidth - 1, mBoxHeight - 1)); + } + + if (!spellManager) + continue; + + TextCommand *spell = spellManager->getSpell(itemId); + if (spell) + { + if (!spell->isEmpty()) + { + Image* image = spell->getImage(); + + if (image) + { + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + } + } + + g->drawText(spell->getSymbol(), itemX + 2, + itemY + mBoxHeight / 2, gcn::Graphics::LEFT); + } + } + + if (mSpellMoved) + { + // Draw the item image being dragged by the cursor. + } + +} + +void SpellShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mSpellMoved && mSpellClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = spellShortcut->getItem(index); + + if (itemId < 0) + return; + } + if (mSpellMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void SpellShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Stores the selected item if theirs one. + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + } + else if (event.getButton() == gcn::MouseEvent::MIDDLE) + { + if (!spellShortcut || !spellManager) + return; + + const int itemId = spellShortcut->getItem(index); + spellManager->invoke(itemId); + } +} + +void SpellShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!spellShortcut || !spellManager) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = spellShortcut->getItem(index); + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (itemId < 0) + return; + + const int selectedId = spellShortcut->getSelectedItem(); + + if (selectedId != itemId) + { + TextCommand *spell = spellManager->getSpell(itemId); + if (spell && !spell->isEmpty()) + { + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num]) + { + itemShortcut[num]->setItemSelected( + spell->getId() + SPELL_MIN_ID); + } + spellShortcut->setItemSelected(spell->getId()); + } + } + else + { + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num]) + itemShortcut[num]->setItemSelected(-1); + spellShortcut->setItemSelected(-1); + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + TextCommand *spell = NULL; + if (itemId >= 0) + spell = spellManager->getSpell(itemId); + + if (spell && viewport) + viewport->showSpellPopup(spell); + } +} + +// Show ItemTooltip +void SpellShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!mSpellPopup || !spellShortcut || !spellManager) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = spellShortcut->getItem(index); + + mSpellPopup->setVisible(false); + TextCommand *spell = spellManager->getSpell(itemId); + if (spell && !spell->isEmpty()) + { + mSpellPopup->setItem(spell); + mSpellPopup->view(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mSpellPopup->setVisible(false); + } +} + +// Hide SpellTooltip +void SpellShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mSpellPopup->setVisible(false); +} diff --git a/src/gui/widgets/spellshortcutcontainer.h b/src/gui/widgets/spellshortcutcontainer.h new file mode 100644 index 000000000..8f1c4b221 --- /dev/null +++ b/src/gui/widgets/spellshortcutcontainer.h @@ -0,0 +1,88 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPELLSHORTCUTCONTAINER_H +#define SPELLSHORTCUTCONTAINER_H + +#include <guichan/mouselistener.hpp> + +#include "gui/widgets/shortcutcontainer.h" +//#include "textcommand.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class SpellPopup; +class TextCommand; + +/** + * An item shortcut container. Used to quickly use items. + * + * \ingroup GUI + */ +class SpellShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + SpellShortcutContainer(); + + /** + * Destructor. + */ + virtual ~SpellShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + bool mSpellClicked; + TextCommand *mSpellMoved; + SpellPopup *mSpellPopup; +}; + +#endif diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp new file mode 100644 index 000000000..43b1d154e --- /dev/null +++ b/src/gui/widgets/tab.cpp @@ -0,0 +1,196 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/tab.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "gui/widgets/tabbedarea.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include <guichan/widgets/label.hpp> + +int Tab::mInstances = 0; +float Tab::mAlpha = 1.0; + +enum +{ + TAB_STANDARD = 0, // 0 + TAB_HIGHLIGHTED, // 1 + TAB_SELECTED, // 2 + TAB_UNUSED, // 3 + TAB_COUNT // 4 - Must be last. +}; + +struct TabData +{ + char const *file; + int gridX; + int gridY; +}; + +static TabData const data[TAB_COUNT] = +{ + { "tab.png", 0, 0 }, + { "tab_hilight.png", 9, 4 }, + { "tabselected.png", 16, 19 }, + { "tab.png", 25, 23 } +}; + +ImageRect Tab::tabImg[TAB_COUNT]; + +Tab::Tab() : gcn::Tab(), + mTabColor(&Theme::getThemeColor(Theme::TAB)) +{ + init(); +} + +Tab::~Tab() +{ + mInstances--; + + if (mInstances == 0) + { + for (int mode = 0; mode < TAB_COUNT; mode++) + for_each(tabImg[mode].grid, tabImg[mode].grid + 9, dtor<Image*>()); + } +} + +void Tab::init() +{ + setFocusable(false); + setFrameSize(0); + mFlash = 0; + + if (mInstances == 0) + { + // Load the skin + Image *tab[TAB_COUNT]; + + int a, x, y, mode; + + for (mode = 0; mode < TAB_COUNT; mode++) + { + tab[mode] = Theme::getImageFromTheme(data[mode].file); + a = 0; + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + tabImg[mode].grid[a] = tab[mode]->getSubImage( + data[x].gridX, data[y].gridY, + data[x + 1].gridX - data[x].gridX + 1, + data[y + 1].gridY - data[y].gridY + 1); + if (tabImg[mode].grid[a]) + tabImg[mode].grid[a]->setAlpha(mAlpha); + a++; + } + } + if (tab[mode]) + tab[mode]->decRef(); + } + } + mInstances++; +} + +void Tab::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + // TODO We don't need to do this for every tab on every draw + // Maybe use a config listener to do it as the value changes. + if (alpha != mAlpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + for (int t = 0; t < TAB_COUNT; t++) + { + if (tabImg[t].grid[a]) + tabImg[t].grid[a]->setAlpha(mAlpha); + } + } + } +} + +void Tab::draw(gcn::Graphics *graphics) +{ + int mode = TAB_STANDARD; + + // check which type of tab to draw + if (mTabbedArea) + { + mLabel->setForegroundColor(*mTabColor); + if (mTabbedArea->isTabSelected(this)) + { + mode = TAB_SELECTED; + // if tab is selected, it doesnt need to highlight activity + mFlash = 0; + } + else if (mHasMouse) + { + mode = TAB_HIGHLIGHTED; + } + + switch (mFlash) + { + case 1: + mLabel->setForegroundColor(Theme::getThemeColor( + Theme::TAB_FLASH)); + break; + case 2: + mLabel->setForegroundColor(Theme::getThemeColor( + Theme::TAB_PLAYER_FLASH)); + break; + default: + break; + } + } + + updateAlpha(); + + // draw tab + static_cast<Graphics*>(graphics)-> + drawImageRect(0, 0, getWidth(), getHeight(), tabImg[mode]); + + // draw label + drawChildren(graphics); +} + +void Tab::setTabColor(const gcn::Color *color) +{ + mTabColor = color; +} + +void Tab::setFlash(int flash) +{ + mFlash = flash; +} diff --git a/src/gui/widgets/tab.h b/src/gui/widgets/tab.h new file mode 100644 index 000000000..b76717bcd --- /dev/null +++ b/src/gui/widgets/tab.h @@ -0,0 +1,80 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TAB_H +#define TAB_H + +#include <guichan/widgets/tab.hpp> + +class ImageRect; +class TabbedArea; + +/** + * A tab, the same as the Guichan tab in 0.8, but extended to allow + * transparency. + */ +class Tab : public gcn::Tab +{ + public: + Tab(); + ~Tab(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draw the tabbed area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Set the normal color fo the tab's text. + */ + void setTabColor(const gcn::Color *color); + + /** + * Set tab flashing state + */ + void setFlash(int flash); + + int getFlash() + { return mFlash; } + + protected: + friend class TabbedArea; + virtual void setCurrent() + { } + + private: + /** Load images if no other instances exist yet */ + void init(); + + static ImageRect tabImg[4]; /**< Tab state graphics */ + static int mInstances; /**< Number of tab instances */ + static float mAlpha; + + const gcn::Color *mTabColor; + int mFlash; +}; + +#endif diff --git a/src/gui/widgets/tabbedarea.cpp b/src/gui/widgets/tabbedarea.cpp new file mode 100644 index 000000000..232664860 --- /dev/null +++ b/src/gui/widgets/tabbedarea.cpp @@ -0,0 +1,221 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/tabbedarea.h" + +#include "gui/widgets/tab.h" + +#include "log.h" + +#include <guichan/widgets/container.hpp> + +TabbedArea::TabbedArea() : gcn::TabbedArea() +{ + mWidgetContainer->setOpaque(false); + addWidgetListener(this); + + widgetResized(NULL); +} + +int TabbedArea::getNumberOfTabs() const +{ + return static_cast<int>(mTabs.size()); +} + +Tab *TabbedArea::getTab(const std::string &name) const +{ + TabContainer::const_iterator itr = mTabs.begin(), itr_end = mTabs.end(); + while (itr != itr_end) + { + if ((*itr).first->getCaption() == name) + return static_cast<Tab*>((*itr).first); + + ++itr; + } + return NULL; +} + +void TabbedArea::draw(gcn::Graphics *graphics) +{ + if (mTabs.empty()) + return; + + drawChildren(graphics); +} + +gcn::Widget *TabbedArea::getWidget(const std::string &name) const +{ + TabContainer::const_iterator itr = mTabs.begin(), itr_end = mTabs.end(); + while (itr != itr_end) + { + if ((*itr).first->getCaption() == name) + return (*itr).second; + + ++itr; + } + + return NULL; +} + +gcn::Widget *TabbedArea::getCurrentWidget() +{ + gcn::Tab *tab = getSelectedTab(); + + if (tab) + return getWidget(tab->getCaption()); + else + return NULL; +} + +void TabbedArea::addTab(gcn::Tab* tab, gcn::Widget* widget) +{ + if (!tab || !widget) + return; + + gcn::TabbedArea::addTab(tab, widget); + + int width = getWidth() - 2 * getFrameSize(); + int height = getHeight() - 2 * getFrameSize() - mTabContainer->getHeight(); + widget->setSize(width, height); +} + +void TabbedArea::addTab(const std::string &caption, gcn::Widget *widget) +{ + Tab *tab = new Tab; + tab->setCaption(caption); + mTabsToDelete.push_back(tab); + + addTab(tab, widget); +} + +void TabbedArea::removeTab(Tab *tab) +{ + int tabIndexToBeSelected = -1; + + if (tab == mSelectedTab) + { + int index = getSelectedTabIndex(); + + if (index == static_cast<int>(mTabs.size()) - 1 && mTabs.size() == 1) + tabIndexToBeSelected = -1; + else + tabIndexToBeSelected = index - 1; + } + + TabContainer::iterator iter; + for (iter = mTabs.begin(); iter != mTabs.end(); iter++) + { + if (iter->first == tab) + { + mTabContainer->remove(tab); + mTabs.erase(iter); + break; + } + } + + std::vector<gcn::Tab*>::iterator iter2; + for (iter2 = mTabsToDelete.begin(); iter2 != mTabsToDelete.end(); iter2++) + { + if (*iter2 == tab) + { + mTabsToDelete.erase(iter2); + delete tab; + break; + } + } + + if (tabIndexToBeSelected >= (signed)mTabs.size()) + tabIndexToBeSelected = mTabs.size() - 1; + if (tabIndexToBeSelected < -1) + tabIndexToBeSelected = -1; + + if (tabIndexToBeSelected == -1) + { + mSelectedTab = 0; + mWidgetContainer->clear(); + } + else + { + setSelectedTab(tabIndexToBeSelected); + } + + adjustSize(); + adjustTabPositions(); +} + +void TabbedArea::logic() +{ + logicChildren(); +} + +void TabbedArea::mousePressed(gcn::MouseEvent &mouseEvent) +{ + if (mouseEvent.isConsumed()) + return; + + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + gcn::Widget *widget = mTabContainer->getWidgetAt(mouseEvent.getX(), + mouseEvent.getY()); + gcn::Tab *tab = dynamic_cast<gcn::Tab*>(widget); + + if (tab) + { + setSelectedTab(tab); + requestFocus(); + } + } +} + +void TabbedArea::setSelectedTab(gcn::Tab *tab) +{ + gcn::TabbedArea::setSelectedTab(tab); + + Tab *newTab = dynamic_cast<Tab*>(tab); + + if (newTab) + newTab->setCurrent(); + + widgetResized(NULL); +} + +void TabbedArea::widgetResized(const gcn::Event &event _UNUSED_) +{ + int width = getWidth() - 2 * getFrameSize() + - 2 * mWidgetContainer->getFrameSize(); + int height = getHeight() - 2 * getFrameSize() - mWidgetContainer->getY() + - 2 * mWidgetContainer->getFrameSize(); + mWidgetContainer->setSize(width, height); + + gcn::Widget *w = getCurrentWidget(); + if (w) + w->setSize(width, height); +} + +/* +void TabbedArea::moveLeft(gcn::Tab *tab) +{ +} + +void TabbedArea::moveRight(gcn::Tab *tab) +{ +} +*/ diff --git a/src/gui/widgets/tabbedarea.h b/src/gui/widgets/tabbedarea.h new file mode 100644 index 000000000..de2ae4b0a --- /dev/null +++ b/src/gui/widgets/tabbedarea.h @@ -0,0 +1,129 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TABBEDAREA_H +#define TABBEDAREA_H + +#include <guichan/widget.hpp> +#include <guichan/widgetlistener.hpp> +#include <guichan/widgets/container.hpp> +#include <guichan/widgets/tabbedarea.hpp> + +#include <string> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Tab; + +/** + * A tabbed area, the same as the guichan tabbed area in 0.8, but extended + */ +class TabbedArea : public gcn::TabbedArea, public gcn::WidgetListener +{ + public: + /** + * Constructor. + */ + TabbedArea(); + + /** + * Draw the tabbed area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Return how many tabs have been created. + * + * @todo Remove this method when upgrading to Guichan 0.9.0 + */ + int getNumberOfTabs() const; + + /** + * Return tab with specified name as caption. + */ + Tab *getTab(const std::string &name) const; + + /** + * Returns the widget with the tab that has specified caption + */ + gcn::Widget *getWidget(const std::string &name) const; + + /** + * Returns the widget for the current tab + */ + gcn::Widget *getCurrentWidget(); + + using gcn::TabbedArea::addTab; + + /** + * Add a tab. Overridden since it needs to size the widget. + * + * @param tab The tab widget for the tab. + * @param widget The widget to view when the tab is selected. + */ + void addTab(gcn::Tab* tab, gcn::Widget* widget); + + /** + * Add a tab. Overridden since it needs to create an instance of Tab + * instead of gcn::Tab. + * + * @param caption The Caption to display + * @param widget The widget to show when tab is selected + */ + void addTab(const std::string &caption, gcn::Widget *widget); + + /** + * Overload the remove tab function as it's broken in guichan 0.8. + */ + void removeTab(Tab *tab); + + /** + * Overload the logic function since it's broken in guichan 0.8. + */ + void logic(); + + int getContainerHeight() const + { return mWidgetContainer->getHeight(); } + + using gcn::TabbedArea::setSelectedTab; + + void setSelectedTab(gcn::Tab *tab); + + void widgetResized(const gcn::Event &event); + +/* + void moveLeft(gcn::Tab *tab); + + void moveRight(gcn::Tab *tab); +*/ + // Inherited from MouseListener + + void mousePressed(gcn::MouseEvent &mouseEvent); + + private: + typedef std::vector< std::pair<gcn::Tab*, gcn::Widget*> > TabContainer; +}; + +#endif diff --git a/src/gui/widgets/table.cpp b/src/gui/widgets/table.cpp new file mode 100644 index 000000000..39ef719ef --- /dev/null +++ b/src/gui/widgets/table.cpp @@ -0,0 +1,585 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/table.h" + +#include "client.h" +#include "configuration.h" + +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "utils/dtor.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/graphics.hpp> +#include <guichan/key.hpp> + +float GuiTable::mAlpha = 1.0; + +class GuiTableActionListener : public gcn::ActionListener +{ +public: + GuiTableActionListener(GuiTable *_table, gcn::Widget *_widget, + int _row, int _column); + + virtual ~GuiTableActionListener(); + + virtual void action(const gcn::ActionEvent& actionEvent); + +protected: + GuiTable *mTable; + int mRow; + int mColumn; + gcn::Widget *mWidget; +}; + + +GuiTableActionListener::GuiTableActionListener(GuiTable *table, + gcn::Widget *widget, int row, + int column) : + mTable(table), + mRow(row), + mColumn(column), + mWidget(widget) +{ + if (widget) + { + widget->addActionListener(this); + widget->_setParent(table); + } +} + +GuiTableActionListener::~GuiTableActionListener() +{ + if (mWidget) + { + mWidget->removeActionListener(this); + mWidget->_setParent(NULL); + } +} + +void GuiTableActionListener::action(const gcn::ActionEvent + &actionEvent _UNUSED_) +{ + mTable->setSelected(mRow, mColumn); + mTable->distributeActionEvent(); +} + + +GuiTable::GuiTable(TableModel *initial_model, gcn::Color background, + bool opacity) : + mLinewiseMode(false), + mWrappingEnabled(false), + mOpaque(opacity), + mBackgroundColor(background), + mModel(NULL), + mSelectedRow(0), + mSelectedColumn(0), + mTopWidget(NULL) +{ + setModel(initial_model); + setFocusable(true); + + addMouseListener(this); + addKeyListener(this); +} + +GuiTable::~GuiTable() +{ + uninstallActionListeners(); + delete mModel; + mModel = 0; +} + +TableModel *GuiTable::getModel() const +{ + return mModel; +} + +void GuiTable::setModel(TableModel *new_model) +{ + if (mModel) + { + uninstallActionListeners(); + mModel->removeListener(this); + } + + mModel = new_model; + installActionListeners(); + + if (new_model) + { + new_model->installListener(this); + recomputeDimensions(); + } +} + +void GuiTable::recomputeDimensions() +{ + if (!mModel) + return; + + int rows_nr = mModel->getRows(); + int columns_nr = mModel->getColumns(); + int width = 0; + int height = 0; + + if (mSelectedRow >= rows_nr) + mSelectedRow = rows_nr - 1; + + if (mSelectedColumn >= columns_nr) + mSelectedColumn = columns_nr - 1; + + for (int i = 0; i < columns_nr; i++) + width += getColumnWidth(i); + + height = getRowHeight() * rows_nr; + + setWidth(width); + setHeight(height); +} + +void GuiTable::setSelected(int row, int column) +{ + mSelectedColumn = column; + mSelectedRow = row; +} + +int GuiTable::getSelectedRow() const +{ + return mSelectedRow; +} + +int GuiTable::getSelectedColumn() const +{ + return mSelectedColumn; +} + +void GuiTable::setLinewiseSelection(bool linewise) +{ + mLinewiseMode = linewise; +} + +int GuiTable::getRowHeight() const +{ + if (mModel) + return mModel->getRowHeight() + 1; // border + else + return 0; +} + +int GuiTable::getColumnWidth(int i) const +{ + if (mModel) + return mModel->getColumnWidth(i) + 1; // border + else + return 0; +} + +void GuiTable::setSelectedRow(int selected) +{ + if (!mModel) + { + mSelectedRow = -1; + } + else + { + if (selected < 0 && !mWrappingEnabled) + { + mSelectedRow = -1; + } + else if (selected >= mModel->getRows() && mWrappingEnabled) + { + mSelectedRow = 0; + } + else if ((selected >= mModel->getRows() && !mWrappingEnabled) || + (selected < 0 && mWrappingEnabled)) + { + mSelectedRow = mModel->getRows() - 1; + } + else + { + mSelectedRow = selected; + } + } +} + +void GuiTable::setSelectedColumn(int selected) +{ + if (!mModel) + { + mSelectedColumn = -1; + } + else + { + if ((selected >= mModel->getColumns() && mWrappingEnabled) || + (selected < 0 && !mWrappingEnabled)) + { + mSelectedColumn = 0; + } + else if ((selected >= mModel->getColumns() && !mWrappingEnabled) || + (selected < 0 && mWrappingEnabled)) + { + mSelectedColumn = mModel->getColumns() - 1; + } + else + { + mSelectedColumn = selected; + } + } +} + +void GuiTable::uninstallActionListeners() +{ + delete_all(mActionListeners); + mActionListeners.clear(); +} + +void GuiTable::installActionListeners() +{ + if (!mModel) + return; + + int rows = mModel->getRows(); + int columns = mModel->getColumns(); + + for (int row = 0; row < rows; ++row) + { + for (int column = 0; column < columns; ++column) + { + gcn::Widget *widget = mModel->getElementAt(row, column); + if (widget) + { + mActionListeners.push_back(new GuiTableActionListener( + this, widget, row, column)); + } + } + } + + _setFocusHandler(_getFocusHandler()); // propagate focus handler to widgets +} + +// -- widget ops +void GuiTable::draw(gcn::Graphics* graphics) +{ + if (!mModel || !getRowHeight()) + return; + + if (Client::getGuiAlpha() != mAlpha) + mAlpha = Client::getGuiAlpha(); + + if (mOpaque) + { + graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND, + static_cast<int>(mAlpha * 255.0f))); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + // First, determine how many rows we need to draw, and where we should start. + int first_row = -(getY() / getRowHeight()); + + if (first_row < 0) + first_row = 0; + + int rows_nr = 1 + (getHeight() / getRowHeight()); // May overestimate + // by one. + + int max_rows_nr = mModel->getRows() - first_row; // clip if neccessary: + if (max_rows_nr < rows_nr) + rows_nr = max_rows_nr; + + // Now determine the first and last column + // Take the easy way out; these are usually bounded and all visible. + int first_column = 0; + int last_column1 = mModel->getColumns(); + + // Set up everything for drawing + int height = getRowHeight(); + int y_offset = first_row * height; + + for (int r = first_row; r < first_row + rows_nr; ++r) + { + int x_offset = 0; + + for (int c = first_column; c + 1 <= last_column1; ++c) + { + gcn::Widget *widget = mModel->getElementAt(r, c); + int width = getColumnWidth(c); + if (widget) + { + gcn::Rectangle bounds(x_offset, y_offset, width, height); + + if (widget == mTopWidget) + { + bounds.height = widget->getHeight(); + bounds.width = widget->getWidth(); + } + + widget->setDimension(bounds); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast<int>(mAlpha * 255.0f))); + + if (mLinewiseMode && r == mSelectedRow && c == 0) + { + graphics->fillRectangle(gcn::Rectangle(0, y_offset, + getWidth(), height)); + } + else if (!mLinewiseMode && + c == mSelectedColumn && r == mSelectedRow) + { + graphics->fillRectangle(gcn::Rectangle(x_offset, y_offset, + width, height)); + } + + graphics->pushClipArea(bounds); + widget->draw(graphics); + graphics->popClipArea(); + } + + x_offset += width; + } + + y_offset += height; + } + + if (mTopWidget) + { + gcn::Rectangle bounds = mTopWidget->getDimension(); + graphics->pushClipArea(bounds); + mTopWidget->draw(graphics); + graphics->popClipArea(); + } +} + +void GuiTable::moveToTop(gcn::Widget *widget) +{ + gcn::Widget::moveToTop(widget); + mTopWidget = widget; +} + +void GuiTable::moveToBottom(gcn::Widget *widget) +{ + gcn::Widget::moveToBottom(widget); + if (widget == mTopWidget) + mTopWidget = NULL; +} + +gcn::Rectangle GuiTable::getChildrenArea() const +{ + return gcn::Rectangle(0, 0, getWidth(), getHeight()); +} + +// -- KeyListener notifications +void GuiTable::keyPressed(gcn::KeyEvent& keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE) + { + distributeActionEvent(); + keyEvent.consume(); + } + else if (key.getValue() == Key::UP) + { + setSelectedRow(mSelectedRow - 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::DOWN) + { + setSelectedRow(mSelectedRow + 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::LEFT) + { + setSelectedColumn(mSelectedColumn - 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::RIGHT) + { + setSelectedColumn(mSelectedColumn + 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::HOME) + { + setSelectedRow(0); + setSelectedColumn(0); + keyEvent.consume(); + } + else if (key.getValue() == Key::END && mModel) + { + setSelectedRow(mModel->getRows() - 1); + setSelectedColumn(mModel->getColumns() - 1); + keyEvent.consume(); + } +} + +// -- MouseListener notifications +void GuiTable::mousePressed(gcn::MouseEvent& mouseEvent) +{ + if (!mModel) + return; + + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + int row = getRowForY(mouseEvent.getY()); + int column = getColumnForX(mouseEvent.getX()); + + if (row > -1 && column > -1 && + row < mModel->getRows() && column < mModel->getColumns()) + { + mSelectedColumn = column; + mSelectedRow = row; + } + + distributeActionEvent(); + } +} + +void GuiTable::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent) +{ + if (isFocused()) + { + if (getSelectedRow() > 0 || (getSelectedRow() == 0 + && mWrappingEnabled)) + { + setSelectedRow(getSelectedRow() - 1); + } + + mouseEvent.consume(); + } +} + +void GuiTable::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent) +{ + if (isFocused()) + { + setSelectedRow(getSelectedRow() + 1); + + mouseEvent.consume(); + } +} + +void GuiTable::mouseDragged(gcn::MouseEvent& mouseEvent) +{ + if (mouseEvent.getButton() != gcn::MouseEvent::LEFT) + return; + + // Make table selection update on drag + const int x = std::max(0, mouseEvent.getX()); + const int y = std::max(0, mouseEvent.getY()); + + setSelectedRow(getRowForY(y)); + setSelectedColumn(getColumnForX(x)); +} + +// -- TableModelListener notifications +void GuiTable::modelUpdated(bool completed) +{ + if (completed) + { + recomputeDimensions(); + installActionListeners(); + } + else + { // before the update? + mTopWidget = NULL; // No longer valid in general + uninstallActionListeners(); + } +} + +gcn::Widget *GuiTable::getWidgetAt(int x, int y) const +{ + int row = getRowForY(y); + int column = getColumnForX(x); + + if (mTopWidget && mTopWidget->getDimension().isPointInRect(x, y)) + return mTopWidget; + + if (mModel && row > -1 && column > -1) + { + gcn::Widget *w = mModel->getElementAt(row, column); + if (w && w->isFocusable()) + return w; + else + return NULL; // Grab the event locally + } + else + return NULL; +} + +int GuiTable::getRowForY(int y) const +{ + int row = -1; + + if (getRowHeight() > 0) + row = y / getRowHeight(); + + if (!mModel || row < 0 || row >= mModel->getRows()) + return -1; + else + return row; +} + +int GuiTable::getColumnForX(int x) const +{ + if (!mModel) + return -1; + + int column; + int delta = 0; + + for (column = 0; column < mModel->getColumns(); column++) + { + delta += getColumnWidth(column); + if (x <= delta) + break; + } + + if (column < 0 || column >= mModel->getColumns()) + return -1; + else + return column; +} + +void GuiTable::_setFocusHandler(gcn::FocusHandler* focusHandler) +{ +// add check for focusHandler. may be need remove it? + + if (!mModel || !focusHandler) + return; + + gcn::Widget::_setFocusHandler(focusHandler); + + if (mModel) + { + for (int r = 0; r < mModel->getRows(); ++r) + { + for (int c = 0; c < mModel->getColumns(); ++c) + { + gcn::Widget *w = mModel->getElementAt(r, c); + if (w) + w->_setFocusHandler(focusHandler); + } + } + } +} diff --git a/src/gui/widgets/table.h b/src/gui/widgets/table.h new file mode 100644 index 000000000..61c7302b2 --- /dev/null +++ b/src/gui/widgets/table.h @@ -0,0 +1,195 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TABLE_H +#define TABLE_H + +#include "tablemodel.h" + +#include <guichan/keylistener.hpp> +#include <guichan/mouselistener.hpp> +#include <guichan/widget.hpp> + +#include <vector> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class GuiTableActionListener; + +/** + * A table, with rows and columns made out of sub-widgets. Largely inspired by + * (and can be thought of as a generalisation of) the guichan listbox + * implementation. + * + * Normally you want this within a ScrollArea. + * + * \ingroup GUI + */ +class GuiTable : public gcn::Widget, + public gcn::MouseListener, + public gcn::KeyListener, + public TableModelListener +{ + // so that the action listener can call distributeActionEvent + friend class GuiTableActionListener; + +public: + GuiTable(TableModel * initial_model = NULL, + gcn::Color background = 0xffffff, + bool opacity = true); + + virtual ~GuiTable(); + + /** + * Retrieves the active table model + */ + TableModel *getModel() const; + + /** + * Sets the table model + * + * Note that actions issued by widgets returned from the model will update + * the table selection, but only AFTER any event handlers installed within + * the widget have been triggered. To be notified after such an update, add + * an action listener to the table instead. + */ + void setModel(TableModel *m); + + void setSelected(int row, int column); + + int getSelectedRow() const; + + int getSelectedColumn() const; + + void setSelectedRow(int selected); + + void setSelectedColumn(int selected); + + bool isWrappingEnabled() const + { return mWrappingEnabled; } + + void setWrappingEnabled(bool wrappingEnabled) + { mWrappingEnabled = wrappingEnabled; } + + gcn::Rectangle getChildrenArea() const; + + /** + * Toggle whether to use linewise selection mode, in which the table selects + * an entire line at a time, rather than a single cell. + * + * Note that column information is tracked even in linewise selection mode; + * this mode therefore only affects visualisation. + * + * Disabled by default. + * + * \param linewise: Whether to enable linewise selection mode + */ + void setLinewiseSelection(bool linewise); + + // Inherited from Widget + virtual void draw(gcn::Graphics* graphics); + + virtual gcn::Widget *getWidgetAt(int x, int y) const; + + virtual void moveToTop(gcn::Widget *child); + + virtual void moveToBottom(gcn::Widget *child); + + virtual void _setFocusHandler(gcn::FocusHandler* focusHandler); + + // Inherited from KeyListener + virtual void keyPressed(gcn::KeyEvent& keyEvent); + + /** + * Sets the table to be opaque, that is sets the table + * to display its background. + * + * @param opaque True if the table should be opaque, false otherwise. + */ + virtual void setOpaque(bool opaque) + { mOpaque = opaque; } + + /** + * Checks if the table is opaque, that is if the table area displays its + * background. + * + * @return True if the table is opaque, false otherwise. + */ + virtual bool isOpaque() const + { return mOpaque; } + + // Inherited from MouseListener + virtual void mousePressed(gcn::MouseEvent& mouseEvent); + + virtual void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent); + + virtual void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent); + + virtual void mouseDragged(gcn::MouseEvent& mouseEvent); + + // Constraints inherited from TableModelListener + virtual void modelUpdated(bool); + +protected: + /** Frees all action listeners on inner widgets. */ + virtual void uninstallActionListeners(); + /** Installs all action listeners on inner widgets. */ + virtual void installActionListeners(); + + virtual int getRowHeight() const; + virtual int getColumnWidth(int i) const; + +private: + int getRowForY(int y) const; // -1 on error + int getColumnForX(int x) const; // -1 on error + void recomputeDimensions(); + bool mLinewiseMode; + bool mWrappingEnabled; + bool mOpaque; + + static float mAlpha; + + /** + * Holds the background color of the table. + */ + gcn::Color mBackgroundColor; + + TableModel *mModel; + + int mSelectedRow; + int mSelectedColumn; + + /** Number of frames to skip upwards when drawing the selected widget. */ + int mPopFramesNr; + + /** If someone moves a fresh widget to the top, we must display it. */ + gcn::Widget *mTopWidget; + + /** Vector for compactness; used as a list in practice. */ + std::vector<GuiTableActionListener *> mActionListeners; +}; + + +#endif // TABLE_H diff --git a/src/gui/widgets/tablemodel.cpp b/src/gui/widgets/tablemodel.cpp new file mode 100644 index 000000000..f1d583ef6 --- /dev/null +++ b/src/gui/widgets/tablemodel.cpp @@ -0,0 +1,173 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/tablemodel.h" + +#include "utils/dtor.h" + +#include <guichan/widget.hpp> + +void TableModel::installListener(TableModelListener *listener) +{ + if (listener) + listeners.insert(listener); +} + +void TableModel::removeListener(TableModelListener *listener) +{ + if (listener) + listeners.erase(listener); +} + +void TableModel::signalBeforeUpdate() +{ + for (std::set<TableModelListener *>::const_iterator it = listeners.begin(); + it != listeners.end(); it++) + { + (*it)->modelUpdated(false); + } +} + +void TableModel::signalAfterUpdate() +{ + for (std::set<TableModelListener *>::const_iterator it = listeners.begin(); + it != listeners.end(); it++) + { + if (*it) + (*it)->modelUpdated(true); + } +} + + +#define WIDGET_AT(row, column) (((row) * mColumns) + (column)) +#define DYN_SIZE(h) ((h) >= 0) + +StaticTableModel::StaticTableModel(int row, int column) : + mRows(row), + mColumns(column), + mHeight(1) +{ + mTableModel.resize(row * column, NULL); + mWidths.resize(column, 1); +} + +StaticTableModel::~StaticTableModel() +{ + delete_all(mTableModel); + mTableModel.clear(); +} + +void StaticTableModel::resize() +{ + mRows = getRows(); + mColumns = getColumns(); + mTableModel.resize(mRows * mColumns, NULL); +} + +void StaticTableModel::set(int row, int column, gcn::Widget *widget) +{ + if (!widget || row >= mRows || row < 0 + || column >= mColumns || column < 0) + { + // raise exn? + return; + } + + if (DYN_SIZE(mHeight) + && widget->getHeight() > mHeight) + { + mHeight = widget->getHeight(); + } + + if (DYN_SIZE(mWidths[column]) + && widget->getWidth() > mWidths[column]) + { + mWidths[column] = widget->getWidth(); + } + + signalBeforeUpdate(); + + delete mTableModel[WIDGET_AT(row, column)]; + + mTableModel[WIDGET_AT(row, column)] = widget; + + signalAfterUpdate(); +} + +gcn::Widget *StaticTableModel::getElementAt(int row, int column) const +{ + return mTableModel[WIDGET_AT(row, column)]; +} + +void StaticTableModel::fixColumnWidth(int column, int width) +{ + if (width < 0 || column < 0 || column >= mColumns) + return; + + mWidths[column] = -width; // Negate to tag as fixed +} + +void StaticTableModel::fixRowHeight(int height) +{ + if (height < 0) + return; + + mHeight = -height; +} + +int StaticTableModel::getRowHeight() const +{ + return abs(mHeight); +} + +int StaticTableModel::getColumnWidth(int column) const +{ + if (column < 0 || column >= mColumns) + return 0; + + return abs(mWidths[column]); +} + +int StaticTableModel::getRows() const +{ + return mRows; +} + +int StaticTableModel::getColumns() const +{ + return mColumns; +} + +int StaticTableModel::getWidth() const +{ + int width = 0; + + for (unsigned int i = 0; i < mWidths.size(); i++) + width += mWidths[i]; + + return width; +} + +int StaticTableModel::getHeight() const +{ + return mColumns * mHeight; +} + diff --git a/src/gui/widgets/tablemodel.h b/src/gui/widgets/tablemodel.h new file mode 100644 index 000000000..2b8729341 --- /dev/null +++ b/src/gui/widgets/tablemodel.h @@ -0,0 +1,149 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TABLE_MODEL_H +#define TABLE_MODEL_H + +#include <guichanfwd.h> + +#include <set> +#include <vector> + +class TableModelListener +{ +public: + /** + * Must be invoked by the TableModel whenever a global change is about to + * occur or has occurred (e.g., when a row or column is being removed or + * added). + * + * This method is triggered twice, once before and once after the update. + * + * \param completed whether we are signalling the end of the update + */ + virtual void modelUpdated(bool completed) = 0; + + virtual ~TableModelListener() {} +}; + +/** + * A model for a regular table of widgets. + */ +class TableModel +{ +public: + virtual ~TableModel() + { } + + /** + * Determines the number of rows (lines) in the table + */ + virtual int getRows() const = 0; + + /** + * Determines the number of columns in each row + */ + virtual int getColumns() const = 0; + + /** + * Determines the height for each row + */ + virtual int getRowHeight() const = 0; + + /** + * Determines the width of each individual column + */ + virtual int getColumnWidth(int index) const = 0; + + /** + * Retrieves the widget stored at the specified location within the table. + */ + virtual gcn::Widget *getElementAt(int row, int column) const = 0; + + virtual void installListener(TableModelListener *listener); + + virtual void removeListener(TableModelListener *listener); + +protected: + /** + * Tells all listeners that the table is about to see an update + */ + virtual void signalBeforeUpdate(); + + /** + * Tells all listeners that the table has seen an update + */ + virtual void signalAfterUpdate(); + +private: + std::set<TableModelListener *> listeners; +}; + + +class StaticTableModel : public TableModel +{ +public: + StaticTableModel(int width, int height); + virtual ~StaticTableModel(); + + /** + * Inserts a widget into the table model. + * The model is resized to accomodate the widget's width and height, + * unless column width / row height have been fixed. + */ + virtual void set(int row, int column, gcn::Widget *widget); + + /** + * Fixes the column width for a given column; this overrides dynamic width + * inference. + * + * Semantics are undefined for width 0. + */ + virtual void fixColumnWidth(int column, int width); + + /** + * Fixes the row height; this overrides dynamic height inference. + * + * Semantics are undefined for width 0. + */ + virtual void fixRowHeight(int height); + + /** + * Resizes the table model + */ + virtual void resize(); + + virtual int getRows() const; + virtual int getColumns() const; + virtual int getRowHeight() const; + virtual int getWidth() const; + virtual int getHeight() const; + virtual int getColumnWidth(int index) const; + virtual gcn::Widget *getElementAt(int row, int column) const; + +protected: + int mRows, mColumns; + int mHeight; + std::vector<gcn::Widget *> mTableModel; + std::vector<int> mWidths; +}; + +#endif // TABLE_MODEL_H diff --git a/src/gui/widgets/textbox.cpp b/src/gui/widgets/textbox.cpp new file mode 100644 index 000000000..f248f35d2 --- /dev/null +++ b/src/gui/widgets/textbox.cpp @@ -0,0 +1,149 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/textbox.h" + +#include "gui/theme.h" + +#include <guichan/font.hpp> + +#include <sstream> + +TextBox::TextBox() : + mTextColor(&Theme::getThemeColor(Theme::TEXT)) +{ + setOpaque(false); + setFrameSize(0); + mMinWidth = getWidth(); +} + +void TextBox::setTextWrapped(const std::string &text, int minDimension) +{ + // Make sure parent scroll area sets width of this widget + if (getParent()) + getParent()->logic(); + + // Take the supplied minimum dimension as a starting point and try to beat it + mMinWidth = minDimension; + + std::stringstream wrappedStream; + std::string::size_type spacePos, newlinePos, lastNewlinePos = 0; + int minWidth = 0; + int xpos; + + spacePos = text.rfind(" ", text.size()); + + if (spacePos != std::string::npos) + { + const std::string word = text.substr(spacePos + 1); + const int length = getFont()->getWidth(word); + + if (length > mMinWidth) + mMinWidth = length; + } + + do + { + // Determine next piece of string to wrap + newlinePos = text.find("\n", lastNewlinePos); + + if (newlinePos == std::string::npos) + newlinePos = text.size(); + + std::string line = + text.substr(lastNewlinePos, newlinePos - lastNewlinePos); + std::string::size_type lastSpacePos = 0; + xpos = 0; + + do + { + spacePos = line.find(" ", lastSpacePos); + + if (spacePos == std::string::npos) + spacePos = line.size(); + + std::string word = + line.substr(lastSpacePos, spacePos - lastSpacePos); + + int width = getFont()->getWidth(word); + + if (xpos == 0 && width > mMinWidth) + { + mMinWidth = width; + xpos = width; + wrappedStream << word; + } + else if (xpos != 0 && xpos + getFont()->getWidth(" ") + width <= + mMinWidth) + { + xpos += getFont()->getWidth(" ") + width; + wrappedStream << " " << word; + } + else if (lastSpacePos == 0) + { + xpos += width; + wrappedStream << word; + } + else + { + if (xpos > minWidth) + minWidth = xpos; + + // The window wasn't big enough. Resize it and try again. + if (minWidth > mMinWidth) + { + mMinWidth = minWidth; + wrappedStream.clear(); + wrappedStream.str(""); + spacePos = 0; + lastNewlinePos = 0; + newlinePos = text.find("\n", lastNewlinePos); + if (newlinePos == std::string::npos) + newlinePos = text.size(); + line = text.substr(lastNewlinePos, newlinePos - + lastNewlinePos); + width = 0; + break; + } + else + { + wrappedStream << "\n" << word; + } + xpos = width; + } + lastSpacePos = spacePos + 1; + } + while (spacePos != line.size()); + + if (text.find("\n", lastNewlinePos) != std::string::npos) + wrappedStream << "\n"; + + lastNewlinePos = newlinePos + 1; + } + while (newlinePos != text.size()); + + if (xpos > minWidth) + minWidth = xpos; + + mMinWidth = minWidth; + + gcn::TextBox::setText(wrappedStream.str()); +} diff --git a/src/gui/widgets/textbox.h b/src/gui/widgets/textbox.h new file mode 100644 index 000000000..dffaf2736 --- /dev/null +++ b/src/gui/widgets/textbox.h @@ -0,0 +1,70 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TEXTBOX_H +#define TEXTBOX_H + +#include <guichan/widgets/textbox.hpp> + +/** + * A text box, meant to be used inside a scroll area. Same as the Guichan text + * box except this one doesn't have a background or border, instead completely + * relying on the scroll area. + * + * \ingroup GUI + */ +class TextBox : public gcn::TextBox +{ + public: + /** + * Constructor. + */ + TextBox(); + + inline void setTextColor(const gcn::Color *color) + { mTextColor = color; } + + /** + * Sets the text after wrapping it to the current width of the widget. + */ + void setTextWrapped(const std::string &text, int minDimension); + + /** + * Get the minimum text width for the text box. + */ + int getMinWidth() const + { return mMinWidth; } + + /** + * Draws the text. + */ + inline void draw(gcn::Graphics *graphics) + { + setForegroundColor(*mTextColor); + gcn::TextBox::draw(graphics); + } + + private: + int mMinWidth; + const gcn::Color *mTextColor; +}; + +#endif diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp new file mode 100644 index 000000000..9a5b2de33 --- /dev/null +++ b/src/gui/widgets/textfield.cpp @@ -0,0 +1,306 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/textfield.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/copynpaste.h" +#include "utils/dtor.h" + +#include <guichan/font.hpp> + +#undef DELETE //Win32 compatibility hack + +int TextField::instances = 0; +float TextField::mAlpha = 1.0; +ImageRect TextField::skin; + +TextField::TextField(const std::string &text, bool loseFocusOnTab, + gcn::ActionListener* listener, std::string eventId): + gcn::TextField(text), + mNumeric(false) +{ + setFrameSize(2); + + mLoseFocusOnTab = loseFocusOnTab; + + if (instances == 0) + { + // Load the skin + Image *textbox = Theme::getImageFromTheme("deepbox.png"); + int gridx[4] = {0, 3, 28, 31}; + int gridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (textbox) + { + skin.grid[a] = textbox->getSubImage( + gridx[x], gridy[y], + gridx[x + 1] - gridx[x] + 1, + gridy[y + 1] - gridy[y] + 1); + if (skin.grid[a]) + skin.grid[a]->setAlpha(Client::getGuiAlpha()); + } + else + { + skin.grid[a] = 0; + } + a++; + } + } + + if (textbox) + textbox->decRef(); + } + + instances++; + + if (!eventId.empty()) + setActionEventId(eventId); + + if (listener) + addActionListener(listener); +} + +TextField::~TextField() +{ + instances--; + + if (instances == 0) + for_each(skin.grid, skin.grid + 9, dtor<Image*>()); +} + +void TextField::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (alpha != mAlpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + if (skin.grid[a]) + skin.grid[a]->setAlpha(mAlpha); + } + } +} + +void TextField::draw(gcn::Graphics *graphics) +{ + updateAlpha(); + + if (isFocused()) + { + drawCaret(graphics, + getFont()->getWidth(mText.substr(0, mCaretPosition)) - + mXScroll); + } + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->setFont(getFont()); + graphics->drawText(mText, 1 - mXScroll, 1); +} + +void TextField::drawFrame(gcn::Graphics *graphics) +{ + //updateAlpha(); -> Not useful... + + int w, h, bs; + bs = getFrameSize(); + w = getWidth() + bs * 2; + h = getHeight() + bs * 2; + + static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, skin); +} + +void TextField::setNumeric(bool numeric) +{ + mNumeric = numeric; + if (!numeric) + return; + + const char *text = mText.c_str(); + for (const char *textPtr = text; *textPtr; ++textPtr) + { + if (*textPtr < '0' || *textPtr > '9') + { + setText(mText.substr(0, textPtr - text)); + return; + } + } +} + +int TextField::getValue() const +{ + if (!mNumeric) + return 0; + + int value = atoi(mText.c_str()); + if (value < mMinimum) + return mMinimum; + + if (value > mMaximum) + return mMaximum; + + return value; +} + +void TextField::keyPressed(gcn::KeyEvent &keyEvent) +{ + int val = keyEvent.getKey().getValue(); + + if (val >= 32) + { + int l; + if (val < 128) + l = 1; // 0xxxxxxx + else if (val < 0x800) + l = 2; // 110xxxxx 10xxxxxx + else if (val < 0x10000) + l = 3; // 1110xxxx 10xxxxxx 10xxxxxx + else + l = 4; // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + + char buf[4]; + for (int i = 0; i < l; ++i) + { + buf[i] = static_cast<char>(val >> (6 * (l - i - 1))); + if (i > 0) + buf[i] = static_cast<char>((buf[i] & 63) | 128); + } + + if (l > 1) + buf[0] |= static_cast<char>(255 << (8 - l)); + + mText.insert(mCaretPosition, std::string(buf, buf + l)); + mCaretPosition += l; + } + + /* In UTF-8, 10xxxxxx is only used for inner parts of characters. So skip + them when processing key presses. */ + + switch (val) + { + case Key::LEFT: + { + while (mCaretPosition > 0) + { + --mCaretPosition; + if ((mText[mCaretPosition] & 192) != 128) + break; + } + } break; + + case Key::RIGHT: + { + unsigned sz = static_cast<unsigned>(mText.size()); + while (mCaretPosition < sz) + { + ++mCaretPosition; + if (mCaretPosition == sz || + (mText[mCaretPosition] & 192) != 128) + { + break; + } + } + } break; + + case Key::DELETE: + { + unsigned sz = static_cast<unsigned>(mText.size()); + while (mCaretPosition < sz) + { + --sz; + mText.erase(mCaretPosition, 1); + if (mCaretPosition == sz || + (mText[mCaretPosition] & 192) != 128) + { + break; + } + } + } break; + + case Key::BACKSPACE: + { + while (mCaretPosition > 0) + { + --mCaretPosition; + int v = mText[mCaretPosition]; + mText.erase(mCaretPosition, 1); + if ((v & 192) != 128) + break; + } + } break; + + case Key::ENTER: + distributeActionEvent(); + break; + + case Key::HOME: + mCaretPosition = 0; + break; + + case Key::END: + mCaretPosition = static_cast<unsigned>(mText.size()); + break; + + case Key::TAB: + if (mLoseFocusOnTab) + return; + break; + + case 22: // Control code 22, SYNCHRONOUS IDLE, sent on Ctrl+v + handlePaste(); + break; + default: + break; + } + + keyEvent.consume(); + fixScroll(); +} + +void TextField::handlePaste() +{ + std::string text = getText(); + std::string::size_type caretPos = getCaretPosition(); + + if (RetrieveBuffer(text, caretPos)) + { + setText(text); + setCaretPosition(static_cast<unsigned>(caretPos)); + } +} diff --git a/src/gui/widgets/textfield.h b/src/gui/widgets/textfield.h new file mode 100644 index 000000000..b894fdc85 --- /dev/null +++ b/src/gui/widgets/textfield.h @@ -0,0 +1,110 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TEXTFIELD_H +#define TEXTFIELD_H + +#include <guichan/widgets/textfield.hpp> + +class ImageRect; +class TextField; + +/** + * A text field. + * + * \ingroup GUI + */ +class TextField : public gcn::TextField +{ + public: + /** + * Constructor, initializes the text field with the given string. + */ + TextField(const std::string &text = "", bool loseFocusOnTab = true, + gcn::ActionListener* listener = NULL, + std::string eventId = ""); + + ~TextField(); + + /** + * Draws the text field. + */ + virtual void draw(gcn::Graphics *graphics); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the background and border. + */ + void drawFrame(gcn::Graphics *graphics); + + /** + * Determine whether the field should be numeric or not + */ + void setNumeric(bool numeric); + + /** + * Set the range on the field if it is numeric + */ + void setRange(int min, int max) + { + mMinimum = min; + mMaximum = max; + } + + /** + * Processes one keypress. + */ + void keyPressed(gcn::KeyEvent &keyEvent); + + /** + * Set the minimum value for a range + */ + void setMinimum(int min) + { mMinimum = min; } + + /** + * Set the maximum value for a range + */ + void setMaximum(int max) + { mMaximum = max; } + + /** + * Return the value for a numeric field + */ + int getValue() const; + + private: + void handlePaste(); + + static int instances; + static float mAlpha; + static ImageRect skin; + bool mNumeric; + int mMinimum; + int mMaximum; + bool mLoseFocusOnTab; +}; + +#endif diff --git a/src/gui/widgets/textpreview.cpp b/src/gui/widgets/textpreview.cpp new file mode 100644 index 000000000..bd38d8a80 --- /dev/null +++ b/src/gui/widgets/textpreview.cpp @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/textpreview.h" + +#include "client.h" +#include "configuration.h" +#include "textrenderer.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/truetypefont.h" + +#include <typeinfo> + +float TextPreview::mAlpha = 1.0; + +TextPreview::TextPreview(const std::string &text): + mText(text) +{ + mTextAlpha = false; + mFont = gui->getFont(); + mTextColor = &Theme::getThemeColor(Theme::TEXT); + mTextBGColor = NULL; + mBGColor = &Theme::getThemeColor(Theme::BACKGROUND); + mOpaque = false; +} + +void TextPreview::draw(gcn::Graphics* graphics) +{ + if (Client::getGuiAlpha() != mAlpha) + mAlpha = Client::getGuiAlpha(); + + int alpha = static_cast<int>(mAlpha * 255.0f); + + if (!mTextAlpha) + alpha = 255; + + if (mOpaque) + { + graphics->setColor(gcn::Color(static_cast<int>(mBGColor->r), + static_cast<int>(mBGColor->g), + static_cast<int>(mBGColor->b), + static_cast<int>(mAlpha * 255.0f))); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (mTextBGColor && typeid(*mFont) == typeid(TrueTypeFont)) + { + TrueTypeFont *font = static_cast<TrueTypeFont*>(mFont); + int x = font->getWidth(mText) + 1 + 2 * ((mOutline || mShadow) ? 1 :0); + int y = font->getHeight() + 1 + 2 * ((mOutline || mShadow) ? 1 : 0); + graphics->setColor(gcn::Color(static_cast<int>(mTextBGColor->r), + static_cast<int>(mTextBGColor->g), + static_cast<int>(mTextBGColor->b), + static_cast<int>(mAlpha * 255.0f))); + graphics->fillRectangle(gcn::Rectangle(1, 1, x, y)); + } + + TextRenderer::renderText(graphics, mText, 2, 2, gcn::Graphics::LEFT, + gcn::Color(mTextColor->r, mTextColor->g, + mTextColor->b, alpha), + mFont, mOutline, mShadow); +} diff --git a/src/gui/widgets/textpreview.h b/src/gui/widgets/textpreview.h new file mode 100644 index 000000000..a34ab3853 --- /dev/null +++ b/src/gui/widgets/textpreview.h @@ -0,0 +1,130 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TEXTPREVIEW_H +#define TEXTPREVIEW_H + +#include <guichan/color.hpp> +#include <guichan/font.hpp> +#include <guichan/widget.hpp> + +/** + * Preview widget for particle colors, etc. + */ +class TextPreview : public gcn::Widget +{ + public: + TextPreview(const std::string &text); + + /** + * Sets the color the text is printed in. + * + * @param color the color to set + */ + inline void setTextColor(const gcn::Color *color) + { mTextColor = color; } + + /** + * Sets the text to use the set alpha value. + * + * @param alpha whether to use alpha values for the text or not + */ + inline void useTextAlpha(bool alpha) + { mTextAlpha = alpha; } + + /** + * Sets the color the text background is drawn in. This is only the + * rectangle directly behind the text, not to full widget. + * + * @param color the color to set + */ + inline void setTextBGColor(const gcn::Color *color) + { mTextBGColor = color; } + + /** + * Sets the background color of the widget. + * + * @param color the color to set + */ + inline void setBGColor(const gcn::Color *color) + { mBGColor = color; } + + /** + * Sets the font to render the text in. + * + * @param font the font to use. + */ + inline void setFont(gcn::Font *font) + { mFont = font; } + + /** + * Sets whether to use a shadow while rendering. + * + * @param shadow true, if a shadow is wanted, false else + */ + inline void setShadow(bool shadow) + { mShadow = shadow; } + + /** + * Sets whether to use an outline while rendering. + * + * @param outline true, if an outline is wanted, false else + */ + inline void setOutline(bool outline) + { mOutline = outline; } + + /** + * Widget's draw method. Does the actual job. + * + * @param graphics graphics to draw into + */ + void draw(gcn::Graphics *graphics); + + /** + * Set opacity for this widget (whether or not to show the background + * color) + * + * @param opaque Whether the widget should be opaque or not + */ + void setOpaque(bool opaque) + { mOpaque = opaque; } + + /** + * Gets opacity for this widget (whether or not the background color + * is shown below the widget) + */ + bool isOpaque() const + { return mOpaque; } + + private: + gcn::Font *mFont; + std::string mText; + const gcn::Color *mTextColor; + const gcn::Color *mBGColor; + const gcn::Color *mTextBGColor; + static float mAlpha; + bool mTextAlpha; + bool mOpaque; + bool mShadow; + bool mOutline; +}; + +#endif diff --git a/src/gui/widgets/tradetab.cpp b/src/gui/widgets/tradetab.cpp new file mode 100644 index 000000000..fb4a57fd5 --- /dev/null +++ b/src/gui/widgets/tradetab.cpp @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/tradetab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "net/net.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +TradeTab::TradeTab() : + ChatTab(_("Trade")) +{ +} + +TradeTab::~TradeTab() +{ +} + +void TradeTab::handleInput(const std::string &msg) +{ + std::string str = "\302\202" + msg; + ChatTab::handleInput(str); +} + +void TradeTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log(std::string("#Trade"), std::string(msg)); +} diff --git a/src/gui/widgets/tradetab.h b/src/gui/widgets/tradetab.h new file mode 100644 index 000000000..fceeb1e40 --- /dev/null +++ b/src/gui/widgets/tradetab.h @@ -0,0 +1,50 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRADETAB_H +#define TRADETAB_H + +#include "gui/widgets/chattab.h" + +/** + * A tab for a party chat channel. + */ +class TradeTab : public ChatTab +{ + public: + TradeTab(); + + ~TradeTab(); + + int getType() const + { return ChatTab::TAB_TRADE; } + + void saveToLogFile(std::string &msg); + + protected: + void handleInput(const std::string &msg); +}; + +extern TradeTab *tradeChatTab; +#endif + + + diff --git a/src/gui/widgets/vertcontainer.cpp b/src/gui/widgets/vertcontainer.cpp new file mode 100644 index 000000000..4dac2a617 --- /dev/null +++ b/src/gui/widgets/vertcontainer.cpp @@ -0,0 +1,53 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/vertcontainer.h" + +VertContainer::VertContainer(int spacing): + mSpacing(spacing), + mCount(0) +{ + addWidgetListener(this); +} + +void VertContainer::add(gcn::Widget *widget) +{ + if (!widget) + return; + + Container::add(widget); + widget->setPosition(0, mCount * mSpacing); + widget->setSize(getWidth(), mSpacing); + mCount++; + setHeight(mCount * mSpacing); +} + +void VertContainer::clear() +{ + Container::clear(); + + mCount = 0; +} + +void VertContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + for (WidgetListIterator it = mWidgets.begin(); it != mWidgets.end(); it++) + (*it)->setWidth(getWidth()); +} diff --git a/src/gui/widgets/vertcontainer.h b/src/gui/widgets/vertcontainer.h new file mode 100644 index 000000000..fe62d8c0e --- /dev/null +++ b/src/gui/widgets/vertcontainer.h @@ -0,0 +1,52 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_VERTCONTAINER_H +#define GUI_VERTCONTAINER_H + +#include "gui/widgets/container.h" + +#include <guichan/widgetlistener.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * A widget container. + * + * This container places it's contents veritcally. + */ +class VertContainer : public Container, public gcn::WidgetListener +{ + public: + VertContainer(int spacing); + virtual void add(gcn::Widget *widget); + virtual void clear(); + void widgetResized(const gcn::Event &event); + + private: + int mSpacing; + int mCount; +}; + +#endif diff --git a/src/gui/widgets/whispertab.cpp b/src/gui/widgets/whispertab.cpp new file mode 100644 index 000000000..f0c347b59 --- /dev/null +++ b/src/gui/widgets/whispertab.cpp @@ -0,0 +1,164 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "whispertab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +WhisperTab::WhisperTab(const std::string &nick) : + ChatTab(nick), + mNick(nick) +{ + setTabColor(&Theme::getThemeColor(Theme::WHISPER)); +} + +WhisperTab::~WhisperTab() +{ + if (chatWindow) + chatWindow->removeWhisper(mNick); +} + +void WhisperTab::handleInput(const std::string &msg) +{ +// if (msg.empty()) +// { +// chatLog(_("Cannot send empty chat!"), BY_SERVER, false); +// return; +// } + + if (chatWindow) + { + Net::getChatHandler()->privateMessage(mNick, + chatWindow->doReplace(msg)); + } + else + { + Net::getChatHandler()->privateMessage(mNick, msg); + } + + if (player_node) + chatLog(player_node->getName(), msg); + else + chatLog("?", msg); +} + +void WhisperTab::handleCommand(const std::string &msg) +{ + if (msg == "close") + { + delete this; + return; + } + + std::string::size_type pos = msg.find(' '); + std::string type(msg, 0, pos); + std::string args(msg, pos == std::string::npos + ? msg.size() : pos + 1); + + if (type == "me") + { + std::string str = strprintf("*%s*", args.c_str()); + Net::getChatHandler()->privateMessage(mNick, str); + if (player_node) + chatLog(player_node->getName(), str); + else + chatLog("?", str); + } + else + { + ChatTab::handleCommand(msg); + } +} + +void WhisperTab::showHelp() +{ + chatLog(_("/ignore > Ignore the other player")); + chatLog(_("/unignore > Stop ignoring the other player")); + chatLog(_("/close > Close the whisper tab")); +} + +bool WhisperTab::handleCommand(const std::string &type, + const std::string &args) +{ + if (type == "help") + { + if (args == "close") + { + chatLog(_("Command: /close")); + chatLog(_("This command closes the current whisper tab.")); + } + else if (args == "ignore") + { + chatLog(_("Command: /ignore")); + chatLog(_("This command ignores the other player regardless of " + "current relations.")); + } + else if (args == "unignore") + { + chatLog(_("Command: /unignore <player>")); + chatLog(_("This command stops ignoring the other player if they " + "are being ignored.")); + } + else + { + return false; + } + } + else if (type == "close") + { + delete this; + if (chatWindow) + chatWindow->defaultTab(); + } + else if (type == "ignore") + { + if (commandHandler) + commandHandler->handleIgnore(mNick, this); + } + else if (type == "unignore") + { + if (commandHandler) + commandHandler->handleUnignore(mNick, this); + } + else + { + return false; + } + + return true; +} + +void WhisperTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log(getNick(), msg); +} diff --git a/src/gui/widgets/whispertab.h b/src/gui/widgets/whispertab.h new file mode 100644 index 000000000..89e70695b --- /dev/null +++ b/src/gui/widgets/whispertab.h @@ -0,0 +1,67 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef WHISPERTAB_H +#define WHISPERTAB_H + +#include "chattab.h" + +class Channel; + +/** + * A tab for whispers from a single player. + */ +class WhisperTab : public ChatTab +{ + public: + const std::string &getNick() const { return mNick; } + + void showHelp(); + + bool handleCommand(const std::string &type, + const std::string &args); + + int getType() const + { return ChatTab::TAB_WHISPER; } + + void saveToLogFile(std::string &msg); + + protected: + friend class ChatWindow; + + /** + * Constructor. + * + * @param nick the name of the player this tab is whispering to + */ + WhisperTab(const std::string &nick); + + ~WhisperTab(); + + void handleInput(const std::string &msg); + + void handleCommand(const std::string &msg); + + private: + std::string mNick; +}; + +#endif // CHANNELTAB_H diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp new file mode 100644 index 000000000..6564a8d3f --- /dev/null +++ b/src/gui/widgets/window.cpp @@ -0,0 +1,924 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/window.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/layout.h" +#include "gui/widgets/resizegrip.h" +#include "gui/widgets/windowcontainer.h" + +#include "resources/image.h" + +#include <guichan/exception.hpp> +#include <guichan/focushandler.hpp> + +int Window::instances = 0; +int Window::mouseResize = 0; + +Window::Window(const std::string &caption, bool modal, Window *parent, + const std::string &skin): + gcn::Window(caption), + mGrip(0), + mParent(parent), + mLayout(NULL), + mWindowName("window"), + mShowTitle(true), + mModal(modal), + mCloseButton(false), + mDefaultVisible(false), + mSaveVisible(false), + mStickyButton(false), + mSticky(false), + mMinWinWidth(100), + mMinWinHeight(40), + mMaxWinWidth(graphics->getWidth()), + mMaxWinHeight(graphics->getHeight()) +{ + logger->log("Window::Window(\"%s\")", caption.c_str()); + + if (!windowContainer) + throw GCN_EXCEPTION("Window::Window(): no windowContainer set"); + + instances++; + + setFrameSize(0); + setPadding(3); + setTitleBarHeight(20); + + // Loads the skin + mSkin = Theme::instance()->load(skin); + + // Add this window to the window container + windowContainer->add(this); + + if (mModal) + { + gui->setCursorType(Gui::CURSOR_POINTER); + requestModalFocus(); + } + + // Windows are invisible by default + setVisible(false); + + addWidgetListener(this); +} + +Window::~Window() +{ + logger->log("Window::~Window(\"%s\")", getCaption().c_str()); + + saveWindowState(); + + delete mLayout; + mLayout = 0; + + while (!mWidgets.empty()) + delete mWidgets.front(); + +// need mWidgets.clean ? + + removeWidgetListener(this); + + instances--; + + if (mSkin) + mSkin->instances--; +} + +void Window::setWindowContainer(WindowContainer *wc) +{ + windowContainer = wc; +} + +void Window::draw(gcn::Graphics *graphics) +{ + if (!mSkin) + return; + + Graphics *g = static_cast<Graphics*>(graphics); + + g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); + + // Draw title + if (mShowTitle) + { + g->setColor(Theme::getThemeColor(Theme::TEXT)); + g->setFont(getFont()); + g->drawText(getCaption(), 7, 5, gcn::Graphics::LEFT); + } + + // Draw Close Button + if (mCloseButton && mSkin->getCloseImage()) + { + g->drawImage(mSkin->getCloseImage(), + getWidth() - mSkin->getCloseImage()->getWidth() - getPadding(), + getPadding()); + } + + // Draw Sticky Button + if (mStickyButton) + { + Image *button = mSkin->getStickyImage(mSticky); + if (button) + { + int x = getWidth() - button->getWidth() - getPadding(); + if (mCloseButton && mSkin->getCloseImage()) + x -= mSkin->getCloseImage()->getWidth(); + + g->drawImage(button, x, getPadding()); + } + } + + drawChildren(graphics); +} + +void Window::setContentSize(int width, int height) +{ + width = width + 2 * getPadding(); + height = height + getPadding() + getTitleBarHeight(); + + if (getMinWidth() > width) + width = getMinWidth(); + else if (getMaxWidth() < width) + width = getMaxWidth(); + if (getMinHeight() > height) + height = getMinHeight(); + else if (getMaxHeight() < height) + height = getMaxHeight(); + + setSize(width, height); +} + +void Window::setLocationRelativeTo(gcn::Widget *widget) +{ + if (!widget) + return; + + int wx, wy; + int x, y; + + widget->getAbsolutePosition(wx, wy); + getAbsolutePosition(x, y); + + setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x), + getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y)); +} + +void Window::setLocationHorisontallyRelativeTo(gcn::Widget *widget) +{ + if (!widget) + return; + + int wx, wy; + int x, y; + + widget->getAbsolutePosition(wx, wy); + getAbsolutePosition(x, y); + + setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x), 0); +} + +void Window::setLocationRelativeTo(ImageRect::ImagePosition position, + int offsetX, int offsetY) +{ + if (position == ImageRect::UPPER_LEFT) + { + } + else if (position == ImageRect::UPPER_CENTER) + { + offsetX += (graphics->getWidth() - getWidth()) / 2; + } + else if (position == ImageRect::UPPER_RIGHT) + { + offsetX += graphics->getWidth() - getWidth(); + } + else if (position == ImageRect::LEFT) + { + offsetY += (graphics->getHeight() - getHeight()) / 2; + } + else if (position == ImageRect::CENTER) + { + offsetX += (graphics->getWidth() - getWidth()) / 2; + offsetY += (graphics->getHeight() - getHeight()) / 2; + } + else if (position == ImageRect::RIGHT) + { + offsetX += graphics->getWidth() - getWidth(); + offsetY += (graphics->getHeight() - getHeight()) / 2; + } + else if (position == ImageRect::LOWER_LEFT) + { + offsetY += graphics->getHeight() - getHeight(); + } + else if (position == ImageRect::LOWER_CENTER) + { + offsetX += (graphics->getWidth() - getWidth()) / 2; + offsetY += graphics->getHeight() - getHeight(); + } + else if (position == ImageRect::LOWER_RIGHT) + { + offsetX += graphics->getWidth() - getWidth(); + offsetY += graphics->getHeight() - getHeight(); + } + + setPosition(offsetX, offsetY); +} + +void Window::setMinWidth(int width) +{ + mMinWinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth(); +} + +void Window::setMinHeight(int height) +{ + mMinWinHeight = height > mSkin->getMinHeight() ? + height : mSkin->getMinHeight(); +} + +void Window::setMaxWidth(int width) +{ + mMaxWinWidth = width; +} + +void Window::setMaxHeight(int height) +{ + mMaxWinHeight = height; +} + +void Window::setResizable(bool r) +{ + if (static_cast<bool>(mGrip) == r) + return; + + if (r) + { + mGrip = new ResizeGrip; + mGrip->setX(getWidth() - mGrip->getWidth() - getChildrenArea().x); + mGrip->setY(getHeight() - mGrip->getHeight() - getChildrenArea().y); + add(mGrip); + } + else + { + remove(mGrip); + delete mGrip; + mGrip = 0; + } +} + +void Window::widgetResized(const gcn::Event &event _UNUSED_) +{ + const gcn::Rectangle area = getChildrenArea(); + + if (mGrip) + { + mGrip->setPosition(getWidth() - mGrip->getWidth() - area.x, + getHeight() - mGrip->getHeight() - area.y); + } + + if (mLayout) + { + int w = area.width; + int h = area.height; + mLayout->reflow(w, h); + } +} + +void Window::widgetHidden(const gcn::Event &event _UNUSED_) +{ + if (gui) + gui->setCursorType(Gui::CURSOR_POINTER); + + WidgetListIterator it; + + if (!mFocusHandler) + return; + + for (it = mWidgets.begin(); it != mWidgets.end(); it++) + { + if (mFocusHandler->isFocused(*it)) + mFocusHandler->focusNone(); + } +} + +void Window::setCloseButton(bool flag) +{ + mCloseButton = flag; +} + +bool Window::isResizable() const +{ + return mGrip; +} + +void Window::setStickyButton(bool flag) +{ + mStickyButton = flag; +} + +void Window::setSticky(bool sticky) +{ + mSticky = sticky; +} + +void Window::setVisible(bool visible) +{ + setVisible(visible, false); +} + +void Window::setVisible(bool visible, bool forceSticky) +{ + if (visible == isVisible()) + return; // Nothing to do + + // Check if the window is off screen... + if (visible) + checkIfIsOffScreen(); + + gcn::Window::setVisible((!forceSticky && isSticky()) || visible); +} + +void Window::scheduleDelete() +{ + windowContainer->scheduleDelete(this); +} + +void Window::mousePressed(gcn::MouseEvent &event) +{ + // Let Guichan move window to top and figure out title bar drag + gcn::Window::mousePressed(event); + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + const int x = event.getX(); + const int y = event.getY(); + + // Handle close button + if (mCloseButton) + { + Image *img = mSkin->getCloseImage(); + if (img) + { + gcn::Rectangle closeButtonRect( + getWidth() - img->getWidth() + - getPadding(), getPadding(), + img->getWidth(), img->getHeight()); + + if (closeButtonRect.isPointInRect(x, y)) + { + mouseResize = 0; + mMoved = 0; + close(); + return; + } + } + } + + // Handle sticky button + if (mStickyButton) + { + Image *button = mSkin->getStickyImage(mSticky); + if (button) + { + int rx = getWidth() - button->getWidth() - getPadding(); + if (mCloseButton) + { + Image *img = mSkin->getCloseImage(); + if (img) + rx -= img->getWidth(); + } + gcn::Rectangle stickyButtonRect(rx, getPadding(), + button->getWidth(), button->getHeight()); + if (stickyButtonRect.isPointInRect(x, y)) + { + setSticky(!isSticky()); + mouseResize = 0; + mMoved = 0; + return; + } + } + } + + // Handle window resizing + mouseResize = getResizeHandles(event); + mMoved = !mouseResize; + } +} + +void Window::close() +{ + setVisible(false); +} + +void Window::mouseReleased(gcn::MouseEvent &event _UNUSED_) +{ + if (mGrip && mouseResize) + { + mouseResize = 0; + if (gui) + gui->setCursorType(Gui::CURSOR_POINTER); + } + + // This should be the responsibility of Guichan (and is from 0.8.0 on) + mMoved = false; +} + +void Window::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mGrip && !mouseResize && gui) + gui->setCursorType(Gui::CURSOR_POINTER); +} + +void Window::mouseMoved(gcn::MouseEvent &event) +{ + if (!gui) + return; + + int resizeHandles = getResizeHandles(event); + + // Changes the custom mouse cursor based on it's current position. + switch (resizeHandles) + { + case BOTTOM | RIGHT: + case TOP | LEFT: + gui->setCursorType(Gui::CURSOR_RESIZE_DOWN_RIGHT); + break; + case TOP | RIGHT: + case BOTTOM | LEFT: + gui->setCursorType(Gui::CURSOR_RESIZE_DOWN_LEFT); + break; + case BOTTOM: + case TOP: + gui->setCursorType(Gui::CURSOR_RESIZE_DOWN); + break; + case RIGHT: + case LEFT: + gui->setCursorType(Gui::CURSOR_RESIZE_ACROSS); + break; + default: + gui->setCursorType(Gui::CURSOR_POINTER); + } + + if (viewport) + viewport->hideBeingPopup(); +} + +void Window::mouseDragged(gcn::MouseEvent &event) +{ + // Let Guichan handle title bar drag + gcn::Window::mouseDragged(event); + + // Keep guichan window inside screen when it may be moved + if (isMovable() && mMoved) + { + int newX = std::max(0, getX()); + int newY = std::max(0, getY()); + newX = std::min(graphics->getWidth() - getWidth(), newX); + newY = std::min(graphics->getHeight() - getHeight(), newY); + setPosition(newX, newY); + } + + if (mouseResize && !mMoved) + { + const int dx = event.getX() - mDragOffsetX; + const int dy = event.getY() - mDragOffsetY; + gcn::Rectangle newDim = getDimension(); + + if (mouseResize & (TOP | BOTTOM)) + { + int newHeight = newDim.height + ((mouseResize & TOP) ? -dy : dy); + newDim.height = std::min(mMaxWinHeight, + std::max(mMinWinHeight, newHeight)); + + if (mouseResize & TOP) + newDim.y -= newDim.height - getHeight(); + } + + if (mouseResize & (LEFT | RIGHT)) + { + int newWidth = newDim.width + ((mouseResize & LEFT) ? -dx : dx); + newDim.width = std::min(mMaxWinWidth, + std::max(mMinWinWidth, newWidth)); + + if (mouseResize & LEFT) + newDim.x -= newDim.width - getWidth(); + } + + // Keep guichan window inside screen (supports resizing any side) + if (newDim.x < 0) + { + newDim.width += newDim.x; + newDim.x = 0; + } + if (newDim.y < 0) + { + newDim.height += newDim.y; + newDim.y = 0; + } + if (newDim.x + newDim.width > graphics->getWidth()) + { + newDim.width = graphics->getWidth() - newDim.x; + } + if (newDim.y + newDim.height > graphics->getHeight()) + { + newDim.height = graphics->getHeight() - newDim.y; + } + + // Update mouse offset when dragging bottom or right border + if (mouseResize & BOTTOM) + mDragOffsetY += newDim.height - getHeight(); + + if (mouseResize & RIGHT) + mDragOffsetX += newDim.width - getWidth(); + + // Set the new window and content dimensions + setDimension(newDim); + } +} + +void Window::setModal(bool modal) +{ + if (mModal != modal) + { + mModal = modal; + if (mModal) + { + if (gui) + gui->setCursorType(Gui::CURSOR_POINTER); + requestModalFocus(); + } + else + { + releaseModalFocus(); + } + } +} + +void Window::loadWindowState() +{ + const std::string &name = mWindowName; + assert(!name.empty()); + + setPosition(config.getValueInt(name + "WinX", mDefaultX), + config.getValueInt(name + "WinY", mDefaultY)); + + if (mSaveVisible) + { + setVisible(config.getValueBool(name + + "Visible", mDefaultVisible)); + } + + if (mStickyButton) + { + setSticky(config.getValueBool(name + + "Sticky", isSticky())); + } + + if (mGrip) + { + int width = config.getValueInt(name + "WinWidth", mDefaultWidth); + int height = config.getValueInt(name + "WinHeight", mDefaultHeight); + + if (getMinWidth() > width) + width = getMinWidth(); + else if (getMaxWidth() < width) + width = getMaxWidth(); + if (getMinHeight() > height) + height = getMinHeight(); + else if (getMaxHeight() < height) + height = getMaxHeight(); + + setSize(width, height); + } + else + { + setSize(mDefaultWidth, mDefaultHeight); + } + + // Check if the window is off screen... + checkIfIsOffScreen(); +} + +void Window::saveWindowState() +{ + // Saving X, Y and Width and Height for resizables in the config + if (!mWindowName.empty() && mWindowName != "window") + { + config.setValue(mWindowName + "WinX", getX()); + config.setValue(mWindowName + "WinY", getY()); + + if (mSaveVisible) + config.setValue(mWindowName + "Visible", isVisible()); + + if (mStickyButton) + config.setValue(mWindowName + "Sticky", isSticky()); + + if (mGrip) + { + if (getMinWidth() > getWidth()) + setWidth(getMinWidth()); + else if (getMaxWidth() < getWidth()) + setWidth(getMaxWidth()); + if (getMinHeight() > getHeight()) + setHeight(getMinHeight()); + else if (getMaxHeight() < getHeight()) + setHeight(getMaxHeight()); + + config.setValue(mWindowName + "WinWidth", getWidth()); + config.setValue(mWindowName + "WinHeight", getHeight()); + } + } +} + +void Window::setDefaultSize(int defaultX, int defaultY, + int defaultWidth, int defaultHeight) +{ + if (getMinWidth() > defaultWidth) + defaultWidth = getMinWidth(); + else if (getMaxWidth() < defaultWidth) + defaultWidth = getMaxWidth(); + if (getMinHeight() > defaultHeight) + defaultHeight = getMinHeight(); + else if (getMaxHeight() < defaultHeight) + defaultHeight = getMaxHeight(); + + mDefaultX = defaultX; + mDefaultY = defaultY; + mDefaultWidth = defaultWidth; + mDefaultHeight = defaultHeight; +} + +void Window::setDefaultSize() +{ + mDefaultX = getX(); + mDefaultY = getY(); + mDefaultWidth = getWidth(); + mDefaultHeight = getHeight(); +} + +void Window::setDefaultSize(int defaultWidth, int defaultHeight, + ImageRect::ImagePosition position, + int offsetX, int offsetY) +{ + int x = 0, y = 0; + + if (position == ImageRect::UPPER_LEFT) + { + } + else if (position == ImageRect::UPPER_CENTER) + { + x = (graphics->getWidth() - defaultWidth) / 2; + } + else if (position == ImageRect::UPPER_RIGHT) + { + x = graphics->getWidth() - defaultWidth; + } + else if (position == ImageRect::LEFT) + { + y = (graphics->getHeight() - defaultHeight) / 2; + } + else if (position == ImageRect::CENTER) + { + x = (graphics->getWidth() - defaultWidth) / 2; + y = (graphics->getHeight() - defaultHeight) / 2; + } + else if (position == ImageRect::RIGHT) + { + x = graphics->getWidth() - defaultWidth; + y = (graphics->getHeight() - defaultHeight) / 2; + } + else if (position == ImageRect::LOWER_LEFT) + { + y = graphics->getHeight() - defaultHeight; + } + else if (position == ImageRect::LOWER_CENTER) + { + x = (graphics->getWidth() - defaultWidth) / 2; + y = graphics->getHeight() - defaultHeight; + } + else if (position == ImageRect::LOWER_RIGHT) + { + x = graphics->getWidth() - defaultWidth; + y = graphics->getHeight() - defaultHeight; + } + + mDefaultX = x - offsetX; + mDefaultY = y - offsetY; + mDefaultWidth = defaultWidth; + mDefaultHeight = defaultHeight; +} + +void Window::resetToDefaultSize() +{ + setPosition(mDefaultX, mDefaultY); + setSize(mDefaultWidth, mDefaultHeight); + saveWindowState(); +} + +int Window::getResizeHandles(gcn::MouseEvent &event) +{ + int resizeHandles = 0; + const int y = event.getY(); + + if (mGrip && (y > static_cast<int>(mTitleBarHeight) + || (y < (int)getPadding() && mTitleBarHeight > getPadding()))) + { + const int x = event.getX(); + + if (!getWindowArea().isPointInRect(x, y) && event.getSource() == this) + { + resizeHandles |= (x > getWidth() - resizeBorderWidth) ? RIGHT : + (x < resizeBorderWidth) ? LEFT : 0; + resizeHandles |= (y > getHeight() - resizeBorderWidth) ? BOTTOM : + (y < resizeBorderWidth) ? TOP : 0; + } + + if (event.getSource() == mGrip) + { + mDragOffsetX = x; + mDragOffsetY = y; + resizeHandles |= BOTTOM | RIGHT; + } + } + + return resizeHandles; +} + +bool Window::isResizeAllowed(gcn::MouseEvent &event) +{ + const int y = event.getY(); + + if (mGrip && (y > static_cast<int>(mTitleBarHeight) + || y < (int)getPadding())) + { + const int x = event.getX(); + + if (!getWindowArea().isPointInRect(x, y) && event.getSource() == this) + return true; + + if (event.getSource() == mGrip) + return true; + } + + return false; +} + +int Window::getGuiAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + return static_cast<int>(alpha * 255.0f); +} + +Layout &Window::getLayout() +{ + if (!mLayout) + mLayout = new Layout; + return *mLayout; +} + +void Window::clearLayout() +{ + clear(); + + // Restore the resize grip + if (mGrip) + add(mGrip); + + // Recreate layout instance when one is present + if (mLayout) + { + delete mLayout; + mLayout = new Layout; + } +} + +LayoutCell &Window::place(int x, int y, gcn::Widget *wg, int w, int h) +{ + add(wg); + return getLayout().place(wg, x, y, w, h); +} + +ContainerPlacer Window::getPlacer(int x, int y) +{ + return ContainerPlacer(this, &getLayout().at(x, y)); +} + +void Window::reflowLayout(int w, int h) +{ + if (!mLayout) + return; + + mLayout->reflow(w, h); + delete mLayout; + mLayout = 0; + setContentSize(w, h); +} + +void Window::redraw() +{ + if (mLayout) + { + const gcn::Rectangle area = getChildrenArea(); + int w = area.width; + int h = area.height; + mLayout->reflow(w, h); + } +} + +void Window::center() +{ + setLocationRelativeTo(getParent()); +} + +void Window::centerHorisontally() +{ + setLocationHorisontallyRelativeTo(getParent()); +} + +void Window::checkIfIsOffScreen(bool partially, bool entirely) +{ + // Move the window onto screen if it has become off screen + // For instance, because of resolution change... + + // First of all, don't deal when a window hasn't got + // any size initialized yet... + if (getWidth() == 0 && getHeight() == 0) + return; + + // Made partially the default behaviour + if (!partially && !entirely) + partially = true; + + // Keep guichan window inside screen (supports resizing any side) + + gcn::Rectangle winDimension = getDimension(); + + if (winDimension.x < 0) + { + winDimension.width += winDimension.x; + winDimension.x = 0; + } + if (winDimension.y < 0) + { + winDimension.height += winDimension.y; + winDimension.y = 0; + } + + // Look if the window is partially off-screen limits... + if (partially) + { + if (winDimension.x + winDimension.width > graphics->getWidth()) + winDimension.x = graphics->getWidth() - winDimension.width; + + if (winDimension.y + winDimension.height > graphics->getHeight()) + winDimension.y = graphics->getHeight() - winDimension.height; + + setDimension(winDimension); + return; + } + + if (entirely) + { + if (winDimension.x > graphics->getWidth()) + winDimension.x = graphics->getWidth() - winDimension.width; + + if (winDimension.y > graphics->getHeight()) + winDimension.y = graphics->getHeight() - winDimension.height; + } + setDimension(winDimension); +} + +gcn::Rectangle Window::getWindowArea() +{ + return gcn::Rectangle(getPadding(), + getPadding(), + getWidth() - getPadding() * 2, + getHeight() - getPadding() * 2); +}
\ No newline at end of file diff --git a/src/gui/widgets/window.h b/src/gui/widgets/window.h new file mode 100644 index 000000000..09e15d3f4 --- /dev/null +++ b/src/gui/widgets/window.h @@ -0,0 +1,441 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef WINDOW_H +#define WINDOW_H + +#include "graphics.h" +#include "guichanfwd.h" + +#include <guichan/widgetlistener.hpp> + +#include <guichan/widgets/window.hpp> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ContainerPlacer; +class Layout; +class LayoutCell; +class ResizeGrip; +class Skin; +class WindowContainer; + +/** + * A window. This window can be dragged around and has a title bar. Windows are + * invisible by default. + * + * \ingroup GUI + */ +class Window : public gcn::Window, gcn::WidgetListener +{ + public: + /** + * Constructor. Initializes the title to the given text and hooks + * itself into the window container. + * + * @param caption The initial window title, "Window" by default. + * @param modal Block input to other windows. + * @param parent The parent window. This is the window standing above + * this one in the window hiearchy. When reordering, + * a window will never go below its parent window. + * @param skin The location where the window's skin XML can be found. + */ + Window(const std::string &caption = "Window", bool modal = false, + Window *parent = NULL, const std::string &skin = "window.xml"); + + /** + * Destructor. Deletes all the added widgets. + */ + ~Window(); + + /** + * Sets the window container to be used by new windows. + */ + static void setWindowContainer(WindowContainer *windowContainer); + + /** + * Draws the window. + */ + void draw(gcn::Graphics *graphics); + + /** + * Sets the size of this window. + */ + void setContentSize(int width, int height); + + /** + * Sets the location relative to the given widget. + */ + void setLocationRelativeTo(gcn::Widget *widget); + + /** + * Sets the location relative to the given widget (only horisontally) + */ + void setLocationHorisontallyRelativeTo(gcn::Widget *widget); + + /** + * Sets the location relative to the given enumerated position. + */ + void setLocationRelativeTo(ImageRect::ImagePosition position, + int offsetX = 0, int offsetY = 0); + + /** + * Sets whether or not the window can be resized. + */ + void setResizable(bool resize); + + void redraw(); + + /** + * Called whenever the widget changes size. + */ + void widgetResized(const gcn::Event &event); + + /** + * Called whenever the widget is hidden. + */ + virtual void widgetHidden(const gcn::Event& event); + + /** + * Sets whether or not the window has a close button. + */ + void setCloseButton(bool flag); + + /** + * Returns whether the window can be resized. + */ + bool isResizable() const; + + /** + * Sets the minimum width of the window. + */ + void setMinWidth(int width); + + int getMinWidth() const + { return mMinWinWidth; } + + /** + * Sets the minimum height of the window. + */ + void setMinHeight(int height); + + int getMinHeight() const + { return mMinWinHeight; } + + /** + * Sets the maximum width of the window. + */ + void setMaxWidth(int width); + + int getMaxWidth() const + { return mMaxWinWidth; } + + /** + * Sets the minimum height of the window. + */ + void setMaxHeight(int height); + + int getMaxHeight() const + { return mMaxWinHeight; } + + /** + * Sets flag to show a title or not. + */ + void setShowTitle(bool flag) + { mShowTitle = flag; } + + /** + * Sets whether or not the window has a sticky button. + */ + void setStickyButton(bool flag); + + /** + * Sets whether the window is sticky. A sticky window will not have + * its visibility set to false on a general setVisible(false) call. + * Use this to set the default before you call loadWindowState(). + */ + void setSticky(bool sticky); + + /** + * Returns whether the window is sticky. + */ + bool isSticky() const + { return mSticky; } + + /** + * Overloads window setVisible by Guichan to allow sticky window + * handling. + */ + virtual void setVisible(bool visible); + + /** + * Overloads window setVisible by Guichan to allow sticky window + * handling, or not, if you force the sticky state. + */ + void setVisible(bool visible, bool forceSticky); + + /** + * Returns whether the window is visible by default. + */ + bool isDefaultVisible() const + { return mDefaultVisible; } + + /** + * Sets whether the window is visible by default. + */ + void setDefaultVisible(bool save) + { mDefaultVisible = save; } + + /** + * Returns whether the window will save it's visibility. + */ + bool willSaveVisible() const + { return mSaveVisible; } + + /** + * Sets whether the window will save it's visibility. + */ + void setSaveVisible(bool save) + { mSaveVisible = save; } + + /** + * Returns the parent window. + * + * @return The parent window or <code>NULL</code> if there is none. + */ + Window *getParentWindow() const + { return mParent; } + + /** + * Schedule this window for deletion. It will be deleted at the start + * of the next logic update. + */ + void scheduleDelete(); + + /** + * Starts window resizing when appropriate. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Implements window resizing and makes sure the window is not + * dragged/resized outside of the screen. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Implements custom cursor image changing context, based on mouse + * relative position. + */ + void mouseMoved(gcn::MouseEvent &event); + + /** + * When the mouse button has been let go, this ensures that the mouse + * custom cursor is restored back to it's standard image. + */ + void mouseReleased(gcn::MouseEvent &event); + + /** + * When the mouse leaves the window this ensures that the custom cursor + * is restored back to it's standard image. + */ + void mouseExited(gcn::MouseEvent &event); + + /** + * Sets the name of the window. This is not the window title. + */ + void setWindowName(const std::string &name) + { mWindowName = name; } + + /** + * Returns the name of the window. This is not the window title. + */ + const std::string &getWindowName() const + { return mWindowName; } + + /** + * Reads the position (and the size for resizable windows) in the + * configuration based on the given string. + * Uses the default values when config values are missing. + * Don't forget to set these default values and resizable before + * calling this function. + */ + void loadWindowState(); + + /** + * Saves the window state so that when the window is reloaded, it'll + * maintain its previous state and location. + */ + void saveWindowState(); + + /** + * Set the default win pos and size. + * (which can be different of the actual ones.) + */ + void setDefaultSize(int defaultX, int defaultY, + int defaultWidth, int defaultHeight); + + /** + * Set the default win pos and size to the current ones. + */ + void setDefaultSize(); + + /** + * Set the default win pos and size. + * (which can be different of the actual ones.) + * This version of setDefaultSize sets the window's position based + * on a relative enumerated position, rather than a coordinate position. + */ + void setDefaultSize(int defaultWidth, int defaultHeight, + ImageRect::ImagePosition position, + int offsetx = 0, int offsetY = 0); + + /** + * Reset the win pos and size to default. Don't forget to set defaults + * first. + */ + virtual void resetToDefaultSize(); + + /** + * Gets the layout handler for this window. + */ + Layout &getLayout(); + + /** + * Clears the window's layout (useful for redesigning the window). Does + * not delete the widgets! + */ + void clearLayout(); + + /** + * Computes the position of the widgets according to the current + * layout. Resizes the window so that the layout fits. Deletes the + * layout. + * @param w if non-zero, force the window to this width. + * @param h if non-zero, force the window to this height. + * @note This function is meant to be called with fixed-size windows. + */ + void reflowLayout(int w = 0, int h = 0); + + /** + * Adds a widget to the window and sets it at given cell. + */ + LayoutCell &place(int x, int y, gcn::Widget *, int w = 1, int h = 1); + + /** + * Returns a proxy for adding widgets in an inner table of the layout. + */ + ContainerPlacer getPlacer(int x, int y); + + /** + * Positions the window in the center of it's parent. + */ + void center(); + + /** + * Positions the window in the horisontal center of it's parent. + */ + void centerHorisontally(); + + /** + * Overrideable functionality for when the window is to close. This + * allows for class implementations to clean up or do certain actions + * on window close they couldn't do otherwise. + */ + virtual void close(); + + /** + * Allows the windows modal status to change + */ + void setModal(bool modal); + + /** + * Gets the alpha value used by the window, in a GUIChan usable format. + */ + int getGuiAlpha(); + + gcn::Rectangle getWindowArea(); + + bool isResizeAllowed(gcn::MouseEvent &event); + + private: + enum ResizeHandles + { + TOP = 0x01, + RIGHT = 0x02, + BOTTOM = 0x04, + LEFT = 0x08 + }; + + /** + * Check if the window is off-screen and then move it to be visible + * again. This is internally used by loadWindowState + * and setVisible(true) members. + */ + void checkIfIsOffScreen(bool partially = true, bool entirely = true); + + /** + * Determines if the mouse is in a resize area and returns appropriate + * resize handles. Also initializes drag offset in case the resize + * grip is used. + * + * @see ResizeHandles + */ + int getResizeHandles(gcn::MouseEvent &event); + + ResizeGrip *mGrip; /**< Resize grip */ + Window *mParent; /**< The parent window */ + Layout *mLayout; /**< Layout handler */ + std::string mWindowName; /**< Name of the window */ + bool mShowTitle; /**< Window has a title bar */ + bool mModal; /**< Window is modal */ + bool mCloseButton; /**< Window has a close button */ + bool mDefaultVisible; /**< Window's default visibility */ + bool mSaveVisible; /**< Window will save visibility */ + bool mStickyButton; /**< Window has a sticky button */ + bool mSticky; /**< Window resists hiding*/ + int mMinWinWidth; /**< Minimum window width */ + int mMinWinHeight; /**< Minimum window height */ + int mMaxWinWidth; /**< Maximum window width */ + int mMaxWinHeight; /**< Maximum window height */ + int mDefaultX; /**< Default window X position */ + int mDefaultY; /**< Default window Y position */ + int mDefaultWidth; /**< Default window width */ + int mDefaultHeight; /**< Default window height */ + + static int mouseResize; /**< Active resize handles */ + static int instances; /**< Number of Window instances */ + + Skin *mSkin; /**< Skin in use by this window */ + + /** + * The width of the resize border. Is independent of the actual window + * border width, and determines mostly the size of the corner area + * where two borders are moved at the same time. + */ + static const int resizeBorderWidth = 10; +}; + +#endif diff --git a/src/gui/widgets/windowcontainer.cpp b/src/gui/widgets/windowcontainer.cpp new file mode 100644 index 000000000..0fff491e4 --- /dev/null +++ b/src/gui/widgets/windowcontainer.cpp @@ -0,0 +1,40 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/widgets/windowcontainer.h" + +#include "utils/dtor.h" + +WindowContainer *windowContainer = NULL; + +void WindowContainer::logic() +{ + delete_all(mDeathList); + mDeathList.clear(); + + gcn::Container::logic(); +} + +void WindowContainer::scheduleDelete(gcn::Widget *widget) +{ + if (widget) + mDeathList.push_back(widget); +} diff --git a/src/gui/widgets/windowcontainer.h b/src/gui/widgets/windowcontainer.h new file mode 100644 index 000000000..2ec65d15a --- /dev/null +++ b/src/gui/widgets/windowcontainer.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef WINDOWCONTAINER_H +#define WINDOWCONTAINER_H + +#include "gui/widgets/container.h" + +/** + * A window container. This container adds functionality for more convenient + * widget (windows in particular) destruction. + * + * \ingroup GUI + */ +class WindowContainer : public Container +{ + public: + /** + * Do GUI logic. This functions adds automatic deletion of objects that + * volunteered to be deleted. + */ + void logic(); + + /** + * Schedule a widget for deletion. It will be deleted at the start of + * the next logic update. + */ + void scheduleDelete(gcn::Widget *widget); + + private: + /** + * List of widgets that are scheduled to be deleted. + */ + typedef std::list<gcn::Widget*> Widgets; + typedef Widgets::iterator WidgetIterator; + Widgets mDeathList; +}; + +extern WindowContainer *windowContainer; + +#endif diff --git a/src/gui/windowmenu.cpp b/src/gui/windowmenu.cpp new file mode 100644 index 000000000..eb146f700 --- /dev/null +++ b/src/gui/windowmenu.cpp @@ -0,0 +1,285 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/windowmenu.h" + +#include "emoteshortcut.h" +#include "graphics.h" +#include "keyboardconfig.h" + +#include "gui/emotepopup.h" +#include "gui/skilldialog.h" +#include "gui/specialswindow.h" +#include "gui/textpopup.h" +#include "gui/viewport.h" + +#include "gui/widgets/window.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/playerhandler.h" + +#include "utils/gettext.h" + +#include <string> + +extern Window *equipmentWindow; +extern Window *inventoryWindow; +extern Window *itemShortcutWindow; +extern Window *dropShortcutWindow; +extern Window *setupWindow; +extern Window *statusWindow; +extern Window *whoIsOnline; +extern Window *killStats; +extern Window *spellShortcutWindow; +extern Window *botCheckerWindow; +extern Window *socialWindow; + +WindowMenu::WindowMenu(): + mEmotePopup(0) +{ + int x = 0, h = 0; + + addButton(N_("BC"), _("Bot checker"), x, h, + KeyboardConfig::KEY_WINDOW_BOT_CHECKER); + addButton(N_("ONL"), _("Who is online"), x, h, + KeyboardConfig::KEY_NO_VALUE); + addButton(N_("KS"), _("Kill stats"), x, h, + KeyboardConfig::KEY_WINDOW_KILLS); + addButton(":-)", _("Smiles"), x, h, + KeyboardConfig::KEY_WINDOW_EMOTE_SHORTCUT); + addButton(N_("STA"), _("Status"), x, h, KeyboardConfig::KEY_WINDOW_STATUS); + addButton(N_("EQU"), _("Equipment"), x, h, + KeyboardConfig::KEY_WINDOW_EQUIPMENT); + addButton(N_("INV"), _("Inventory"), x, h, + KeyboardConfig::KEY_WINDOW_INVENTORY); + + if (skillDialog->hasSkills()) + { + addButton(N_("SKI"), _("Skills"), x, h, + KeyboardConfig::KEY_WINDOW_SKILL); + } + + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + addButton(N_("SPE"), _("Specials"), x, h, + KeyboardConfig::KEY_NO_VALUE); + } + + addButton(N_("SOC"), _("Social"), x, h, KeyboardConfig::KEY_WINDOW_SOCIAL); + addButton(N_("SH"), _("Shortcuts"), x, h, + KeyboardConfig::KEY_WINDOW_SHORTCUT); + addButton(N_("SP"), _("Spells"), x, h, KeyboardConfig::KEY_WINDOW_SPELLS); + addButton(N_("DR"), _("Drop"), x, h, KeyboardConfig::KEY_WINDOW_DROP); + addButton(N_("SET"), _("Setup"), x, h, KeyboardConfig::KEY_WINDOW_SETUP); + + mTextPopup = new TextPopup; + setDimension(gcn::Rectangle(graphics->getWidth() - x - 3, 3, + x - 3, h)); + + addMouseListener(this); + setVisible(true); +} + +WindowMenu::~WindowMenu() +{ + delete mTextPopup; + mTextPopup = 0; + mButtonNames.clear(); +} + +void WindowMenu::action(const gcn::ActionEvent &event) +{ + Window *window = 0; + + if (event.getId() == ":-)") + { + if (!mEmotePopup) + { + const gcn::Widget *s = event.getSource(); + if (s) + { + const gcn::Rectangle &r = s->getDimension(); + const int parentX = s->getParent()->getX(); + + mEmotePopup = new EmotePopup; + const int offset = (r.width - mEmotePopup->getWidth()) / 2; + mEmotePopup->setPosition(parentX + r.x + offset, + r.y + r.height + 5); + + mEmotePopup->addSelectionListener(this); + } + else + { + mEmotePopup = 0; + } + } + else + { + if (windowContainer) + windowContainer->scheduleDelete(mEmotePopup); + mEmotePopup = 0; + } + } + else if (event.getId() == "STA") + { + window = statusWindow; + } + else if (event.getId() == "EQU") + { + window = equipmentWindow; + } + else if (event.getId() == "INV") + { + window = inventoryWindow; + } + else if (event.getId() == "SKI") + { + window = skillDialog; + } + else if (event.getId() == "SPE") + { + window = specialsWindow; + } + else if (event.getId() == "SH") + { + window = itemShortcutWindow; + } + else if (event.getId() == "SOC") + { + window = socialWindow; + } + else if (event.getId() == "DR") + { + window = dropShortcutWindow; + } + else if (event.getId() == "SET") + { + window = setupWindow; + } + else if (event.getId() == "ONL") + { + window = whoIsOnline; + } + else if (event.getId() == "KS") + { + window = killStats; + } + else if (event.getId() == "BC") + { + window = botCheckerWindow; + } + else if (event.getId() == "SP") + { + window = spellShortcutWindow; + } + + if (window) + { + window->setVisible(!window->isVisible()); + if (window->isVisible()) + window->requestMoveToTop(); + } +} + +void WindowMenu::valueChanged(const gcn::SelectionEvent &event) +{ + if (event.getSource() == mEmotePopup) + { + int emote = mEmotePopup->getSelectedEmote(); + if (emote && emoteShortcut) + emoteShortcut->useEmote(emote); + + if (windowContainer) + windowContainer->scheduleDelete(mEmotePopup); + mEmotePopup = 0; + } +} + +void WindowMenu::addButton(const char* text, std::string description, + int &x, int &h, int key) +{ + Button *btn = new Button(gettext(text), text, this); + btn->setPosition(x, 0); + btn->setDescription(description); + btn->setTag(key); + add(btn); + mButtons.push_back(btn); + x += btn->getWidth() + 3; + h = btn->getHeight(); + mButtonNames[text] = btn; +} + +void WindowMenu::mousePressed(gcn::MouseEvent &event) +{ + if (!viewport) + return; + + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + Button *btn = dynamic_cast<Button*>(event.getSource()); + if (!btn) + return; + if (viewport) + viewport->showPopup(event.getX(), event.getY(), btn); + } +} + +void WindowMenu::mouseMoved(gcn::MouseEvent &event) +{ + if (!mTextPopup) + return; + + if (event.getSource() == this) + { + mTextPopup->hide(); + return; + } + + Button *btn = dynamic_cast<Button*>(event.getSource()); + + if (!btn) + { + mTextPopup->hide(); + return; + } + + const int x = event.getX(); + const int y = event.getY(); + int key = btn->getTag(); + if (key != KeyboardConfig::KEY_NO_VALUE) + { + mTextPopup->show(x + getX(), y + getY(), btn->getDescription(), + "Key: " + keyboard.getKeyValueString(key)); + } + else + { + mTextPopup->show(x + getX(), y + getY(), btn->getDescription()); + } +} + +void WindowMenu::mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_) +{ + if (!mTextPopup) + return; + + mTextPopup->hide(); +}
\ No newline at end of file diff --git a/src/gui/windowmenu.h b/src/gui/windowmenu.h new file mode 100644 index 000000000..f619a161a --- /dev/null +++ b/src/gui/windowmenu.h @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef WINDOWMENU_H +#define WINDOWMENU_H + +#include "gui/widgets/container.h" +#include "gui/widgets/button.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/selectionlistener.hpp> + +#include <map> + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class EmotePopup; +class TextPopup; + +/** + * The window menu. Allows showing and hiding many of the different windows + * used in the game. + * + * \ingroup Interface + */ +class WindowMenu : public Container, + public gcn::ActionListener, + public gcn::SelectionListener, + public gcn::MouseListener +{ + public: + WindowMenu(); + ~WindowMenu(); + + void action(const gcn::ActionEvent &event); + + void valueChanged(const gcn::SelectionEvent &event); + + void mousePressed(gcn::MouseEvent &event); + + void mouseMoved(gcn::MouseEvent &event); + + void mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_); + + std::map <std::string, gcn::Button*> &getButtonNames() + { return mButtonNames; } + + private: + inline void addButton(const char* text, std::string description, + int &x, int &h, int key); + + EmotePopup *mEmotePopup; + TextPopup *mTextPopup; + std::list <gcn::Button*> mButtons; + std::map <std::string, gcn::Button*> mButtonNames; +}; + +extern WindowMenu *windowMenu; + +#endif diff --git a/src/gui/worldselectdialog.cpp b/src/gui/worldselectdialog.cpp new file mode 100644 index 000000000..f00871bd8 --- /dev/null +++ b/src/gui/worldselectdialog.cpp @@ -0,0 +1,139 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/worldselectdialog.h" + +#include "client.h" + +#include "gui/sdlinput.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" +#include "net/worldinfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern WorldInfo **server_info; + +/** + * The list model for the server list. + */ +class WorldListModel : public gcn::ListModel +{ + public: + WorldListModel(Worlds worlds): + mWorlds(worlds) + { + } + + virtual ~WorldListModel() {} + + int getNumberOfElements() + { + return static_cast<int>(mWorlds.size()); + } + + std::string getElementAt(int i) + { + const WorldInfo *si = mWorlds[i]; + if (si) + return si->name + " (" + toString(si->online_users) + ")"; + else + return "???"; + } + private: + Worlds mWorlds; +}; + +WorldSelectDialog::WorldSelectDialog(Worlds worlds): + Window(_("Select World")) +{ + mWorldListModel = new WorldListModel(worlds); + mWorldList = new ListBox(mWorldListModel); + ScrollArea *worldsScroll = new ScrollArea(mWorldList); + mChangeLoginButton = new Button(_("Change Login"), "login", this); + mChooseWorld = new Button(_("Choose World"), "world", this); + + worldsScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + place(0, 0, worldsScroll, 3, 5).setPadding(2); + place(1, 5, mChangeLoginButton); + place(2, 5, mChooseWorld); + + // Make sure the list has enough height + getLayout().setRowHeight(0, 60); + + reflowLayout(0, 0); + + if (worlds.size() == 0) + // Disable Ok button + mChooseWorld->setEnabled(false); + else + // Select first server + mWorldList->setSelected(0); + + addKeyListener(this); + + center(); + setVisible(true); + mChooseWorld->requestFocus(); +} + +WorldSelectDialog::~WorldSelectDialog() +{ + delete mWorldListModel; + mWorldListModel = 0; +} + +void WorldSelectDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "world") + { + mChangeLoginButton->setEnabled(false); + mChooseWorld->setEnabled(false); + Net::getLoginHandler()->chooseServer(mWorldList->getSelected()); + + // Check in case netcode moves us forward + if (Client::getState() == STATE_WORLD_SELECT) + Client::setState(STATE_WORLD_SELECT_ATTEMPT); + } + else if (event.getId() == "login") + { + Client::setState(STATE_LOGIN); + } +} + +void WorldSelectDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + action(gcn::ActionEvent(NULL, mChangeLoginButton->getActionEventId())); + else if (key.getValue() == Key::ENTER) + action(gcn::ActionEvent(NULL, mChooseWorld->getActionEventId())); +} diff --git a/src/gui/worldselectdialog.h b/src/gui/worldselectdialog.h new file mode 100644 index 000000000..e22438714 --- /dev/null +++ b/src/gui/worldselectdialog.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef WORLD_SELECT_DIALOG_H +#define WORLD_SELECT_DIALOG_H + +#include "gui/widgets/window.h" + +#include "net/worldinfo.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> +#include <guichan/listmodel.hpp> + +#include <vector> + +class LoginData; +class WorldListModel; + +/** + * The server select dialog. + * + * \ingroup Interface + */ +class WorldSelectDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + WorldSelectDialog(Worlds worlds); + + /** + * Destructor. + */ + ~WorldSelectDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + private: + WorldListModel *mWorldListModel; + gcn::ListBox *mWorldList; + gcn::Button *mChangeLoginButton; + gcn::Button *mChooseWorld; +}; + +#endif // WORLD_SELECT_DIALOG_H |