1 /** @file savelistwidget.cpp List showing the available saves of a game.
2 *
3 * @authors Copyright (c) 2016-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * GPL: http://www.gnu.org/licenses/gpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 * Public License for more details. You should have received a copy of the GNU
15 * General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "ui/home/savelistwidget.h"
20 #include "ui/home/gamepanelbuttonwidget.h"
21 #include "ui/savelistdata.h"
22
23 #include <doomsday/game.h>
24 #include <doomsday/GameStateFolder>
25
26 #include <de/CallbackAction>
27 #include <de/DocumentPopupWidget>
28 #include <de/FileSystem>
29
30 using namespace de;
31
DENG_GUI_PIMPL(SaveListWidget)32 DENG_GUI_PIMPL(SaveListWidget)
33 , DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate)
34 {
35 /**
36 * Handles mouse button doubleclicks on the save items.
37 */
38 struct ClickHandler : public GuiWidget::IEventHandler
39 {
40 SaveListWidget::Impl *d;
41
42 ClickHandler(SaveListWidget::Impl *d) : d(d) {}
43
44 bool handleEvent(GuiWidget &button, Event const &event)
45 {
46 if (event.type() == Event::MouseButton)
47 {
48 MouseEvent const &mouse = event.as<MouseEvent>();
49
50 // Check for right-clicking.
51 if (mouse.button() == MouseEvent::Right)
52 {
53 switch (button.handleMouseClick(event, MouseEvent::Right))
54 {
55 case MouseClickStarted:
56 return true;
57
58 case MouseClickAborted:
59 return true;
60
61 case MouseClickFinished:
62 saveRightClicked(button);
63 return true;
64
65 default:
66 return false; // Ignore.
67 }
68 }
69
70 /*if (button.hitTest(mouse) && mouse.state() == MouseEvent::DoubleClick)
71 {
72 emit d->self().doubleClicked(d->self().items().find(
73 *d->self().organizer().findItemForWidget(button)));
74 return true;
75 }*/
76 }
77 return false;
78 }
79
80 void saveRightClicked(GuiWidget &saveButton)
81 {
82 auto const &saveItem = d->self().organizer()
83 .findItemForWidget(saveButton)->as<SaveListData::SaveItem>();
84
85 if (GameStateFolder const *saved = FS::tryLocate<GameStateFolder>(saveItem.savePath()))
86 {
87 auto *docPop = new DocumentPopupWidget;
88 docPop->setDeleteAfterDismissed(true);
89 docPop->setCloseButtonVisible(true);
90 docPop->setAnchorAndOpeningDirection(saveButton.rule(), ui::Right);
91 docPop->document().setText(saved->metadata().asStyledText());
92 saveButton.add(docPop);
93 docPop->open();
94 }
95 }
96 };
97
98 GamePanelButtonWidget &owner;
99 ui::DataPos selected = ui::Data::InvalidPos;
100
101 Impl(Public *i, GamePanelButtonWidget &owner) : Base(i), owner(owner)
102 {
103 self().organizer().audienceForWidgetUpdate() += this;
104 }
105
106 void widgetUpdatedForItem(GuiWidget &widget, ui::Item const &item)
107 {
108 auto &button = widget.as<ButtonWidget>();
109 button.setTextAlignment(ui::AlignRight);
110 button.setAlignment(ui::AlignLeft);
111 button.setTextLineAlignment(ui::AlignLeft);
112 button.setSizePolicy(ui::Filled, ui::Expand);
113 button.setText(item.label());
114 button.margins().set("dialog.gap");
115 button.set(Background(Vector4f()));
116
117 button.setActionFn([this, &button] () {
118 toggleSelectedItem(button);
119 emit owner.mouseActivity();
120 });
121 button.addEventHandler(new ClickHandler(this));
122
123 auto const &saveItem = item.as<SaveListData::SaveItem>();
124 button.setImage(style().images().image(Game::logoImageForId(saveItem.gameId())));
125 button.setOverrideImageSize(style().fonts().font("default").height() * 1.4f);
126 }
127
128 void toggleSelectedItem(ButtonWidget &button)
129 {
130 auto const buttonItemPos = self().items().find(*self().organizer().findItemForWidget(button));
131
132 if (selected == buttonItemPos)
133 {
134 // Unselect.
135 selected = ui::Data::InvalidPos;
136 updateItemHighlights(nullptr);
137 }
138 else
139 {
140 selected = buttonItemPos;
141 updateItemHighlights(&button);
142 }
143
144 emit self().selectionChanged(selected);
145
146 // Keep focus on the clicked button.
147 self().root().setFocus(&self());
148 }
149
150 void updateItemHighlights(ButtonWidget *selectedButton)
151 {
152 for (auto *w : self().childWidgets())
153 {
154 if (auto *bw = maybeAs<ButtonWidget>(w))
155 {
156 if (selectedButton == bw)
157 {
158 bw->useInfoStyle();
159 bw->set(Background(style().colors().colorf("inverted.background")));
160 }
161 else
162 {
163 bw->useNormalStyle();
164 bw->set(Background());
165 }
166 }
167 }
168 }
169 };
170
SaveListWidget(GamePanelButtonWidget & owner)171 SaveListWidget::SaveListWidget(GamePanelButtonWidget &owner)
172 : d(new Impl(this, owner))
173 {
174 setGridSize(1, ui::Filled, 0, ui::Expand);
175 enableScrolling(false);
176 enablePageKeys(false);
177 }
178
selectedPos() const179 ui::DataPos SaveListWidget::selectedPos() const
180 {
181 return d->selected;
182 }
183
setSelectedPos(ui::DataPos pos)184 void SaveListWidget::setSelectedPos(ui::DataPos pos)
185 {
186 if (d->selected != pos)
187 {
188 d->selected = pos;
189 d->updateItemHighlights(&itemWidget<ButtonWidget>(items().at(pos)));
190 emit selectionChanged(d->selected);
191 }
192 }
193
clearSelection()194 void SaveListWidget::clearSelection()
195 {
196 if (d->selected != ui::Data::InvalidPos)
197 {
198 d->selected = ui::Data::InvalidPos;
199 d->updateItemHighlights(nullptr);
200 emit selectionChanged(d->selected);
201 }
202 }
203