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