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