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