Mana
Loading...
Searching...
No Matches
questswindow.cpp
Go to the documentation of this file.
1/*
2 * The Mana Client
3 * Copyright (C) 2025 The Mana Developers
4 *
5 * This file is part of The Mana Client.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "questswindow.h"
22
23#include "configuration.h"
24
25#include "gui/setup.h"
26
30#include "gui/widgets/layout.h"
31#include "gui/widgets/listbox.h"
33
34#include "net/net.h"
35#include "net/playerhandler.h"
36
37#include "resources/questdb.h"
38
39#include "utils/gettext.h"
40
41#include <guichan/font.hpp>
42
43#include <algorithm>
44
45class QuestsModel final : public gcn::ListModel
46{
47public:
48 int getNumberOfElements() override
49 { return mQuests.size(); }
50
51 std::string getElementAt(int i) override
52 { return mQuests[i].name(); }
53
54 const std::vector<QuestEntry> &getQuests() const
55 { return mQuests; }
56
57 void setQuests(const std::vector<QuestEntry> &quests)
58 { mQuests = quests; }
59
60private:
61 std::vector<QuestEntry> mQuests;
62};
63
64
65class QuestsListBox final : public ListBox
66{
67public:
69 : ListBox(model)
70 {}
71
72 unsigned getRowHeight() const override;
73
74 void draw(gcn::Graphics *graphics) override;
75};
76
78{
79 auto rowHeight = ListBox::getRowHeight();
80
81 if (auto icon = gui->getTheme()->getIcon("complete"))
82 rowHeight = std::max<unsigned>(rowHeight, icon->getHeight() + 2);
83
84 return rowHeight;
85}
86
87void QuestsListBox::draw(gcn::Graphics *gcnGraphics)
88{
89 if (!mListModel)
90 return;
91
92 auto *graphics = static_cast<Graphics *>(gcnGraphics);
93 auto *model = static_cast<QuestsModel *>(getListModel());
94
95 const int rowHeight = getRowHeight();
96
97 auto theme = gui->getTheme();
98 auto completeIcon = theme->getIcon("complete");
99 auto incompleteIcon = theme->getIcon("incomplete");
100
101 // Draw filled rectangle around the selected list element
102 if (mSelected >= 0)
103 {
104 auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT);
105 highlightColor.a = gui->getTheme()->getGuiAlpha();
106 graphics->setColor(highlightColor);
107 graphics->fillRectangle(gcn::Rectangle(0, rowHeight * mSelected,
108 getWidth(), rowHeight));
109 }
110
111 // Draw the list elements
112 graphics->setFont(getFont());
114
115 const int fontHeight = getFont()->getHeight();
116
117 for (int i = 0, y = 0; i < model->getNumberOfElements();
118 ++i, y += rowHeight)
119 {
120 if (mSelected == i)
122 else
124
125 auto &quest = model->getQuests()[i];
126 int x = 1;
127
128 if (const Image *icon = quest.completed ? completeIcon : incompleteIcon)
129 {
130 graphics->drawImage(icon, x, y + (rowHeight - icon->getHeight()) / 2);
131 x += icon->getWidth() + 4;
132 }
133
134 graphics->drawText(quest.name(), x, y + (rowHeight - fontHeight) / 2);
135 }
136}
137
138
140 : Window(_("Quests"))
141 , mQuestsModel(std::make_unique<QuestsModel>())
142 , mQuestsListBox(new QuestsListBox(mQuestsModel.get()))
143 , mHideCompletedCheckBox(new CheckBox(_("Hide completed"), config.hideCompletedQuests))
144 , mQuestDetails(new BrowserBox(BrowserBox::AUTO_WRAP))
145 , mLinkHandler(std::make_unique<ItemLinkHandler>())
146{
147 setWindowName("Quests");
149 setResizable(true);
150 setCloseButton(true);
151 setSaveVisible(true);
152
154 setMinWidth(316);
155 setMinHeight(179);
156
157 mQuestsListBox->addSelectionListener(this);
158 mHideCompletedCheckBox->setActionEventId("hideCompleted");
159 mHideCompletedCheckBox->addActionListener(this);
160
161 auto questListScrollArea = new ScrollArea(mQuestsListBox);
162 questListScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
163
166 mQuestDetailsScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
167
168 auto place = getPlacer(0, 0);
169 place(0, 0, questListScrollArea, 2, 2).setPadding(2);
171 place = getPlacer(0, 1);
173
174 getLayout().setRowHeight(1, 0); // Don't scale up the bottom row
175
177
180}
181
183
184void QuestsWindow::action(const gcn::ActionEvent &event)
185{
186 if (event.getId() == "hideCompleted")
187 {
190 }
191}
192
193void QuestsWindow::valueChanged(const gcn::SelectionEvent &event)
194{
195 if (mSelectedQuestIndex != mQuestsListBox->getSelected())
197}
198
199void QuestsWindow::event(Event::Channel channel, const Event &event)
200{
201 if (channel == Event::QuestsChannel)
202 {
203 if (event.getType() == Event::QuestVarsChanged)
205 }
206}
207
209{
210 // Store the currently selected quest state and varId to preserve selection
211 const QuestState *selectedQuestState = nullptr;
212 int selectedVarId = -1;
213 if (mSelectedQuestIndex >= 0 && mSelectedQuestIndex < mQuestsModel->getNumberOfElements())
214 {
215 const auto &selectedQuest = mQuestsModel->getQuests().at(mSelectedQuestIndex);
216 selectedQuestState = selectedQuest.state;
217 selectedVarId = selectedQuest.varId;
218 }
219
220 auto &questVars = Net::getPlayerHandler()->getQuestVars();
221 auto newQuests = QuestDB::getQuestsEntries(questVars, config.hideCompletedQuests);
222
223 // Put completed quests at the top
224 std::stable_sort(newQuests.begin(), newQuests.end(), [](const QuestEntry &a, const QuestEntry &b) {
225 return a.completed > b.completed;
226 });
227
228 mQuestsModel->setQuests(newQuests);
229
230 if (!selectedQuestState)
231 return;
232
233 // Try to find and reselect the same quest, preferring exact state match
234 int newSelectedIndex = -1;
235
236 for (int i = 0; i < static_cast<int>(newQuests.size()); ++i)
237 {
238 if (newQuests[i].state == selectedQuestState)
239 {
240 newSelectedIndex = i;
241 break;
242 }
243 else if (newSelectedIndex == -1 && newQuests[i].varId == selectedVarId)
244 {
245 newSelectedIndex = i;
246 // Don't break here - continue looking for exact state match
247 }
248 }
249
250 if (mSelectedQuestIndex != newSelectedIndex)
251 mQuestsListBox->setSelected(newSelectedIndex);
252 else
254}
255
257{
259
260 mSelectedQuestIndex = mQuestsListBox->getSelected();
261 if (mSelectedQuestIndex < 0 || mSelectedQuestIndex >= mQuestsModel->getNumberOfElements())
262 return;
263
264 const QuestEntry &quest = mQuestsModel->getQuests().at(mSelectedQuestIndex);
265 for (const auto &row : quest.rows())
266 {
267 switch (row.type)
268 {
270 mQuestDetails->addRow(row.text);
271 break;
273 mQuestDetails->addRow("[" + row.text + "]");
274 break;
276 mQuestDetails->addRow(strprintf(_("Reward: %s"), row.text.c_str()));
277 break;
279 mQuestDetails->addRow(strprintf(_("Quest Giver: %s"), row.text.c_str()));
280 break;
282 mQuestDetails->addRow(strprintf(_("Coordinates: %s (%d, %d)"),
283 row.text.c_str(), row.x, row.y));
284 break;
286 mQuestDetails->addRow(strprintf(_("NPC: %s"), row.text.c_str()));
287 break;
288 }
289 }
290}
A simple browser box able to handle links and forward events to the parent conteiner.
Definition browserbox.h:74
void addRow(std::string_view row)
Adds a text row to the browser.
void setLinkHandler(LinkHandler *handler)
Sets the handler for links.
Definition browserbox.h:88
void clearRows()
Remove all rows.
Check box widget.
Definition checkbox.h:32
void listen(Event::Channel channel)
Definition event.h:42
@ QuestVarsChanged
Definition event.h:104
Channel
Definition event.h:45
@ QuestsChannel
Definition event.h:57
A central point of control for graphics.
Definition graphics.h:78
void drawText(const std::string &text, int x, int y, gcn::Graphics::Alignment alignment, const gcn::Color &color, gcn::Font *font, bool outline=false, bool shadow=false, const std::optional< gcn::Color > &outlineColor={}, const std::optional< gcn::Color > &shadowColor={})
Definition graphics.cpp:176
void setColor(const gcn::Color &color) override
Definition graphics.h:255
bool drawImage(const Image *image, int x, int y)
Blits an image onto the screen.
Definition graphics.cpp:36
Theme * getTheme() const
The global GUI theme.
Definition gui.h:138
Defines a class for loading and storing images.
Definition image.h:45
void setRowHeight(int n, int h)
Definition layout.h:221
LayoutCell & setPadding(int p)
Sets the padding around the cell content.
Definition layout.h:179
A list box, meant to be used inside a scroll area.
Definition listbox.h:36
const QuestVars & getQuestVars() const
void draw(gcn::Graphics *graphics) override
QuestsListBox(QuestsModel *model)
unsigned getRowHeight() const override
std::vector< QuestEntry > mQuests
const std::vector< QuestEntry > & getQuests() const
std::string getElementAt(int i) override
void setQuests(const std::vector< QuestEntry > &quests)
int getNumberOfElements() override
void valueChanged(const gcn::SelectionEvent &event) override
void refreshQuestList()
CheckBox * mHideCompletedCheckBox
BrowserBox * mQuestDetails
void action(const gcn::ActionEvent &event) override
std::unique_ptr< QuestsModel > mQuestsModel
QuestsListBox * mQuestsListBox
void updateQuestDetails()
void event(Event::Channel channel, const Event &event) override
std::unique_ptr< LinkHandler > mLinkHandler
ScrollArea * mQuestDetailsScrollArea
int mSelectedQuestIndex
A scroll area.
Definition scrollarea.h:38
void registerWindowForReset(Window *window)
Enables the Reset Windows button.
Definition setup.cpp:108
int getGuiAlpha() const
Get the current GUI alpha value.
Definition theme.h:321
static const gcn::Color & getThemeColor(int type)
Gets the color associated with the type in the default palette (0).
Definition theme.cpp:313
@ HIGHLIGHT_TEXT
Definition theme.h:232
@ HIGHLIGHT
Definition theme.h:231
@ TEXT
Definition theme.h:214
const Image * getIcon(const std::string &name) const
Definition theme.cpp:441
A window.
Definition window.h:59
void setMinHeight(int height)
Sets the minimum height of the window.
Definition window.cpp:197
Layout & getLayout()
Gets the layout handler for this window.
Definition window.cpp:714
void setWindowName(const std::string &name)
Sets the name of the window.
Definition window.h:272
ContainerPlacer getPlacer(int x, int y)
Returns a proxy for adding widgets in an inner table of the layout.
Definition window.cpp:743
void setResizable(bool resize)
Sets whether or not the window can be resized.
Definition window.cpp:212
void setSaveVisible(bool save)
Sets whether the window will save it's visibility.
Definition window.h:225
LayoutCell & place(int x, int y, gcn::Widget *, int w=1, int h=1)
Adds a widget to the window and sets it at given cell.
Definition window.cpp:737
void setCloseButton(bool flag)
Sets whether or not the window has a close button.
Definition window.cpp:262
void setDefaultSize()
Set the default win pos and size to the current ones.
Definition window.cpp:545
void loadWindowState()
Reads the position (and the size for resizable windows) in the configuration based on the given strin...
Definition window.cpp:467
void setMinWidth(int width)
Sets the minimum width of the window.
Definition window.cpp:192
Config config
Global settings (config.xml)
Definition client.cpp:97
Graphics * graphics
Definition client.cpp:104
#define _(s)
Definition gettext.h:38
Gui * gui
The GUI system.
Definition gui.cpp:50
PlayerHandler * getPlayerHandler()
Definition net.cpp:110
std::vector< QuestEntry > getQuestsEntries(const QuestVars &questVars, bool skipCompleted)
Definition questdb.cpp:160
Setup * setupWindow
Definition setup.cpp:120
std::string strprintf(char const *format,...)
A safe version of sprintf that returns a std::string of the result.
bool hideCompletedQuests
const std::vector< QuestRow > & rows() const
Definition questdb.h:114