1 /*
2  * prefs-widget.cc
3  * Copyright 2007-2014 Tomasz Moń, Ariadne Conill, and John Lindgren
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include "libaudqt.h"
21 #include "prefs-widget.h"
22 
23 #include <assert.h>
24 #include <math.h>
25 
26 #include <QButtonGroup>
27 #include <QComboBox>
28 #include <QHBoxLayout>
29 #include <QLineEdit>
30 #include <QSpinBox>
31 #include <QVBoxLayout>
32 
33 #include <libaudcore/audstrings.h>
34 #include <libaudcore/i18n.h>
35 
36 namespace audqt
37 {
38 
HookableWidget(const PreferencesWidget * parent,const char * domain)39 HookableWidget::HookableWidget(const PreferencesWidget * parent,
40                                const char * domain)
41     : m_parent(parent), m_domain(domain)
42 {
43     if (m_parent->cfg.hook)
44         hook.connect(m_parent->cfg.hook);
45 }
46 
update_from_cfg()47 void HookableWidget::update_from_cfg()
48 {
49     m_updating = true;
50     update();
51     m_updating = false;
52 }
53 
54 /* button */
ButtonWidget(const PreferencesWidget * parent,const char * domain)55 ButtonWidget::ButtonWidget(const PreferencesWidget * parent,
56                            const char * domain)
57     : QPushButton(translate_str(parent->label, domain))
58 {
59     setAutoDefault(false);
60     QObject::connect(this, &QPushButton::clicked, parent->data.button.callback);
61 }
62 
63 /* boolean widget (checkbox) */
BooleanWidget(const PreferencesWidget * parent,const char * domain)64 BooleanWidget::BooleanWidget(const PreferencesWidget * parent,
65                              const char * domain)
66     : QCheckBox(translate_str(parent->label, domain)),
67       ParentWidget(parent, domain)
68 {
69     update();
70 
71     QObject::connect(this, &QCheckBox::stateChanged, [this](int state) {
72         if (m_updating)
73             return;
74         m_parent->cfg.set_bool(state != Qt::Unchecked);
75         if (m_child_layout)
76             enable_layout(m_child_layout, state != Qt::Unchecked);
77     });
78 }
79 
update()80 void BooleanWidget::update()
81 {
82     bool on = m_parent->cfg.get_bool();
83     setCheckState(on ? Qt::Checked : Qt::Unchecked);
84     if (m_child_layout)
85         enable_layout(m_child_layout, on);
86 }
87 
88 /* integer (radio button) */
RadioButtonWidget(const PreferencesWidget * parent,const char * domain,QButtonGroup * btn_group)89 RadioButtonWidget::RadioButtonWidget(const PreferencesWidget * parent,
90                                      const char * domain,
91                                      QButtonGroup * btn_group)
92     : QRadioButton(translate_str(parent->label, domain)),
93       ParentWidget(parent, domain)
94 {
95     if (btn_group)
96         btn_group->addButton(this, parent->data.radio_btn.value);
97 
98     update();
99 
100     QObject::connect(this, &QAbstractButton::toggled, [this](bool checked) {
101         if (m_updating)
102             return;
103         if (checked)
104             m_parent->cfg.set_int(m_parent->data.radio_btn.value);
105         if (m_child_layout)
106             enable_layout(m_child_layout, checked);
107     });
108 }
109 
update()110 void RadioButtonWidget::update()
111 {
112     bool checked = (m_parent->cfg.get_int() == m_parent->data.radio_btn.value);
113     if (checked)
114         setChecked(true);
115     if (m_child_layout)
116         enable_layout(m_child_layout, checked);
117 }
118 
119 /* integer (spinbox) */
IntegerWidget(const PreferencesWidget * parent,const char * domain)120 IntegerWidget::IntegerWidget(const PreferencesWidget * parent,
121                              const char * domain)
122     : HookableWidget(parent, domain), m_spinner(new QSpinBox)
123 {
124     auto layout = make_hbox(this);
125 
126     if (parent->label)
127         layout->addWidget(new QLabel(translate_str(parent->label, domain)));
128 
129     m_spinner->setRange((int)m_parent->data.spin_btn.min,
130                         (int)m_parent->data.spin_btn.max);
131     m_spinner->setSingleStep((int)m_parent->data.spin_btn.step);
132     layout->addWidget(m_spinner);
133 
134     if (parent->data.spin_btn.right_label)
135         layout->addWidget(new QLabel(
136             translate_str(parent->data.spin_btn.right_label, domain)));
137 
138     layout->addStretch(1);
139 
140     update();
141 
142     /*
143      * Qt has two different valueChanged signals for spin boxes.  So we have to
144      * do an explicit cast to the type of the correct valueChanged signal.
145      * --kaniini.
146      */
147     void (QSpinBox::*signal)(int) = &QSpinBox::valueChanged;
148     QObject::connect(m_spinner, signal, [this](int value) {
149         if (!m_updating)
150             m_parent->cfg.set_int(value);
151     });
152 }
153 
update()154 void IntegerWidget::update() { m_spinner->setValue(m_parent->cfg.get_int()); }
155 
156 /* double (spinbox) */
DoubleWidget(const PreferencesWidget * parent,const char * domain)157 DoubleWidget::DoubleWidget(const PreferencesWidget * parent,
158                            const char * domain)
159     : HookableWidget(parent, domain), m_spinner(new QDoubleSpinBox)
160 {
161     auto layout = make_hbox(this);
162 
163     if (parent->label)
164         layout->addWidget(new QLabel(translate_str(parent->label, domain)));
165 
166     auto decimals_for = [](double step) {
167         return aud::max(0, -(int)floor(log10(step) + 0.01));
168     };
169 
170     m_spinner->setDecimals(decimals_for(m_parent->data.spin_btn.step));
171     m_spinner->setRange(m_parent->data.spin_btn.min,
172                         m_parent->data.spin_btn.max);
173     m_spinner->setSingleStep(m_parent->data.spin_btn.step);
174     layout->addWidget(m_spinner);
175 
176     if (parent->data.spin_btn.right_label)
177         layout->addWidget(new QLabel(
178             translate_str(parent->data.spin_btn.right_label, domain)));
179 
180     layout->addStretch(1);
181 
182     update();
183 
184     void (QDoubleSpinBox::*signal)(double) = &QDoubleSpinBox::valueChanged;
185     QObject::connect(m_spinner, signal, [this](double value) {
186         if (!m_updating)
187             m_parent->cfg.set_float(value);
188     });
189 }
190 
update()191 void DoubleWidget::update() { m_spinner->setValue(m_parent->cfg.get_float()); }
192 
193 /* string (lineedit) */
StringWidget(const PreferencesWidget * parent,const char * domain)194 StringWidget::StringWidget(const PreferencesWidget * parent,
195                            const char * domain)
196     : HookableWidget(parent, domain), m_lineedit(new QLineEdit)
197 {
198     auto layout = make_hbox(this);
199 
200     if (parent->label)
201         layout->addWidget(new QLabel(translate_str(parent->label, domain)));
202 
203     if (parent->type == PreferencesWidget::Entry && parent->data.entry.password)
204         m_lineedit->setEchoMode(QLineEdit::Password);
205 
206     layout->addWidget(m_lineedit, 1);
207 
208     update();
209 
210     QObject::connect(m_lineedit, &QLineEdit::textChanged,
211                      [this](const QString & value) {
212                          if (!m_updating)
213                              m_parent->cfg.set_string(value.toUtf8());
214                      });
215 }
216 
update()217 void StringWidget::update()
218 {
219     m_lineedit->setText((const char *)m_parent->cfg.get_string());
220 }
221 
222 /* file widget (audqt::FileEntry) */
FileWidget(const PreferencesWidget * parent,const char * domain)223 FileWidget::FileWidget(const PreferencesWidget * parent, const char * domain)
224     : HookableWidget(parent, domain)
225 {
226     QFileDialog::FileMode file_mode;
227     const char * title;
228 
229     switch (parent->data.file_entry.mode)
230     {
231     default:
232     case FileSelectMode::File:
233         file_mode = QFileDialog::ExistingFile;
234         title = _("Choose File");
235         break;
236     case FileSelectMode::Folder:
237         file_mode = QFileDialog::Directory;
238         title = _("Choose Folder");
239         break;
240     }
241 
242     m_lineedit =
243         file_entry_new(this, title, file_mode, QFileDialog::AcceptOpen);
244 
245     auto layout = make_hbox(this);
246 
247     if (parent->label)
248         layout->addWidget(new QLabel(translate_str(parent->label, domain)));
249 
250     layout->addWidget(m_lineedit, 1);
251 
252     update();
253 
254     QObject::connect(
255         m_lineedit, &QLineEdit::textChanged, [this](const QString &) {
256             if (!m_updating)
257                 m_parent->cfg.set_string(file_entry_get_uri(m_lineedit));
258         });
259 }
260 
update()261 void FileWidget::update()
262 {
263     file_entry_set_uri(m_lineedit, m_parent->cfg.get_string());
264 }
265 
266 /* font widget (audqt::FontEntry) */
FontWidget(const PreferencesWidget * parent,const char * domain)267 FontWidget::FontWidget(const PreferencesWidget * parent, const char * domain)
268     : HookableWidget(parent, domain), m_lineedit(font_entry_new(this, nullptr))
269 {
270     auto layout = make_hbox(this);
271 
272     if (parent->label)
273         layout->addWidget(new QLabel(translate_str(parent->label, domain)));
274 
275     layout->addWidget(m_lineedit, 1);
276 
277     update();
278 
279     QObject::connect(m_lineedit, &QLineEdit::textChanged,
280                      [this](const QString & value) {
281                          if (!m_updating)
282                              m_parent->cfg.set_string(value.toUtf8());
283                      });
284 }
285 
update()286 void FontWidget::update()
287 {
288     m_lineedit->setText((const char *)m_parent->cfg.get_string());
289 }
290 
291 /* combo box widget (string or int) */
ComboBoxWidget(const PreferencesWidget * parent,const char * domain)292 ComboBoxWidget::ComboBoxWidget(const PreferencesWidget * parent,
293                                const char * domain)
294     : HookableWidget(parent, domain), m_combobox(new QComboBox)
295 {
296     auto layout = make_hbox(this);
297 
298     if (parent->label)
299         layout->addWidget(new QLabel(translate_str(parent->label, domain)));
300 
301     layout->addWidget(m_combobox);
302     layout->addStretch(1);
303 
304     update();
305 
306     void (QComboBox::*signal)(int) = &QComboBox::currentIndexChanged;
307     QObject::connect(m_combobox, signal, [this](int idx) {
308         if (m_updating)
309             return;
310 
311         QVariant data = m_combobox->itemData(idx);
312 
313         switch (m_parent->cfg.type)
314         {
315         case WidgetConfig::Int:
316             m_parent->cfg.set_int(data.toInt());
317             break;
318         case WidgetConfig::String:
319             m_parent->cfg.set_string(data.toString().toUtf8());
320             break;
321         default:
322             break;
323         }
324     });
325 }
326 
update()327 void ComboBoxWidget::update()
328 {
329     ArrayRef<ComboItem> items = m_parent->data.combo.elems;
330 
331     if (m_parent->data.combo.fill)
332         items = m_parent->data.combo.fill();
333 
334     m_combobox->clear();
335 
336     /* add combobox items */
337     switch (m_parent->cfg.type)
338     {
339     case WidgetConfig::Int:
340         for (const ComboItem & item : items)
341             m_combobox->addItem(dgettext(m_domain, item.label), item.num);
342         break;
343     case WidgetConfig::String:
344         for (const ComboItem & item : items)
345             m_combobox->addItem(dgettext(m_domain, item.label), item.str);
346         break;
347     default:
348         break;
349     }
350 
351     /* set selected index */
352     switch (m_parent->cfg.type)
353     {
354     case WidgetConfig::Int:
355     {
356         int num = m_parent->cfg.get_int();
357 
358         for (int i = 0; i < items.len; i++)
359         {
360             if (items.data[i].num == num)
361             {
362                 m_combobox->setCurrentIndex(i);
363                 break;
364             }
365         }
366 
367         break;
368     }
369     case WidgetConfig::String:
370     {
371         String str = m_parent->cfg.get_string();
372 
373         for (int i = 0; i < items.len; i++)
374         {
375             if (!strcmp_safe(items.data[i].str, str))
376             {
377                 m_combobox->setCurrentIndex(i);
378                 break;
379             }
380         }
381 
382         break;
383     }
384     default:
385         break;
386     }
387 }
388 
389 /* layout widgets */
BoxWidget(const PreferencesWidget * parent,const char * domain,bool horizontal_layout)390 BoxWidget::BoxWidget(const PreferencesWidget * parent, const char * domain,
391                      bool horizontal_layout)
392 {
393     QBoxLayout * layout;
394     if (parent->data.box.horizontal)
395         layout = make_hbox(this, sizes.TwoPt);
396     else
397         layout = make_vbox(this, sizes.TwoPt);
398 
399     prefs_populate(layout, parent->data.box.widgets, domain);
400 
401     /* only add stretch if the orientation does not match the enclosing layout
402      */
403     if (parent->data.box.horizontal != horizontal_layout)
404         layout->addStretch(1);
405 }
406 
TableWidget(const PreferencesWidget * parent,const char * domain)407 TableWidget::TableWidget(const PreferencesWidget * parent, const char * domain)
408 {
409     // TODO: proper table layout
410     auto layout = make_vbox(this, sizes.TwoPt);
411     prefs_populate(layout, parent->data.table.widgets, domain);
412 }
413 
NotebookWidget(const PreferencesWidget * parent,const char * domain)414 NotebookWidget::NotebookWidget(const PreferencesWidget * parent,
415                                const char * domain)
416 {
417     for (const NotebookTab & tab : parent->data.notebook.tabs)
418     {
419         auto widget = new QWidget(this);
420         widget->setContentsMargins(margins.FourPt);
421 
422         auto layout = make_vbox(widget, sizes.TwoPt);
423         prefs_populate(layout, tab.widgets, domain);
424         layout->addStretch(1);
425 
426         addTab(widget, translate_str(tab.name, domain));
427     }
428 }
429 
430 } // namespace audqt
431