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