1 /* This file is part of the KDE project
2    Copyright (C) 2008 Fela Winkelmolen <fela.kde@gmail.com>
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18 */
19 
20 #include "KarbonCalligraphyOptionWidget.h"
21 
22 #include <KoIcon.h>
23 
24 #include <klocalizedstring.h>
25 #include <kcombobox.h>
26 #include <kconfiggroup.h>
27 #include <ksharedconfig.h>
28 #include <kconfig.h>
29 #include <QDebug>
30 #include <kmessagebox.h>
31 #include <KisAngleSelector.h>
32 
33 #include <QInputDialog>
34 #include <QCheckBox>
35 #include <QDoubleSpinBox>
36 #include <QLabel>
37 #include <QGridLayout>
38 #include <QToolButton>
39 
40 #include "kis_double_parse_spin_box.h"
41 #include "kis_int_parse_spin_box.h"
42 
43 /*
44 Profiles are saved in karboncalligraphyrc
45 
46 In the group "General", profile is the name of profile used
47 
48 Every profile is described in a group, the name of which is "ProfileN"
49 Starting to count from 0 onwards
50 (NOTE: the index in profiles is different from the N)
51 
52 Default profiles are added by the function addDefaultProfiles(), once they
53 have been added, the entry defaultProfilesAdded in the "General" group is
54 set to true
55 
56 TODO: add a reset defaults option?
57 */
58 
59 // name of the configuration file
60 const QString RCFILENAME = "karboncalligraphyrc";
61 
KarbonCalligraphyOptionWidget()62 KarbonCalligraphyOptionWidget::KarbonCalligraphyOptionWidget()
63     : m_changingProfile(false)
64 {
65     QGridLayout *layout = new QGridLayout(this);
66     layout->setContentsMargins(0, 0, 0, 0);
67 
68     m_comboBox = new KComboBox(this);
69     layout->addWidget(m_comboBox, 0, 0);
70 
71     m_saveButton = new QToolButton(this);
72     m_saveButton->setToolTip(i18n("Save profile as..."));
73     m_saveButton->setIcon(koIcon("document-save-as"));
74     layout->addWidget(m_saveButton, 0, 1);
75 
76     m_removeButton = new QToolButton(this);
77     m_removeButton->setToolTip(i18n("Remove profile"));
78     m_removeButton->setIcon(koIcon("list-remove"));
79     layout->addWidget(m_removeButton, 0, 2);
80 
81     QGridLayout *detailsLayout = new QGridLayout();
82     detailsLayout->setContentsMargins(0, 0, 0, 0);
83     detailsLayout->setVerticalSpacing(0);
84 
85     m_usePath = new QCheckBox(i18n("&Follow selected path"), this);
86     detailsLayout->addWidget(m_usePath, 0, 0, 1, 4);
87 
88     m_usePressure = new QCheckBox(i18n("Use tablet &pressure"), this);
89     detailsLayout->addWidget(m_usePressure, 1, 0, 1, 4);
90 
91     QLabel *widthLabel = new QLabel(i18n("Width:"), this);
92     widthLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
93     m_widthBox = new KisDoubleParseSpinBox(this);
94     m_widthBox->setRange(0.0, 999.0);
95     widthLabel->setBuddy(m_widthBox);
96     detailsLayout->addWidget(widthLabel, 2, 2);
97     detailsLayout->addWidget(m_widthBox, 2, 3);
98 
99     QLabel *thinningLabel = new QLabel(i18n("Thinning:"), this);
100     thinningLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
101     m_thinningBox = new KisDoubleParseSpinBox(this);
102     m_thinningBox->setRange(-1.0, 1.0);
103     m_thinningBox->setSingleStep(0.1);
104     thinningLabel->setBuddy(m_thinningBox);
105     detailsLayout->addWidget(thinningLabel, 2, 0);
106     detailsLayout->addWidget(m_thinningBox, 2, 1);
107 
108     m_useAngle = new QCheckBox(i18n("Use tablet &angle"), this);
109     detailsLayout->addWidget(m_useAngle, 3, 0, 1, 4);
110 
111     QLabel *angleLabel = new QLabel(i18n("Angle:"), this);
112     angleLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
113     m_angleBox = new KisAngleSelector(this);
114     m_angleBox->setRange(0, 179);
115     m_angleBox->setDecimals(0);
116     m_angleBox->setResetAngle(30);
117     m_angleBox->setFlipOptionsMode(KisAngleSelector::FlipOptionsMode_ContextMenu);
118     angleLabel->setBuddy(m_angleBox);
119     detailsLayout->addWidget(angleLabel, 4, 0);
120     detailsLayout->addWidget(m_angleBox, 4, 1);
121 
122     QLabel *fixationLabel = new QLabel(i18n("Fixation:"), this);
123     fixationLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
124     m_fixationBox = new KisDoubleParseSpinBox(this);
125     m_fixationBox->setRange(0.0, 1.0);
126     m_fixationBox->setSingleStep(0.1);
127     fixationLabel->setBuddy(m_fixationBox);
128     detailsLayout->addWidget(fixationLabel, 5, 0);
129     detailsLayout->addWidget(m_fixationBox, 5, 1);
130 
131     QLabel *capsLabel = new QLabel(i18n("Caps:"), this);
132     capsLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
133     m_capsBox = new KisDoubleParseSpinBox(this);
134     m_capsBox->setRange(0.0, 2.0);
135     m_capsBox->setSingleStep(0.03);
136     capsLabel->setBuddy(m_capsBox);
137     detailsLayout->addWidget(capsLabel, 5, 2);
138     detailsLayout->addWidget(m_capsBox, 5, 3);
139 
140     QLabel *massLabel = new QLabel(i18n("Mass:"), this);
141     massLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
142     m_massBox = new KisDoubleParseSpinBox(this);
143     m_massBox->setRange(0.0, 20.0);
144     m_massBox->setDecimals(1);
145     massLabel->setBuddy(m_massBox);
146     detailsLayout->addWidget(massLabel, 6, 0);
147     detailsLayout->addWidget(m_massBox, 6, 1);
148 
149     QLabel *dragLabel = new QLabel(i18n("Drag:"), this);
150     dragLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
151     m_dragBox = new KisDoubleParseSpinBox(this);
152     m_dragBox->setRange(0.0, 1.0);
153     m_dragBox->setSingleStep(0.1);
154     dragLabel->setBuddy(m_dragBox);
155     detailsLayout->addWidget(dragLabel, 6, 2);
156     detailsLayout->addWidget(m_dragBox, 6, 3);
157 
158     layout->addLayout(detailsLayout, 1, 0, 1, 3);
159     layout->setRowStretch(2, 1);
160 
161     createConnections();
162     addDefaultProfiles(); // if they are already added does nothing
163     loadProfiles();
164 }
165 
~KarbonCalligraphyOptionWidget()166 KarbonCalligraphyOptionWidget::~KarbonCalligraphyOptionWidget()
167 {
168     qDeleteAll(m_profiles);
169 }
170 
emitAll()171 void KarbonCalligraphyOptionWidget::emitAll()
172 {
173     emit usePathChanged(m_usePath->isChecked());
174     emit usePressureChanged(m_usePressure->isChecked());
175     emit useAngleChanged(m_useAngle->isChecked());
176     emit widthChanged(m_widthBox->value());
177     emit thinningChanged(m_thinningBox->value());
178     emit angleChanged(static_cast<int>(m_angleBox->angle()));
179     emit fixationChanged(m_fixationBox->value());
180     emit capsChanged(m_capsBox->value());
181     emit massChanged(m_massBox->value());
182     emit dragChanged(m_dragBox->value());
183 }
184 
loadProfile(const QString & name)185 void KarbonCalligraphyOptionWidget::loadProfile(const QString &name)
186 {
187     if (m_changingProfile) {
188         return;
189     }
190     // write the new profile in the config file
191     KConfig config(RCFILENAME);
192     KConfigGroup generalGroup(&config, "General");
193     generalGroup.writeEntry("profile", name);
194     config.sync();
195 
196     // and load it
197     loadCurrentProfile();
198 
199     // don't show Current if it isn't selected
200     if (name != i18n("Current")) {
201         removeProfile(i18n("Current"));
202     }
203 }
204 
updateCurrentProfile()205 void KarbonCalligraphyOptionWidget::updateCurrentProfile()
206 {
207     if (!m_changingProfile) {
208         saveProfile(i18n("Current"));
209     }
210 }
211 
saveProfileAs()212 void KarbonCalligraphyOptionWidget::saveProfileAs()
213 {
214     QString name;
215 
216     // loop until a valid name is entered or the user cancelled
217     while (1) {
218         bool ok;
219         name = QInputDialog::getText(this,
220                                      i18n("Profile name"),
221                                      i18n("Please insert the name by which "
222                                           "you want to save this profile:"),
223                                      QLineEdit::Normal, QString(), &ok);
224         if (!ok) {
225             return;
226         }
227 
228         if (name.isEmpty() || name == i18n("Current")) {
229             KMessageBox::sorry(this,
230                                i18n("Sorry, the name you entered is invalid."),
231                                i18nc("invalid profile name", "Invalid name."));
232             // try again
233             saveProfileAs();
234             continue; // ask again
235         }
236 
237         if (m_profiles.contains(name)) {
238             int ret = KMessageBox::warningYesNo(this,
239                                                 i18n("A profile with that name already exists.\n"
240                                                         "Do you want to overwrite it?"));
241 
242             if (ret == KMessageBox::Yes) {
243                 break;    // exit while loop (save profile)
244             }
245             // else ask again
246         } else {
247             // the name is valid
248             break; // exit while loop (save profile)
249         }
250     }
251 
252     saveProfile(name);
253 }
254 
removeProfile()255 void KarbonCalligraphyOptionWidget::removeProfile()
256 {
257     removeProfile(m_comboBox->currentText());
258 }
259 
toggleUseAngle(bool checked)260 void KarbonCalligraphyOptionWidget::toggleUseAngle(bool checked)
261 {
262     m_angleBox->setEnabled(! checked);
263 }
264 
increaseWidth()265 void KarbonCalligraphyOptionWidget::increaseWidth()
266 {
267     m_widthBox->setValue(m_widthBox->value() + 1);
268 }
269 
decreaseWidth()270 void KarbonCalligraphyOptionWidget::decreaseWidth()
271 {
272     m_widthBox->setValue(m_widthBox->value() - 1);
273 }
274 
increaseAngle()275 void KarbonCalligraphyOptionWidget::increaseAngle()
276 {
277     m_angleBox->setAngle(static_cast<int>(m_angleBox->angle() + 3) % 180);
278 }
279 
decreaseAngle()280 void KarbonCalligraphyOptionWidget::decreaseAngle()
281 {
282     m_angleBox->setAngle(static_cast<int>(m_angleBox->angle() - 3) % 180);
283 }
284 
285 /******************************************************************************
286  ************************* Convenience Functions ******************************
287  ******************************************************************************/
288 
createConnections()289 void KarbonCalligraphyOptionWidget::createConnections()
290 {
291     connect(m_comboBox, SIGNAL(currentIndexChanged(QString)),
292             SLOT(loadProfile(QString)));
293 
294     // propagate changes
295     connect(m_usePath, SIGNAL(toggled(bool)),
296             SIGNAL(usePathChanged(bool)));
297 
298     connect(m_usePressure, SIGNAL(toggled(bool)),
299             SIGNAL(usePressureChanged(bool)));
300 
301     connect(m_useAngle, SIGNAL(toggled(bool)),
302             SIGNAL(useAngleChanged(bool)));
303 
304     connect(m_widthBox, SIGNAL(valueChanged(double)),
305             SIGNAL(widthChanged(double)));
306 
307     connect(m_thinningBox, SIGNAL(valueChanged(double)),
308             SIGNAL(thinningChanged(double)));
309 
310     connect(m_angleBox, SIGNAL(angleChanged(qreal)),
311             SLOT(on_m_angleBox_angleChanged(qreal)));
312 
313     connect(m_fixationBox, SIGNAL(valueChanged(double)),
314             SIGNAL(fixationChanged(double)));
315 
316     connect(m_capsBox, SIGNAL(valueChanged(double)),
317             SIGNAL(capsChanged(double)));
318 
319     connect(m_massBox, SIGNAL(valueChanged(double)),
320             SIGNAL(massChanged(double)));
321 
322     connect(m_dragBox, SIGNAL(valueChanged(double)),
323             SIGNAL(dragChanged(double)));
324 
325     // update profile
326     connect(m_usePath, SIGNAL(toggled(bool)),
327             SLOT(updateCurrentProfile()));
328 
329     connect(m_usePressure, SIGNAL(toggled(bool)),
330             SLOT(updateCurrentProfile()));
331 
332     connect(m_useAngle, SIGNAL(toggled(bool)),
333             SLOT(updateCurrentProfile()));
334 
335     connect(m_widthBox, SIGNAL(valueChanged(double)),
336             SLOT(updateCurrentProfile()));
337 
338     connect(m_thinningBox, SIGNAL(valueChanged(double)),
339             SLOT(updateCurrentProfile()));
340 
341     connect(m_angleBox, SIGNAL(valueChanged(int)),
342             SLOT(updateCurrentProfile()));
343 
344     connect(m_fixationBox, SIGNAL(valueChanged(double)),
345             SLOT(updateCurrentProfile()));
346 
347     connect(m_capsBox, SIGNAL(valueChanged(double)),
348             SLOT(updateCurrentProfile()));
349 
350     connect(m_massBox, SIGNAL(valueChanged(double)),
351             SLOT(updateCurrentProfile()));
352 
353     connect(m_dragBox, SIGNAL(valueChanged(double)),
354             SLOT(updateCurrentProfile()));
355 
356     connect(m_saveButton, SIGNAL(clicked()), SLOT(saveProfileAs()));
357     connect(m_removeButton, SIGNAL(clicked()), SLOT(removeProfile()));
358 
359     // visualization
360     connect(m_useAngle, SIGNAL(toggled(bool)), SLOT(toggleUseAngle(bool)));
361 }
362 
addDefaultProfiles()363 void KarbonCalligraphyOptionWidget::addDefaultProfiles()
364 {
365     // check if the profiles where already added
366     KConfig config(RCFILENAME);
367     KConfigGroup generalGroup(&config, "General");
368 
369     if (generalGroup.readEntry("defaultProfilesAdded", false)) {
370         return;
371     }
372 
373     KConfigGroup profile0(&config, "Profile0");
374     profile0.writeEntry("name", i18n("Mouse"));
375     profile0.writeEntry("usePath", false);
376     profile0.writeEntry("usePressure", false);
377     profile0.writeEntry("useAngle", false);
378     profile0.writeEntry("width", 30.0);
379     profile0.writeEntry("thinning", 0.2);
380     profile0.writeEntry("angle", 30);
381     profile0.writeEntry("fixation", 1.0);
382     profile0.writeEntry("caps", 0.0);
383     profile0.writeEntry("mass", 3.0);
384     profile0.writeEntry("drag", 0.7);
385 
386     KConfigGroup profile1(&config, "Profile1");
387     profile1.writeEntry("name", i18n("Graphics Pen"));
388     profile1.writeEntry("width", 50.0);
389     profile1.writeEntry("usePath", false);
390     profile1.writeEntry("usePressure", false);
391     profile1.writeEntry("useAngle", false);
392     profile1.writeEntry("thinning", 0.2);
393     profile1.writeEntry("angle", 30);
394     profile1.writeEntry("fixation", 1.0);
395     profile1.writeEntry("caps", 0.0);
396     profile1.writeEntry("mass", 1.0);
397     profile1.writeEntry("drag", 0.9);
398 
399     generalGroup.writeEntry("profile", i18n("Mouse"));
400     generalGroup.writeEntry("defaultProfilesAdded", true);
401 
402     config.sync();
403 }
404 
loadProfiles()405 void KarbonCalligraphyOptionWidget::loadProfiles()
406 {
407     KConfig config(RCFILENAME);
408 
409     // load profiles as long as they are present
410     int i = 0;
411     while (1) { // forever
412         KConfigGroup profileGroup(&config, "Profile" + QString::number(i));
413         // invalid profile, assume we reached the last one
414         if (!profileGroup.hasKey("name")) {
415             break;
416         }
417 
418         Profile *profile = new Profile;
419         profile->index = i;
420         profile->name =         profileGroup.readEntry("name", QString());
421         profile->usePath =      profileGroup.readEntry("usePath", false);
422         profile->usePressure =  profileGroup.readEntry("usePressure", false);
423         profile->useAngle =     profileGroup.readEntry("useAngle", false);
424         profile->width =        profileGroup.readEntry("width", 30.0);
425         profile->thinning =     profileGroup.readEntry("thinning", 0.2);
426         profile->angle =        profileGroup.readEntry("angle", 30);
427         profile->fixation =     profileGroup.readEntry("fixation", 0.0);
428         profile->caps =         profileGroup.readEntry("caps", 0.0);
429         profile->mass =         profileGroup.readEntry("mass", 3.0);
430         profile->drag =         profileGroup.readEntry("drag", 0.7);
431 
432         m_profiles.insert(profile->name, profile);
433         ++i;
434     }
435 
436     m_changingProfile = true;
437     ProfileMap::const_iterator it = m_profiles.constBegin();
438     ProfileMap::const_iterator lastIt = m_profiles.constEnd();
439     for (; it != lastIt; ++it) {
440         m_comboBox->addItem(it.key());
441     }
442     m_changingProfile = false;
443 
444     loadCurrentProfile();
445 }
446 
loadCurrentProfile()447 void KarbonCalligraphyOptionWidget::loadCurrentProfile()
448 {
449     KConfig config(RCFILENAME);
450     KConfigGroup generalGroup(&config, "General");
451     QString currentProfile = generalGroup.readEntry("profile", QString());
452     // find the index needed by the comboBox
453     int index = profilePosition(currentProfile);
454 
455     if (currentProfile.isEmpty() || index < 0) {
456         return;
457     }
458 
459     m_comboBox->setCurrentIndex(index);
460 
461     Profile *profile = m_profiles[currentProfile];
462 
463     m_changingProfile = true;
464     m_usePath->setChecked(profile->usePath);
465     m_usePressure->setChecked(profile->usePressure);
466     m_useAngle->setChecked(profile->useAngle);
467     m_widthBox->setValue(profile->width);
468     m_thinningBox->setValue(profile->thinning);
469     m_angleBox->setAngle(profile->angle);
470     m_fixationBox->setValue(profile->fixation);
471     m_capsBox->setValue(profile->caps);
472     m_massBox->setValue(profile->mass);
473     m_dragBox->setValue(profile->drag);
474     m_changingProfile = false;
475 }
476 
saveProfile(const QString & name)477 void KarbonCalligraphyOptionWidget::saveProfile(const QString &name)
478 {
479     Profile *profile = new Profile;
480     profile->name = name;
481     profile->usePath = m_usePath->isChecked();
482     profile->usePressure = m_usePressure->isChecked();
483     profile->useAngle = m_useAngle->isChecked();
484     profile->width = m_widthBox->value();
485     profile->thinning = m_thinningBox->value();
486     profile->angle = static_cast<int>(m_angleBox->angle());
487     profile->fixation = m_fixationBox->value();
488     profile->caps = m_capsBox->value();
489     profile->mass = m_massBox->value();
490     profile->drag = m_dragBox->value();
491 
492     if (m_profiles.contains(name)) {
493         // there is already a profile with the same name, overwrite
494         profile->index = m_profiles[name]->index;
495         m_profiles.insert(name, profile);
496     } else {
497         // it is a new profile
498         profile->index = m_profiles.count();
499         m_profiles.insert(name, profile);
500         // add the profile to the combobox
501         QString dbg;
502         for (int i = 0; i < m_comboBox->count(); ++i) {
503             dbg += m_comboBox->itemText(i) + ' ';
504         }
505         int pos = profilePosition(name);
506         m_changingProfile = true;
507         m_comboBox->insertItem(pos, name);
508         m_changingProfile = false;
509         for (int i = 0; i < m_comboBox->count(); ++i) {
510             dbg += m_comboBox->itemText(i) + ' ';
511         }
512     }
513 
514     KConfig config(RCFILENAME);
515     QString str = "Profile" + QString::number(profile->index);
516     KConfigGroup profileGroup(&config, str);
517 
518     profileGroup.writeEntry("name", name);
519     profileGroup.writeEntry("usePath", profile->usePath);
520     profileGroup.writeEntry("usePressure", profile->usePressure);
521     profileGroup.writeEntry("useAngle", profile->useAngle);
522     profileGroup.writeEntry("width", profile->width);
523     profileGroup.writeEntry("thinning", profile->thinning);
524     profileGroup.writeEntry("angle", profile->angle);
525     profileGroup.writeEntry("fixation", profile->fixation);
526     profileGroup.writeEntry("caps", profile->caps);
527     profileGroup.writeEntry("mass", profile->mass);
528     profileGroup.writeEntry("drag", profile->drag);
529 
530     KConfigGroup generalGroup(&config, "General");
531     generalGroup.writeEntry("profile", name);
532 
533     config.sync();
534 
535     m_comboBox->setCurrentIndex(profilePosition(name));
536 }
537 
removeProfile(const QString & name)538 void KarbonCalligraphyOptionWidget::removeProfile(const QString &name)
539 {
540     int index = profilePosition(name);
541     if (index < 0) {
542         return;    // no such profile
543     }
544 
545     // remove the file from the config file
546     KConfig config(RCFILENAME);
547     int deletedIndex = m_profiles[name]->index;
548     QString deletedGroup = "Profile" + QString::number(deletedIndex);
549     config.deleteGroup(deletedGroup);
550     config.sync();
551 
552     // and from profiles
553     m_profiles.remove(name);
554 
555     m_comboBox->removeItem(index);
556 
557     // now in the config file there is value ProfileN missing,
558     // where N = configIndex, so put the last one there
559     if (m_profiles.isEmpty()) {
560         return;
561     }
562 
563     int lastN = -1;
564     Profile *profile = 0; // profile to be moved, will be the last one
565     Q_FOREACH (Profile *p, m_profiles) {
566         if (p->index > lastN) {
567             lastN = p->index;
568             profile = p;
569         }
570     }
571 
572     Q_ASSERT(profile != 0);
573 
574     // do nothing if the deleted group was the last one
575     if (deletedIndex > lastN) {
576         return;
577     }
578 
579     QString lastGroup = "Profile" + QString::number(lastN);
580     config.deleteGroup(lastGroup);
581 
582     KConfigGroup profileGroup(&config, deletedGroup);
583     profileGroup.writeEntry("name", profile->name);
584     profileGroup.writeEntry("usePath", profile->usePath);
585     profileGroup.writeEntry("usePressure", profile->usePressure);
586     profileGroup.writeEntry("useAngle", profile->useAngle);
587     profileGroup.writeEntry("width", profile->width);
588     profileGroup.writeEntry("thinning", profile->thinning);
589     profileGroup.writeEntry("angle", profile->angle);
590     profileGroup.writeEntry("fixation", profile->fixation);
591     profileGroup.writeEntry("caps", profile->caps);
592     profileGroup.writeEntry("mass", profile->mass);
593     profileGroup.writeEntry("drag", profile->drag);
594     config.sync();
595 
596     profile->index = deletedIndex;
597 }
598 
profilePosition(const QString & profileName)599 int KarbonCalligraphyOptionWidget::profilePosition(const QString &profileName)
600 {
601     int res = 0;
602     ProfileMap::const_iterator it = m_profiles.constBegin();
603     ProfileMap::const_iterator lastIt = m_profiles.constEnd();
604     for (; it != lastIt; ++it) {
605         if (it.key() == profileName) {
606             return res;
607         }
608         ++res;
609     }
610     return -1;
611 }
612 
setUsePathEnabled(bool enabled)613 void KarbonCalligraphyOptionWidget::setUsePathEnabled(bool enabled)
614 {
615     m_usePath->setEnabled(enabled);
616 }
617 
on_m_angleBox_angleChanged(qreal angle)618 void KarbonCalligraphyOptionWidget::on_m_angleBox_angleChanged(qreal angle)
619 {
620     emit angleChanged(static_cast<int>(angle));
621 }
622