1 // Copyright (C) 2012-2019 The VPaint Developers.
2 // See the COPYRIGHT file at the top-level directory of this distribution
3 // and at https://github.com/dalboris/vpaint/blob/master/COPYRIGHT
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #include "BackgroundWidget.h"
18 
19 #include "Background.h"
20 #include "BackgroundUrlValidator.h"
21 #include "../ColorSelector.h"
22 #include "../Global.h"
23 
24 #include <QFormLayout>
25 #include <QHBoxLayout>
26 #include <QVBoxLayout>
27 #include <QGridLayout>
28 #include <QLabel>
29 #include <QLineEdit>
30 #include <QPushButton>
31 #include <QCheckBox>
32 #include <QDoubleSpinBox>
33 #include <QComboBox>
34 #include <QFileDialog>
35 #include <QTextEdit>
36 #include <QDialogButtonBox>
37 
BackgroundWidget(QWidget * parent)38 BackgroundWidget::BackgroundWidget(QWidget * parent) :
39     QWidget(parent),
40     background_(nullptr),
41     isUpdatingFromBackground_(false),
42     isBeingEdited_(false)
43 {
44     // Clarify that there is one background per layer
45     QVBoxLayout * whichLayerAndFormLayout = new QVBoxLayout();
46     setLayout(whichLayerAndFormLayout);
47     whichLayerAndFormLayout->addWidget(new QLabel(tr("(Note: each layer has its own background)")));
48 
49     // Form layout (contains everything except the clarification note above)
50     QFormLayout * layout = new QFormLayout();
51     whichLayerAndFormLayout->addLayout(layout);
52     whichLayerAndFormLayout->addStretch();
53 
54     // Color
55     colorSelector_ = new ColorSelector(Qt::transparent);
56     colorSelector_->setToolTip(tr("Set background color"));
57     colorSelector_->setStatusTip(tr("Set background color, possibly transparent."));
58     layout->addRow(tr("Color:"), colorSelector_);
59     connect(colorSelector_, SIGNAL(colorChanged(Color)),
60             this, SLOT(processColorSelectorColorChanged_(Color)));
61 
62     // Images
63     imageLineEdit_ = new QLineEdit();
64     imageLineEdit_->setValidator(new BackgroundUrlValidator(imageLineEdit_));
65     imageLineEdit_->setToolTip(tr("Set background image(s) url\n\n"
66                                   "Example 1: 'image.png' for the same image at all frames\n"
67                                   "Example 2: 'image*.png' for 'image2.png' on frame 2, etc."));
68     imageLineEdit_->setStatusTip(tr("Set background image(s) url. For example, set "
69                                     "'image.png' for a fixed image shared across all frames, "
70                                     ", or set 'image*.png' for 'image1.png' at frame 1, "
71                                     "'image2.png' at frame 2, etc. Paths must be relative to "
72                                     "where the vec file is saved."));
73     imageBrowseButton_ = new QPushButton("...");
74     imageBrowseButton_->setToolTip(tr("Browse for background image(s)"));
75     imageBrowseButton_->setStatusTip(tr("Browse for background image(s). Select two or more files, "
76                                         "and a pattern of the form 'image*.png' will be automatically "
77                                         "detected, loading all images matching patterns even if not selected."));
78     imageBrowseButton_->setMaximumWidth(30);
79     imageRefreshButton_ = new QPushButton(QIcon(":/images/refresh.png"), tr(""));
80     imageRefreshButton_->setToolTip(tr("Reload background image(s)"));
81     imageRefreshButton_->setStatusTip(tr("Reload background image(s) to reflect changes on disk."));
82     imageRefreshButton_->setMaximumWidth(30);
83     QHBoxLayout * imagesLayout = new QHBoxLayout();
84 #ifdef Q_OS_MAC
85     imagesLayout->setSpacing(10);
86 #else
87     imagesLayout->setSpacing(0);
88 #endif
89     imagesLayout->addWidget(imageLineEdit_);
90     imagesLayout->addWidget(imageBrowseButton_);
91     imagesLayout->addWidget(imageRefreshButton_);
92     layout->addRow(tr("Image(s):"), imagesLayout);
93     connect(imageLineEdit_, SIGNAL(editingFinished()),
94             this, SLOT(processImageLineEditEditingFinished_()));
95     connect(imageBrowseButton_, SIGNAL(clicked(bool)),
96             this, SLOT(processImageBrowseButtonClicked_()));
97     connect(imageRefreshButton_, SIGNAL(clicked(bool)),
98             this, SLOT(processImageRefreshButtonClicked_()));
99 
100     // Position
101     leftSpinBox_ = new QDoubleSpinBox();
102     leftSpinBox_->setToolTip(tr("X coordinate of top-left corner of background image(s)"));
103     leftSpinBox_->setStatusTip(tr("Set the X coordinate of the position of the top-left corner of background image(s)."));
104     leftSpinBox_->setMaximumWidth(80);
105     leftSpinBox_->setMinimum(-1e6);
106     leftSpinBox_->setMaximum(1e6);
107     topSpinBox_ = new QDoubleSpinBox();
108     topSpinBox_->setToolTip(tr("Y coordinate of top-left corner of background image(s)"));
109     topSpinBox_->setStatusTip(tr("Set the Y coordinate of the position of the top-left corner of background image(s)."));
110     topSpinBox_->setMaximumWidth(80);
111     topSpinBox_->setMinimum(-1e6);
112     topSpinBox_->setMaximum(1e6);
113     QHBoxLayout * positionLayout = new QHBoxLayout();
114     positionLayout->addWidget(leftSpinBox_);
115     positionLayout->addWidget(topSpinBox_);
116     layout->addRow(tr("Position:"), positionLayout);
117     connect(leftSpinBox_, SIGNAL(valueChanged(double)),
118             this, SLOT(processLeftSpinBoxValueChanged_(double)));
119     connect(topSpinBox_, SIGNAL(valueChanged(double)),
120             this, SLOT(processTopSpinBoxValueChanged_(double)));
121     connect(leftSpinBox_, SIGNAL(editingFinished()),
122             this, SLOT(processLeftSpinBoxEditingFinished_()));
123     connect(topSpinBox_, SIGNAL(editingFinished()),
124             this, SLOT(processTopSpinBoxEditingFinished_()));
125 
126     // Size
127     sizeComboBox_ = new QComboBox();
128     sizeComboBox_->setToolTip(tr("Set size of background image(s)"));
129     sizeComboBox_->setStatusTip(tr("Set the size of background image(s)."));
130     sizeComboBox_->addItem(tr("Fit to canvas"));
131     sizeComboBox_->addItem(tr("Manual"));
132     sizeComboBox_->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred));
133     widthSpinBox_ = new QDoubleSpinBox();
134     widthSpinBox_->setToolTip(tr("Width of background image(s)"));
135     widthSpinBox_->setStatusTip(tr("Set width of background image(s)."));
136     widthSpinBox_->setMaximumWidth(80);
137     widthSpinBox_->setMinimum(-1e6);
138     widthSpinBox_->setMaximum(1e6);
139     widthSpinBox_->setValue(1280);
140     heightSpinBox_ = new QDoubleSpinBox();
141     heightSpinBox_->setToolTip(tr("Height of background image(s)"));
142     heightSpinBox_->setStatusTip(tr("Set height of background image(s)."));
143     heightSpinBox_->setMaximumWidth(80);
144     heightSpinBox_->setMinimum(-1e6);
145     heightSpinBox_->setMaximum(1e6);
146     heightSpinBox_->setValue(720);
147     QGridLayout * sizeLayout = new QGridLayout();
148     sizeLayout->addWidget(sizeComboBox_, 0, 0, 1, 2);
149     sizeLayout->addWidget(widthSpinBox_, 1, 0);
150     sizeLayout->addWidget(heightSpinBox_, 1, 1);
151     layout->addRow(tr("Size:"), sizeLayout);
152     connect(sizeComboBox_, SIGNAL(currentIndexChanged(int)),
153             this, SLOT(processSizeComboBoxCurrentIndexChanged_(int)));
154     connect(widthSpinBox_, SIGNAL(valueChanged(double)),
155             this, SLOT(processWidthSpinBoxValueChanged_(double)));
156     connect(heightSpinBox_, SIGNAL(valueChanged(double)),
157             this, SLOT(processHeightSpinBoxValueChanged_(double)));
158     connect(widthSpinBox_, SIGNAL(editingFinished()),
159             this, SLOT(processWidthSpinBoxEditingFinished_()));
160     connect(heightSpinBox_, SIGNAL(editingFinished()),
161             this, SLOT(processHeightSpinBoxEditingFinished_()));
162 
163     // Repeat
164     repeatComboBox_ = new QComboBox();
165     repeatComboBox_->setToolTip(tr("Repeat background image(s)"));
166     repeatComboBox_->setStatusTip(tr("Set whether background image(s) should "
167                                      "be repeated, either horizontally, vertically, or both"));
168     repeatComboBox_->addItem(tr("No"));
169     repeatComboBox_->addItem(tr("Horizontally"));
170     repeatComboBox_->addItem(tr("Vertically"));
171     repeatComboBox_->addItem(tr("Both"));
172     repeatComboBox_->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred));
173     layout->addRow(tr("Repeat:"), repeatComboBox_);
174     connect(repeatComboBox_, SIGNAL(currentIndexChanged(int)),
175             this, SLOT(processRepeatComboBoxCurrentIndexChanged_(int)));
176 
177     // Opacity
178     opacitySpinBox_ = new QDoubleSpinBox();
179     opacitySpinBox_->setToolTip(tr("Opacity of background image(s)"));
180     opacitySpinBox_->setStatusTip(tr("Set the opacity of background image(s). Note: this does "
181                                      "not affect the opacity of the background color (use an alpha "
182                                      "value for the color instead)."));
183     opacitySpinBox_->setMaximumWidth(80);
184     opacitySpinBox_->setMinimum(0);
185     opacitySpinBox_->setMaximum(1);
186     opacitySpinBox_->setSingleStep(0.1);
187     opacitySpinBox_->setValue(1.0);
188     layout->addRow(tr("Opacity:"), opacitySpinBox_);
189     connect(opacitySpinBox_, SIGNAL(valueChanged(double)),
190             this, SLOT(processOpacitySpinBoxValueChanged_(double)));
191     connect(opacitySpinBox_, SIGNAL(editingFinished()),
192             this, SLOT(processOpacitySpinBoxEditingFinished_()));
193 
194     // Hold
195     holdCheckBox_ = new QCheckBox();
196     holdCheckBox_->setToolTip(tr("Hold background image(s)"));
197     holdCheckBox_->setStatusTip(tr("Set whether to hold background image(s). Example: 'image*.png'"
198                                    " with only 'image01.png' and 'image03.png' on disk. At "
199                                    "frame 2, if hold is checked, 'image01.png' appears. If hold is "
200                                    "not checked, no image appears, unless 'image.png' exists in which "
201                                    "case it is used as a fallback value."));
202     holdCheckBox_->setChecked(true );
203     layout->addRow(tr("Hold:"), holdCheckBox_);
204     connect(holdCheckBox_, SIGNAL(toggled(bool)),
205             this, SLOT(processHoldCheckBoxToggled_(bool)));
206 
207     // Set no background
208     setBackground(nullptr);
209 }
210 
setBackground(Background * background)211 void BackgroundWidget::setBackground(Background * background)
212 {
213     // Disconnect previous connections. This assumes that background_
214     // is either nullptr or not destroyed yet, which is ensured by
215     // onBackgroundDestroyed_().
216     if (background_)
217     {
218         background_->disconnect(this);
219     }
220 
221     // Store value
222     background_ = background;
223 
224     // Disable all widgets if background_ == NULL
225     bool areChildrenEnabled = background_ ? true : false;
226     QList<QWidget*> children = findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly);
227     foreach (QWidget * w, children)
228         w->setEnabled(areChildrenEnabled);
229 
230     // Set widgets values from background values
231     updateFromBackground_();
232 
233     // Update widget values when data changes
234     if (background_)
235     {
236         connect(background_, SIGNAL(destroyed()), this, SLOT(onBackgroundDestroyed_()));
237         connect(background_, SIGNAL(changed()), this, SLOT(updateFromBackground_()));
238     }
239 }
240 
updateFromBackground_()241 void BackgroundWidget::updateFromBackground_()
242 {
243     if (background_)
244     {
245         // Set guard
246         isUpdatingFromBackground_ = true;
247 
248         // Color
249         colorSelector_->setColor(background_->color());
250 
251         // Image
252         imageLineEdit_->setText(background_->imageUrl());
253 
254         // Position
255         leftSpinBox_->setValue(background_->position()[0]);
256         topSpinBox_->setValue(background_->position()[1]);
257 
258         // Size
259         sizeComboBox_->setCurrentIndex((int) background_->sizeType());
260         widthSpinBox_->setValue(background_->size()[0]);
261         heightSpinBox_->setValue(background_->size()[1]);
262         switch (background_->sizeType())
263         {
264         case Background::SizeType::Cover:
265             widthSpinBox_->hide();
266             heightSpinBox_->hide();
267             break;
268         case Background::SizeType::Manual:
269             widthSpinBox_->show();
270             heightSpinBox_->show();
271             break;
272         }
273 
274         // Repeat
275         repeatComboBox_->setCurrentIndex((int) background_->repeatType());
276 
277         // Opacity
278         opacitySpinBox_->setValue(background_->opacity());
279 
280         // Hold
281         holdCheckBox_->setChecked(background_->hold());
282 
283         // Cache value before editing
284         if (!isBeingEdited_)
285         {
286             dataBeforeEditing_ = background_->data();
287         }
288 
289         // Unset guard
290         isUpdatingFromBackground_ = false;
291     }
292 }
293 
onBackgroundDestroyed_()294 void BackgroundWidget::onBackgroundDestroyed_()
295 {
296     background_ = nullptr;
297     setBackground(nullptr);
298 }
299 
background() const300 Background * BackgroundWidget::background() const
301 {
302     return background_;
303 }
304 
processColorSelectorColorChanged_(const Color & newColor)305 void BackgroundWidget::processColorSelectorColorChanged_(const Color & newColor)
306 {
307     if (background_ && !isUpdatingFromBackground_)
308     {
309         isBeingEdited_ = true;
310         background_->setColor(newColor);
311         isBeingEdited_ = false;
312         emitCheckpoint_();
313     }
314 }
315 
processImageLineEditEditingFinished_()316 void BackgroundWidget::processImageLineEditEditingFinished_()
317 {
318     if (background_ && !isUpdatingFromBackground_)
319     {
320         isBeingEdited_ = true;
321         background_->setImageUrl(imageLineEdit_->text());
322         isBeingEdited_ = false;
323         emitCheckpoint_();
324     }
325 }
326 
327 
328 namespace
329 {
330 class InconsistentFileNamesDialog: public QDialog
331 {
332 public:
InconsistentFileNamesDialog(QWidget * parent=0)333     InconsistentFileNamesDialog(QWidget * parent = 0) :
334         QDialog(parent)
335     {
336         // Window title
337         setWindowTitle("Inconsistent file names");
338 
339         // Label with warning message and pattern
340         label_ = new QLabel;
341         label_->setWordWrap(true);
342 
343         // Text edit with list of inconsistent file names
344         textEdit_ = new QTextEdit;
345         textEdit_->setReadOnly(true);
346 
347         // Button box
348         QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
349         connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
350 
351         // Layout
352         QVBoxLayout * layout = new QVBoxLayout(this);
353         layout->addWidget(label_);
354         layout->addWidget(textEdit_);
355         layout->addWidget(buttonBox);
356         setLayout(layout);
357     }
358 
setPattern(const QString & pattern)359     void setPattern(const QString & pattern)
360     {
361         label_->setText(
362             tr("Warning: The selected files do not have consistent names. "
363                "The detected pattern is \"%1\", but the following files "
364                "do not match it and therefore will be ignored:").arg(pattern));
365     }
366 
setFileNames(const QStringList & fileNames)367     void setFileNames(const QStringList & fileNames)
368     {
369         QString text;
370         for (int i=0; i<fileNames.size(); ++i)
371         {
372             if (i>0)
373                 text += '\n';
374             text += fileNames[i];
375         }
376         textEdit_->setText(text);
377     }
378 
379 private:
380     QLabel * label_;
381     QTextEdit * textEdit_;
382 };
383 }
384 
processImageBrowseButtonClicked_()385 void BackgroundWidget::processImageBrowseButtonClicked_()
386 {
387     // Get filenames
388     QDir documentDir = global()->documentDir();
389     QStringList filenames = QFileDialog::getOpenFileNames(
390                 this,
391                 tr("Select image, or sequence of images, to set as background"),
392                 documentDir.path(),
393                 tr("Image files (*.jpg *.png)"));
394 
395 
396     // Convert to path relative to current document
397     for (int i=0; i<filenames.size(); ++i)
398     {
399         filenames[i] = documentDir.relativeFilePath(filenames[i]);
400     }
401 
402     // Detect wildcard
403     QString url;
404     if (filenames.size() == 0)
405     {
406         url = QString();
407     }
408     else if (filenames.size() == 1)
409     {
410         url = filenames[0];
411     }
412     else // filenames.size() >= 2
413     {
414         // Compute largest shared prefix of first two filenames
415         const QString & s0 = filenames[0];
416         const QString & s1 = filenames[1];
417         int prefixLength = 0;
418         while (s0.length() > prefixLength &&
419                s1.length() > prefixLength &&
420                s0[prefixLength] == s1[prefixLength])
421         {
422             prefixLength++;
423         }
424 
425         // Chop digits at the end of prefix
426         while (prefixLength > 0 &&
427                s0[prefixLength-1].isDigit())
428         {
429             prefixLength--;
430         }
431 
432         // Chop minus sign, unless all filenames have one, in which case it's
433         // probably intented to be a separating dash and not a minus sign
434         if (prefixLength > 0 && s0[prefixLength-1] == '-')
435         {
436             bool theyAllHaveIt = true;
437             for (int i=0; i<filenames.size(); ++i)
438             {
439                 if ( (filenames[i].length() < prefixLength) ||
440                      (filenames[i][prefixLength-1] != '-' )    )
441                 {
442                     theyAllHaveIt = false;
443                     break;
444                 }
445             }
446 
447             if (!theyAllHaveIt)
448                 prefixLength--;
449         }
450 
451         // Read wildcard of s0
452         int s0WildcardLength = 0;
453         if (s0.length() == prefixLength)
454         {
455             // That's weird, but might be the fallback value with
456             // a wildcard at the end (i.e., without file extension)
457             s0WildcardLength = 0;
458         }
459         else if (s0[prefixLength] == '-')
460         {
461             // s0 wildcard is negative
462             s0WildcardLength++;
463             while (s0.length() > prefixLength+s0WildcardLength &&
464                    s0[prefixLength+s0WildcardLength].isDigit())
465             {
466                 s0WildcardLength++;
467             }
468         }
469         else if (s0[prefixLength].isDigit())
470         {
471             // s0 wildcard is positive
472             while (s0.length() > prefixLength+s0WildcardLength &&
473                    s0[prefixLength+s0WildcardLength].isDigit())
474             {
475                 s0WildcardLength++;
476             }
477         }
478         else
479         {
480             // Might be the fallback value
481             s0WildcardLength = 0;
482         }
483 
484         // Deduce prefix and suffix
485         int suffixLength = s0.length() - prefixLength - s0WildcardLength;
486         QString prefix = s0.left(prefixLength);
487         QString suffix = s0.right(suffixLength);
488 
489         // Set url
490         url = prefix + "*" + suffix;
491 
492         // Check for inconsistent names
493         QStringList inconsistentFilenames;
494         for (int i=0; i<filenames.size(); ++i)
495         {
496             // Check that prefix and suffix match
497             if (filenames[i].left(prefixLength)  != prefix ||
498                 filenames[i].right(suffixLength) != suffix )
499             {
500                 inconsistentFilenames << filenames[i];
501             }
502 
503             // Check that wildcard can be converted to an int
504             else
505             {
506                 // Get wildcard and convert to int
507                 QString w = filenames[i].mid(prefixLength, filenames[i].length() - url.length() + 1);
508                 bool canConvertToInt;
509                 w.toInt(&canConvertToInt);
510 
511                 // Add to inconsistent names if it cannot be converted to an int
512                 // (unless length == 0, in which case it's the fallback value)
513                 if (w.length() > 0 && !canConvertToInt)
514                 {
515                     inconsistentFilenames << filenames[i];
516                 }
517             }
518         }
519 
520         // Display warning to user if some file names are inconsistent
521         if (inconsistentFilenames.length() > 0)
522         {
523             // issue warning
524             InconsistentFileNamesDialog dialog(this);
525             dialog.setPattern(url);
526             dialog.setFileNames(inconsistentFilenames);
527             dialog.exec();
528         }
529     }
530 
531     // Set image url
532     if (background_ && !isUpdatingFromBackground_)
533     {
534         isBeingEdited_ = true;
535         background_->setImageUrl(url);
536         isBeingEdited_ = false;
537         emitCheckpoint_();
538     }
539 }
540 
processImageRefreshButtonClicked_()541 void BackgroundWidget::processImageRefreshButtonClicked_()
542 {
543     if (background_)
544     {
545         background_->clearCache();
546     }
547 }
548 
processLeftSpinBoxValueChanged_(double newLeft)549 void BackgroundWidget::processLeftSpinBoxValueChanged_(double newLeft)
550 {
551     if (background_ && !isUpdatingFromBackground_)
552     {
553         isBeingEdited_ = true;
554         double top = background_->position()[1];
555         background_->setPosition(Eigen::Vector2d(newLeft, top));
556         isBeingEdited_ = false;
557     }
558 }
559 
processTopSpinBoxValueChanged_(double newTop)560 void BackgroundWidget::processTopSpinBoxValueChanged_(double newTop)
561 {
562     if (background_ && !isUpdatingFromBackground_)
563     {
564         isBeingEdited_ = true;
565         double left = background_->position()[0];
566         background_->setPosition(Eigen::Vector2d(left, newTop));
567         isBeingEdited_ = false;
568     }
569 }
570 
processLeftSpinBoxEditingFinished_()571 void BackgroundWidget::processLeftSpinBoxEditingFinished_()
572 {
573     emitCheckpoint_();
574 }
575 
processTopSpinBoxEditingFinished_()576 void BackgroundWidget::processTopSpinBoxEditingFinished_()
577 {
578     emitCheckpoint_();
579 }
580 
processSizeComboBoxCurrentIndexChanged_(int newSizeType)581 void BackgroundWidget::processSizeComboBoxCurrentIndexChanged_(int newSizeType)
582 {
583     if (background_ && !isUpdatingFromBackground_)
584     {
585         isBeingEdited_ = true;
586         background_->setSizeType(static_cast<Background::SizeType>(newSizeType));
587         isBeingEdited_ = false;
588         emitCheckpoint_();
589     }
590 }
591 
processWidthSpinBoxValueChanged_(double newWidth)592 void BackgroundWidget::processWidthSpinBoxValueChanged_(double newWidth)
593 {
594     if (background_ && !isUpdatingFromBackground_)
595     {
596         isBeingEdited_ = true;
597         double height = background_->size()[1];
598         background_->setSize(Eigen::Vector2d(newWidth, height));
599         isBeingEdited_ = false;
600     }
601 }
602 
processHeightSpinBoxValueChanged_(double newHeight)603 void BackgroundWidget::processHeightSpinBoxValueChanged_(double newHeight)
604 {
605     if (background_ && !isUpdatingFromBackground_)
606     {
607         isBeingEdited_ = true;
608         double width = background_->size()[0];
609         background_->setSize(Eigen::Vector2d(width, newHeight));
610         isBeingEdited_ = false;
611     }
612 }
613 
processWidthSpinBoxEditingFinished_()614 void BackgroundWidget::processWidthSpinBoxEditingFinished_()
615 {
616     emitCheckpoint_();
617 }
618 
processHeightSpinBoxEditingFinished_()619 void BackgroundWidget::processHeightSpinBoxEditingFinished_()
620 {
621     emitCheckpoint_();
622 }
623 
processRepeatComboBoxCurrentIndexChanged_(int newRepeatType)624 void BackgroundWidget::processRepeatComboBoxCurrentIndexChanged_(int newRepeatType)
625 {
626     if (background_ && !isUpdatingFromBackground_)
627     {
628         isBeingEdited_ = true;
629         background_->setRepeatType(static_cast<Background::RepeatType>(newRepeatType));
630         isBeingEdited_ = false;
631         emitCheckpoint_();
632     }
633 }
634 
processOpacitySpinBoxValueChanged_(double newOpacity)635 void BackgroundWidget::processOpacitySpinBoxValueChanged_(double newOpacity)
636 {
637     if (background_ && !isUpdatingFromBackground_)
638     {
639         isBeingEdited_ = true;
640         background_->setOpacity(newOpacity);
641         isBeingEdited_ = false;
642     }
643 }
644 
processOpacitySpinBoxEditingFinished_()645 void BackgroundWidget::processOpacitySpinBoxEditingFinished_()
646 {
647     emitCheckpoint_();
648 }
649 
processHoldCheckBoxToggled_(bool newHold)650 void BackgroundWidget::processHoldCheckBoxToggled_(bool newHold)
651 {
652     if (background_ && !isUpdatingFromBackground_)
653     {
654         isBeingEdited_ = true;
655         background_->setHold(newHold);
656         isBeingEdited_ = false;
657         emitCheckpoint_();
658     }
659 }
660 
emitCheckpoint_()661 void BackgroundWidget::emitCheckpoint_()
662 {
663     if (background_ && (background_->data() != dataBeforeEditing_))
664     {
665         dataBeforeEditing_ = background_->data();
666         background_->emitCheckpoint();
667     }
668 }
669