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