1 /** @file variablegroupeditor.cpp  Editor of a group of variables.
2  *
3  * @authors Copyright (c) 2013-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/editors/variablegroupeditor.h"
20 
21 #include <de/PopupMenuWidget>
22 #include <de/SignalAction>
23 #include <de/SequentialLayout>
24 
25 using namespace de;
26 
27 /**
28  * Opens a popup menu for folding/unfolding all settings groups.
29  */
30 struct RightClickHandler : public GuiWidget::IEventHandler
31 {
32     VariableGroupEditor &editor;
33 
RightClickHandlerRightClickHandler34     RightClickHandler(VariableGroupEditor *editor) : editor(*editor)
35     {}
36 
handleEventRightClickHandler37     bool handleEvent(GuiWidget &widget, Event const &event)
38     {
39         switch (widget.handleMouseClick(event, MouseEvent::Right))
40         {
41         case GuiWidget::MouseClickFinished: {
42             PopupMenuWidget *pop = new PopupMenuWidget;
43             pop->setDeleteAfterDismissed(true);
44             editor.add(pop);
45             pop->setAnchorAndOpeningDirection(widget.rule(), ui::Left);
46             pop->items()
47                     << new ui::ActionItem(QObject::tr("Fold All"),   new SignalAction(&editor, SLOT(foldAll())))
48                     << new ui::ActionItem(QObject::tr("Unfold All"), new SignalAction(&editor, SLOT(unfoldAll())));
49             pop->open();
50             return true; }
51 
52         case GuiWidget::MouseClickUnrelated:
53             return false;
54 
55         default:
56             return true;
57         }
58     }
59 };
60 
DENG2_PIMPL(VariableGroupEditor)61 DENG2_PIMPL(VariableGroupEditor)
62 {
63     IOwner *owner;
64     bool resetable = false;
65     SafeWidgetPtr<ButtonWidget> resetButton;
66     GuiWidget *content;
67     GuiWidget *header;
68     GridLayout layout;
69     Rule const *firstColumnWidth;
70 
71     Impl(Public *i, IOwner *owner)
72         : Base(i)
73         , owner(owner)
74         , firstColumnWidth(0)
75     {}
76 
77     ~Impl()
78     {
79         releaseRef(firstColumnWidth);
80     }
81 
82     void foldAll(bool fold)
83     {
84         foreach (GuiWidget *child, owner->containerWidget().childWidgets())
85         {
86             if (auto *g = maybeAs<VariableGroupEditor>(child))
87             {
88                 if (fold)
89                     g->close(0);
90                 else
91                     g->open();
92             }
93         }
94     }
95 };
96 
VariableGroupEditor(IOwner * owner,String const & name,String const & titleText,GuiWidget * header)97 VariableGroupEditor::VariableGroupEditor(IOwner *owner, String const &name,
98                                          String const &titleText, GuiWidget *header)
99     : FoldPanelWidget(name)
100     , d(new Impl(this, owner))
101 {
102     d->content = new GuiWidget;
103     setContent(d->content);
104 
105     makeTitle(titleText);
106 
107     // Set up a context menu for right-clicking.
108     title().addEventHandler(new RightClickHandler(this));
109 
110     d->header = header;
111     if (header)
112     {
113         d->content->add(header);
114         header->rule()
115                 .setInput(Rule::Left,  d->content->rule().left())
116                 .setInput(Rule::Top,   d->content->rule().top())
117                 .setInput(Rule::Width, d->layout.width());
118     }
119 
120     // We want the first column of all groups to be aligned with each other.
121     d->layout.setColumnFixedWidth(0, owner->firstColumnWidthRule());
122 
123     d->layout.setGridSize(2, 0);
124     d->layout.setColumnAlignment(0, ui::AlignRight);
125     if (header)
126     {
127         d->layout.setLeftTop(d->content->rule().left(),
128                              d->content->rule().top() + header->rule().height());
129     }
130     else
131     {
132         d->layout.setLeftTop(d->content->rule().left(), d->content->rule().top());
133     }
134 
135     // Button for reseting this content to defaults.
136     d->resetButton.reset(new ButtonWidget);
137     d->resetButton->setText(tr("Reset"));
138     d->resetButton->setAction(new SignalAction(this, SLOT(resetToDefaults())));
139     d->resetButton->rule()
140             .setInput(Rule::Right,   d->owner->containerWidget().contentRule().right())
141             .setInput(Rule::AnchorY, title().rule().top() + title().rule().height() / 2)
142             .setAnchorPoint(Vector2f(0, .5f));
143     d->resetButton->disable();
144 
145     d->owner->containerWidget().add(&title());
146     d->owner->containerWidget().add(d->resetButton);
147     d->owner->containerWidget().add(this);
148 }
149 
destroyAssociatedWidgets()150 void VariableGroupEditor::destroyAssociatedWidgets()
151 {
152     GuiWidget::destroy(d->resetButton);
153     GuiWidget::destroy(&title());
154 }
155 
setResetable(bool resetable)156 void VariableGroupEditor::setResetable(bool resetable)
157 {
158     d->resetable = resetable;
159 }
160 
header() const161 GuiWidget *VariableGroupEditor::header() const
162 {
163     return d->header;
164 }
165 
resetButton()166 ButtonWidget &VariableGroupEditor::resetButton()
167 {
168     return *d->resetButton;
169 }
170 
firstColumnWidth() const171 Rule const &VariableGroupEditor::firstColumnWidth() const
172 {
173     return *d->firstColumnWidth;
174 }
175 
preparePanelForOpening()176 void VariableGroupEditor::preparePanelForOpening()
177 {
178     FoldPanelWidget::preparePanelForOpening();
179     if (d->resetable)
180     {
181         d->resetButton->enable();
182     }
183 }
184 
panelClosing()185 void VariableGroupEditor::panelClosing()
186 {
187     FoldPanelWidget::panelClosing();
188     d->resetButton->disable();
189 }
190 
addSpace()191 void VariableGroupEditor::addSpace()
192 {
193     d->layout << Const(0);
194 }
195 
addLabel(String const & text,LabelType labelType)196 LabelWidget *VariableGroupEditor::addLabel(String const &text, LabelType labelType)
197 {
198     LabelWidget *w = LabelWidget::newWithText(text, d->content);
199     if (labelType == SingleCell)
200     {
201         d->layout << *w;
202     }
203     else
204     {
205         d->layout.setCellAlignment(Vector2i(0, d->layout.gridSize().y), ui::AlignLeft);
206         d->layout.append(*w, 2);
207     }
208     return w;
209 }
210 
addToggle(char const * cvar,String const & label)211 CVarToggleWidget *VariableGroupEditor::addToggle(char const *cvar, String const &label)
212 {
213     CVarToggleWidget *w = new CVarToggleWidget(cvar, label);
214     d->content->add(w);
215     d->layout << *w;
216     return w;
217 }
218 
addChoice(char const * cvar,ui::Direction opening)219 CVarChoiceWidget *VariableGroupEditor::addChoice(char const *cvar, ui::Direction opening)
220 {
221     CVarChoiceWidget *w = new CVarChoiceWidget(cvar);
222     w->setOpeningDirection(opening);
223     w->popup().useInfoStyle();
224     d->content->add(w);
225     d->layout << *w;
226     return w;
227 }
228 
addSlider(char const * cvar)229 CVarSliderWidget *VariableGroupEditor::addSlider(char const *cvar)
230 {
231     auto *w = new CVarSliderWidget(cvar);
232     d->content->add(w);
233     d->layout << *w;
234     return w;
235 }
236 
addSlider(char const * cvar,Ranged const & range,double step,int precision)237 CVarSliderWidget *VariableGroupEditor::addSlider(char const *cvar, Ranged const &range, double step, int precision)
238 {
239     auto *w = addSlider(cvar);
240     w->setRange(range, step);
241     w->setPrecision(precision);
242     return w;
243 }
244 
addToggle(Variable & var,String const & label)245 VariableToggleWidget *VariableGroupEditor::addToggle(Variable &var, String const &label)
246 {
247     auto *w = new VariableToggleWidget(label, var);
248     d->content->add(w);
249     d->layout << *w;
250     return w;
251 }
252 
addSlider(Variable & var,Ranged const & range,double step,int precision)253 VariableSliderWidget *VariableGroupEditor::addSlider(Variable &var, Ranged const &range, double step, int precision)
254 {
255     auto *w = new VariableSliderWidget(var, range, step);
256     w->setPrecision(precision);
257     d->content->add(w);
258     d->layout << *w;
259     return w;
260 }
261 
addLineEdit(Variable & var)262 VariableLineEditWidget *VariableGroupEditor::addLineEdit(Variable &var)
263 {
264     auto *w = new VariableLineEditWidget(var);
265     d->content->add(w);
266     d->layout << *w;
267     w->rule().setInput(Rule::Width, rule("slider.width"));
268     return w;
269 }
270 
addWidget(GuiWidget * widget)271 void VariableGroupEditor::addWidget(GuiWidget *widget)
272 {
273     d->content->add(widget);
274     d->layout << *widget;
275 }
276 
commit()277 void VariableGroupEditor::commit()
278 {
279     d->content->rule().setSize(d->layout.width(), d->layout.height() +
280                                (d->header? d->header->rule().height() : Const(0)));
281 
282     // Extend the title all the way to the button.
283     title().rule().setInput(Rule::Right, d->resetButton->rule().left());
284 
285     // Calculate the maximum rule for the first column items.
286     for (int i = 0; i < d->layout.gridSize().y; ++i)
287     {
288         GuiWidget const *w = d->layout.at(Vector2i(0, i));
289         if (w && d->layout.widgetCellSpan(*w) == 1)
290         {
291             changeRef(d->firstColumnWidth,
292                       OperatorRule::maximum(w->rule().width(), d->firstColumnWidth));
293         }
294     }
295     if (d->header)
296     {
297         // Make sure the editor is wide enough to fit the entire header.
298         d->content->rule().setInput(Rule::Width,
299                 OperatorRule::maximum(d->layout.width(), d->header->rule().width()));
300     }
301 }
302 
fetch()303 void VariableGroupEditor::fetch()
304 {
305     foreach (GuiWidget *child, d->content->childWidgets())
306     {
307         if (ICVarWidget *w = maybeAs<ICVarWidget>(child))
308         {
309             w->updateFromCVar();
310         }
311     }
312 }
313 
resetToDefaults()314 void VariableGroupEditor::resetToDefaults()
315 {
316     foreach (GuiWidget *child, d->content->childWidgets())
317     {
318         if (ICVarWidget *w = maybeAs<ICVarWidget>(child))
319         {
320             d->owner->resetToDefaults(w->cvarPath());
321             //d->settings.resetSettingToDefaults(w->cvarPath());
322             w->updateFromCVar();
323         }
324     }
325 }
326 
foldAll()327 void VariableGroupEditor::foldAll()
328 {
329     d->foldAll(true);
330 }
331 
unfoldAll()332 void VariableGroupEditor::unfoldAll()
333 {
334     d->foldAll(false);
335 }
336