1 /** @file logsettingsdialog.cpp Dialog for Log settings.
2 *
3 * @authors Copyright (c) 2014-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/dialogs/logsettingsdialog.h"
20 #include "clientapp.h"
21 #include "ConfigProfiles"
22
23 #include <de/Config>
24 #include <de/FoldPanelWidget>
25 #include <de/LogFilter>
26 #include <de/SignalAction>
27 #include <de/VariableChoiceWidget>
28 #include <de/VariableToggleWidget>
29
30 using namespace de;
31
32 struct DomainText {
33 char const *name;
34 char const *label;
35 };
36 static DomainText const domainText[] = {
37 { "generic", "Minimum Level" },
38 { "resource", "Resources" },
39 { "map", "Map" },
40 { "script", "Scripting" },
41 { "gl", "Graphics" },
42 { "audio", "Audio" },
43 { "input", "Input" },
44 { "network", "Network" }
45 };
46
47 #define NUM_DOMAINS (sizeof(domainText)/sizeof(domainText[0]))
48
DENG2_PIMPL(LogSettingsDialog)49 DENG2_PIMPL(LogSettingsDialog)
50 , DENG2_OBSERVES(ToggleWidget, Toggle)
51 {
52 ui::ListData levels;
53 VariableToggleWidget *separately;
54 FoldPanelWidget *fold;
55 GridLayout foldLayout;
56 IndirectRule *columnWidth; ///< Sync column width in and out of the fold.
57 struct DomainWidgets
58 {
59 LabelWidget *label;
60 VariableChoiceWidget *level;
61 VariableToggleWidget *dev;
62 VariableToggleWidget *alert;
63 };
64 DomainWidgets domWidgets[NUM_DOMAINS];
65
66 Impl(Public *i) : Base(i)
67 {
68 try
69 {
70 zap(domWidgets);
71
72 columnWidth = new IndirectRule;
73
74 separately = new VariableToggleWidget(tr("Filter by Subsystem"),
75 App::config("log.filterBySubsystem"));
76
77 levels << new ChoiceItem( tr("1 - X.Verbose"), LogEntry::XVerbose)
78 << new ChoiceItem( tr("2 - Verbose"), LogEntry::Verbose )
79 << new ChoiceItem( tr("3 - Message"), LogEntry::Message )
80 << new ChoiceItem( tr("4 - Note"), LogEntry::Note )
81 << new ChoiceItem(_E(D) + tr("5 - Warning"), LogEntry::Warning )
82 << new ChoiceItem(_E(D) + tr("6 - Error"), LogEntry::Error )
83 << new ChoiceItem(_E(D) + tr("7 - Critical"), LogEntry::Critical);
84
85 // Folding panel for the per-domain settings.
86 fold = new FoldPanelWidget;
87 fold->setContent(new GuiWidget);
88
89 foldLayout.setLeftTop(fold->content().rule().left(),
90 fold->content().rule().top());
91 foldLayout.setGridSize(4, 0);
92 foldLayout.setColumnFixedWidth(1, *columnWidth);
93 foldLayout.setColumnAlignment(0, ui::AlignRight);
94
95 for (uint i = 0; i < NUM_DOMAINS; ++i)
96 {
97 initDomain(domainText[i],
98 domWidgets[i],
99 i == 0? &self().area() : &fold->content());
100 }
101 self().area().add(separately);
102 self().area().add(fold);
103
104 // This'll keep the dialog's size fixed even though the choices change size.
105 columnWidth->setSource(domWidgets[0].level->maximumWidth());
106
107 separately->audienceForToggle() += this;
108 }
109 catch (Error const &er)
110 {
111 LOGDEV_ERROR("") << er.asText();
112 deinit();
113 throw;
114 }
115 }
116
117 ~Impl()
118 {
119 deinit();
120 releaseRef(columnWidth);
121 }
122
123 void initDomain(DomainText const &dom, DomainWidgets &wgt, GuiWidget *parent)
124 {
125 // Text label.
126 wgt.label = LabelWidget::newWithText(tr(dom.label) + ":", parent);
127
128 // Minimum level for log entries.
129 parent->add(wgt.level = new VariableChoiceWidget(
130 Config::get(String("log.filter.%1.minLevel").arg(dom.name)),
131 VariableChoiceWidget::Number));
132 wgt.level->setItems(levels);
133 wgt.level->updateFromVariable();
134 QObject::connect(wgt.level, SIGNAL(selectionChangedByUser(uint)),
135 thisPublic, SLOT(updateLogFilter()));
136
137 // Developer messages?
138 parent->add(wgt.dev =
139 new VariableToggleWidget(tr("Dev"), Config::get(String("log.filter.%1.allowDev").arg(dom.name))));
140 QObject::connect(wgt.dev, SIGNAL(stateChangedByUser(ToggleWidget::ToggleState)),
141 thisPublic, SLOT(updateLogFilter()));
142
143 // Raise alerts?
144 parent->add(wgt.alert =
145 new VariableToggleWidget(tr("Alerts"), Config::get(String("alert.") + dom.name)));
146 wgt.alert->setActiveValue(LogEntry::Warning);
147 wgt.alert->setInactiveValue(LogEntry::HighestLogLevel + 1);
148
149 // Lay out the folding panel's contents.
150 if (parent == &fold->content())
151 {
152 foldLayout << *wgt.label << *wgt.level << *wgt.dev << *wgt.alert;
153 }
154 }
155
156 void deinit()
157 {
158 // The common 'levels' will be deleted soon.
159 for (uint i = 0; i < NUM_DOMAINS; ++i)
160 {
161 if (domWidgets[i].level)
162 {
163 domWidgets[i].level->useDefaultItems();
164 }
165 }
166 }
167
168 void overrideWithGeneric()
169 {
170 Config &cfg = App::config();
171 LogFilter &logf = App::logFilter();
172
173 // Check the generic filter settings.
174 LogEntry::Level minLevel = LogEntry::Level(domWidgets[0].level->selectedItem().data().toInt());
175 bool allowDev = domWidgets[0].dev->isActive();
176 bool alerts = domWidgets[0].alert->isActive();
177
178 // Override the individual filters with the generic one.
179 logf.setMinLevel(LogEntry::AllDomains, minLevel);
180 logf.setAllowDev(LogEntry::AllDomains, allowDev);
181
182 // Update the variables (UI updated automatically).
183 logf.write(cfg.objectNamespace().subrecord("log.filter"));
184 for (uint i = 0; i < NUM_DOMAINS; ++i)
185 {
186 char const *name = domainText[i].name;
187 cfg.set(String("alert.") + name, int(alerts? LogEntry::Warning : (LogEntry::HighestLogLevel + 1)));
188 }
189 }
190
191 void toggleStateChanged(ToggleWidget &toggle)
192 {
193 if (toggle.isActive())
194 {
195 overrideWithGeneric();
196 fold->open();
197 }
198 else
199 {
200 fold->close();
201 overrideWithGeneric();
202 }
203 }
204
205 void updateLogFilter()
206 {
207 if (separately->isInactive())
208 {
209 overrideWithGeneric();
210 }
211 // Re-read from Config, which has been changed via the widgets.
212 applyFilterFromConfig();
213 }
214
215 void applyFilterFromConfig()
216 {
217 App::logFilter().read(App::config().subrecord("log.filter"));
218 }
219 };
220
LogSettingsDialog(String const & name)221 LogSettingsDialog::LogSettingsDialog(String const &name)
222 : DialogWidget(name, WithHeading), d(new Impl(this))
223 {
224 heading().setText(tr("Log Filter & Alerts"));
225 heading().setImage(style().images().image("log"));
226
227 // Layout.
228 GridLayout layout(area().contentRule().left(), area().contentRule().top());
229 layout.setGridSize(4, 0);
230 layout.setColumnFixedWidth(1, *d->columnWidth);
231 layout.setColumnAlignment(0, ui::AlignRight);
232
233 layout << *d->domWidgets[0].label
234 << *d->domWidgets[0].level
235 << *d->domWidgets[0].dev
236 << *d->domWidgets[0].alert
237 << Const(0);
238 layout.append(*d->separately, 3);
239 layout.append(*d->fold, 4)
240 << Const(0);
241
242 // Fold's layout is complete.
243 d->fold->content().rule().setSize(d->foldLayout.width(), d->foldLayout.height());
244
245 // Dialog content size.
246 area().setContentSize(layout);
247
248 buttons()
249 << new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close"))
250 << new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"),
251 new SignalAction(this, SLOT(resetToDefaults())));
252
253 if (d->separately->isActive())
254 {
255 d->fold->open();
256 }
257 }
258
resetToDefaults()259 void LogSettingsDialog::resetToDefaults()
260 {
261 ClientApp::logSettings().resetToDefaults();
262
263 d->applyFilterFromConfig();
264 }
265
updateLogFilter()266 void LogSettingsDialog::updateLogFilter()
267 {
268 d->updateLogFilter();
269 }
270