1
2
3 #include "adjustthicknesspopup.h"
4
5 // Tnz6 includes
6 #include "tapp.h"
7 #include "menubarcommandids.h"
8 #include "cellselection.h"
9 #include "filmstripselection.h"
10 #include "columnselection.h"
11
12 // TnzTools includes
13 #include "tools/strokeselection.h"
14 #include "tools/levelselection.h"
15
16 // TnzQt includes
17 #include "toonzqt/tselectionhandle.h"
18 #include "toonzqt/icongenerator.h"
19 #include "toonzqt/planeviewer.h"
20 #include "toonzqt/doublefield.h"
21
22 // TnzLib includes
23 #include "toonz/txsheet.h"
24 #include "toonz/txshcell.h"
25 #include "toonz/txshsimplelevel.h"
26 #include "toonz/stage.h"
27 #include "toonz/txshlevelcolumn.h"
28 #include "toonz/txsheethandle.h"
29 #include "toonz/tframehandle.h"
30 #include "toonz/tcolumnhandle.h"
31 #include "toonz/txshlevelhandle.h"
32
33 // TnzCore includes
34 #include "tundo.h"
35 #include "tstroke.h"
36 #include "tstrokeutil.h"
37
38 // Qt includes
39 #include <QTimer>
40 #include <QGridLayout>
41 #include <QLabel>
42 #include <QPushButton>
43 #include <QComboBox>
44 #include <QSplitter>
45 #include <QScrollArea>
46 #include <QMainWindow>
47
48 // tcg includes
49 #include "tcg/tcg_numeric_ops.h"
50
51 // boost includes
52 #include <boost/iterator/counting_iterator.hpp>
53 #include <boost/iterator/filter_iterator.hpp>
54
55 //**************************************************************************
56 // Local namespace stuff
57 //**************************************************************************
58
59 namespace {
60
61 static const double dmax = (std::numeric_limits<double>::max)(), dmin = -dmax;
62
63 //--------------------------------------------------------------
64
65 struct TransformFunc {
66 TVectorImage &m_vi;
67 const double (&m_transform)[2];
68
69 std::vector<int> m_changedStrokeIdxs;
70 std::vector<TStroke *> m_changedStrokes;
71
~TransformFunc__anone3633a5a0111::TransformFunc72 ~TransformFunc() //! Recalculates m_vi's regions.
73 {
74 m_vi.notifyChangedStrokes(m_changedStrokeIdxs, m_changedStrokes);
75 }
76
operator ()__anone3633a5a0111::TransformFunc77 void operator()(
78 int s) //! Transforms the stroke thickness, and marks the specified
79 //! stroke as \a modified.
80 {
81 TStroke *stroke = m_vi.getStroke(s);
82
83 if (stroke->getMaxThickness() > 0.0) {
84 ::transform_thickness(*stroke, m_transform, 2);
85
86 m_changedStrokeIdxs.push_back(s);
87 m_changedStrokes.push_back(stroke);
88 }
89 }
90 };
91
92 //--------------------------------------------------------------
93
transformThickness_image(const TVectorImageP & vi,const double (& transform)[2])94 void transformThickness_image(const TVectorImageP &vi,
95 const double (&transform)[2]) {
96 assert(vi);
97 if (transform[0] == 0.0 &&
98 transform[1] == 1.0) // Bail out if transform is the identity
99 return; //
100
101 TransformFunc transformer = {*vi, transform};
102
103 std::for_each(boost::counting_iterator<int>(0),
104 boost::counting_iterator<int>(vi->getStrokeCount()),
105 transformer);
106 }
107
108 //--------------------------------------------------------------
109
transformThickness_strokes(const TVectorImageP & vi,const double (& transform)[2],const int strokesSelection[],int strokesSelectionCount)110 void transformThickness_strokes(const TVectorImageP &vi,
111 const double (&transform)[2],
112 const int strokesSelection[],
113 int strokesSelectionCount) {
114 assert(vi);
115 if (transform[0] == 0.0 &&
116 transform[1] == 1.0) // Bail out if transform is the identity
117 return; //
118
119 TransformFunc transformer = {*vi, transform};
120
121 std::for_each(strokesSelection, strokesSelection + strokesSelectionCount,
122 transformer);
123 }
124
125 //--------------------------------------------------------------
126
127 namespace {
128
129 struct StylesFilter {
130 const TVectorImage &m_vi;
131 const int *m_stylesStart, *m_stylesEnd;
132
operator ()__anone3633a5a0111::__anone3633a5a0211::StylesFilter133 bool operator()(int strokeIdx) const {
134 int strokeStyle = m_vi.getStroke(strokeIdx)->getStyle();
135 return std::binary_search(m_stylesStart, m_stylesEnd, strokeStyle);
136 }
137 };
138
139 } // namespace
140
transformThickness_styles(const TVectorImageP & vi,const double (& transform)[2],const int stylesSelection[],int stylesSelectionCount)141 void transformThickness_styles(const TVectorImageP &vi,
142 const double (&transform)[2],
143 const int stylesSelection[],
144 int stylesSelectionCount) {
145 assert(vi);
146 if (transform[0] == 0.0 &&
147 transform[1] == 1.0) // Bail out if transform is the identity
148 return; //
149
150 TransformFunc transformer = {*vi, transform};
151 StylesFilter filter = {*vi, stylesSelection,
152 stylesSelection + stylesSelectionCount};
153
154 boost::counting_iterator<int> sBegin(0), sEnd(vi->getStrokeCount());
155
156 std::for_each(boost::make_filter_iterator(filter, sBegin, sEnd),
157 boost::make_filter_iterator(filter, sEnd, sEnd), transformer);
158 }
159
160 //------------------------------------------------------------------------
161
xsheet()162 TXsheet *xsheet() { return TApp::instance()->getCurrentXsheet()->getXsheet(); }
163
164 //------------------------------------------------------------------------
165
frame()166 double frame() { return TApp::instance()->getCurrentFrame()->getFrame(); }
167
168 //------------------------------------------------------------------------
169
column()170 int column() { return TApp::instance()->getCurrentColumn()->getColumnIndex(); }
171
172 //------------------------------------------------------------------------
173
levelFid()174 TFrameId levelFid() { return TApp::instance()->getCurrentFrame()->getFid(); }
175
176 //--------------------------------------------------------------
177
simpleLevel()178 TXshSimpleLevel *simpleLevel() {
179 return TApp::instance()->getCurrentLevel()->getSimpleLevel();
180 }
181
182 //--------------------------------------------------------------
183
currentLevelIndex()184 std::pair<TXshSimpleLevel *, int> currentLevelIndex() {
185 TFrameHandle *frameHandle = TApp::instance()->getCurrentFrame();
186
187 TXshSimpleLevel *sl = 0;
188 TFrameId fid;
189
190 if (frameHandle->getFrameType() == TFrameHandle::SceneFrame) {
191 const TXshCell &cell = xsheet()->getCell(frame(), column());
192
193 sl = cell.getSimpleLevel();
194 fid = cell.getFrameId();
195 } else {
196 sl = simpleLevel();
197 fid = levelFid();
198 }
199
200 return std::make_pair(sl, sl ? sl->fid2index(fid) : -1);
201 }
202
203 //--------------------------------------------------------------
204
relativePosition(int start,int end,int pos)205 double relativePosition(int start, int end, int pos) {
206 return tcrop((start == end) ? 0.0 : double(pos - start) / double(end - start),
207 0.0, 1.0);
208 }
209
210 } // namespace
211
212 //**************************************************************************
213 // Adjust Thickness Swatch
214 //**************************************************************************
215
216 class AdjustThicknessPopup::Swatch final : public PlaneViewer {
217 TVectorImageP m_vi;
218
219 public:
Swatch(QWidget * parent=0)220 Swatch(QWidget *parent = 0) : PlaneViewer(parent) {
221 setBgColor(TPixel32::White, TPixel32::White);
222 }
223
image() const224 TVectorImageP image() const { return m_vi; }
image()225 TVectorImageP &image() { return m_vi; }
226
paintGL()227 void paintGL() override {
228 drawBackground();
229
230 if (m_vi) {
231 glEnable(GL_BLEND);
232 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
233
234 // Note GL_ONE instead of GL_SRC_ALPHA: it's needed since the input
235 // image is supposedly premultiplied - and it works because the
236 // viewer's background is opaque.
237 // See tpixelutils.h's overPixT function for comparison.
238
239 pushGLWorldCoordinates();
240 draw(m_vi);
241 popGLCoordinates();
242
243 glDisable(GL_BLEND);
244 }
245 }
246 };
247
248 //**************************************************************************
249 // FrameData implementation
250 //**************************************************************************
251
FrameData()252 AdjustThicknessPopup::FrameData::FrameData() : m_frameIdx(-1) {}
253
254 //--------------------------------------------------------------
255
FrameData(const TXshSimpleLevelP & sl,int frameIdx)256 AdjustThicknessPopup::FrameData::FrameData(const TXshSimpleLevelP &sl,
257 int frameIdx)
258 : m_sl(sl), m_frameIdx(frameIdx) {}
259
260 //--------------------------------------------------------------
261
getCurrent()262 AdjustThicknessPopup::FrameData AdjustThicknessPopup::FrameData::getCurrent() {
263 const std::pair<TXshSimpleLevel *, int> &data = ::currentLevelIndex();
264
265 FrameData fd;
266 fd.m_sl = data.first, fd.m_frameIdx = data.second;
267
268 return fd;
269 }
270
271 //--------------------------------------------------------------
272
operator ==(const FrameData & other) const273 bool AdjustThicknessPopup::FrameData::operator==(const FrameData &other) const {
274 return (m_sl == other.m_sl) && (m_frameIdx == other.m_frameIdx);
275 }
276
277 //--------------------------------------------------------------
278
image() const279 TVectorImageP AdjustThicknessPopup::FrameData::image() const {
280 return m_sl ? TVectorImageP(m_sl->getFullsampledFrame(
281 m_sl->index2fid(m_frameIdx), false))
282 : TVectorImageP();
283 }
284
285 //**************************************************************************
286 // SelectionData implementation
287 //**************************************************************************
288
SelectionData()289 AdjustThicknessPopup::SelectionData::SelectionData()
290 : m_contentType(NONE), m_sl() {}
291
292 //--------------------------------------------------------------
293
SelectionData(const TSelection * sel)294 AdjustThicknessPopup::SelectionData::SelectionData(const TSelection *sel)
295 : m_contentType(NONE), m_sl() {
296 struct Locals {
297 SelectionData *m_this;
298
299 void resetIfInvalid() // Resets to empty if thickness adjustment is
300 { // not applicable:
301 if (!m_this->m_sl) // 1. The level is not a VECTOR level
302 { // 2. There is no selected frame
303 assert(!*m_this);
304 return;
305 }
306
307 if (m_this->m_sl->getType() != PLI_XSHLEVEL) {
308 *m_this = SelectionData();
309 return;
310 }
311
312 switch (m_this->m_framesType) {
313 case ALL_FRAMES:
314 if (m_this->m_sl->getFrameCount() <= 0) *m_this = SelectionData();
315 break;
316 case SELECTED_FRAMES:
317 // Since fid2index may return negative indexes, cut them out
318 m_this->m_frameIdxs.erase(m_this->m_frameIdxs.begin(),
319 m_this->m_frameIdxs.lower_bound(0));
320
321 // Also cut indexes greater than m_sl's frames count
322 m_this->m_frameIdxs.erase(
323 m_this->m_frameIdxs.lower_bound(m_this->m_sl->getFrameCount()),
324 m_this->m_frameIdxs.end());
325
326 // Reset to empty in case no frame was selected
327 if (m_this->m_frameIdxs.empty()) *m_this = SelectionData();
328
329 // NOTE: This may notably happen whenever non-empty level cells refer to
330 // frames
331 // not present in the level.
332 break;
333 }
334 }
335
336 void initialize(const TFilmstripSelection &selection) {
337 TXshSimpleLevel *sl = simpleLevel();
338
339 if (sl && sl->getType() == PLI_XSHLEVEL) {
340 m_this->m_contentType = IMAGE;
341 m_this->m_framesType = SELECTED_FRAMES;
342 m_this->m_sl = sl;
343
344 const std::set<TFrameId> &fids = selection.getSelectedFids();
345
346 std::set<int> s;
347 for (auto const &e : fids) {
348 s.insert(m_this->m_sl->fid2index(e));
349 }
350 m_this->m_frameIdxs = std::move(s);
351
352 resetIfInvalid();
353 }
354 }
355
356 void initialize(int r0, int c0, int r1, int c1) {
357 assert(!m_this->m_sl);
358
359 const TXsheet *xsh = xsheet();
360 for (int r = r0; r <= r1; ++r) {
361 for (int c = c0; c <= c1; ++c) {
362 const TXshCell &cell = xsh->getCell(r, c);
363 TXshSimpleLevel *newSl = cell.getSimpleLevel();
364
365 if (newSl && newSl->getType() == PLI_XSHLEVEL) {
366 if (m_this->m_sl) {
367 if (m_this->m_sl != newSl) // Only a single vector level
368 { // is allowed in the selection
369 *m_this = SelectionData();
370 return;
371 }
372 } else {
373 m_this->m_contentType = IMAGE;
374 m_this->m_framesType = SELECTED_FRAMES;
375 m_this->m_sl = newSl;
376 }
377
378 int idx = m_this->m_sl->fid2index(cell.getFrameId());
379 if (idx >= 0) m_this->m_frameIdxs.insert(idx);
380 }
381 }
382 }
383
384 resetIfInvalid();
385 }
386
387 void initialize(const TCellSelection &selection) {
388 int r0, c0, r1, c1;
389 selection.getSelectedCells(r0, c0, r1, c1);
390
391 initialize(r0, c0, r1, c1);
392 }
393
394 void initialize(const TColumnSelection &selection) {
395 if (selection.getIndices().size() != 1) // Bail out if we don't have a
396 return; // specific column
397
398 int c = *selection.getIndices().begin();
399
400 TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
401 TXshColumn *col = xsh->getColumn(c);
402
403 assert(col);
404
405 // Retrieve the first level in the column
406 if (TXshCellColumn *cCol = dynamic_cast<TXshCellColumn *>(col)) {
407 int r0, r1;
408 cCol->getRange(r0, r1);
409
410 initialize(r0, c, r1, c);
411 }
412 }
413
414 void initialize(const StrokeSelection &selection) {
415 const std::pair<TXshSimpleLevel *, int> &pair = currentLevelIndex();
416
417 TXshSimpleLevel *sl = pair.first;
418
419 if (sl && sl->getType() == PLI_XSHLEVEL) {
420 assert(selection.getImage() ==
421 sl->getFullsampledFrame(sl->index2fid(pair.second), false));
422
423 m_this->m_contentType = STROKES;
424 m_this->m_framesType = SELECTED_FRAMES;
425 m_this->m_sl = sl;
426
427 m_this->m_frameIdxs.insert(pair.second);
428 resetIfInvalid();
429
430 if (*m_this) {
431 const std::set<int> &strokeIdxs = selection.getSelection();
432
433 m_this->m_idxs =
434 std::vector<int>(strokeIdxs.begin(), strokeIdxs.end());
435
436 // Reset to empty in case no stroke was selected
437 if (m_this->m_idxs.empty()) *m_this = SelectionData();
438 }
439 }
440 }
441
442 void initialize(const LevelSelection &selection) {
443 if (TXshSimpleLevel *sl = simpleLevel()) {
444 assert(sl->getType() == PLI_XSHLEVEL);
445 m_this->m_sl = sl;
446
447 // Discriminate styles selection modes
448 switch (selection.filter()) {
449 case LevelSelection::WHOLE:
450 m_this->m_contentType = IMAGE;
451 break;
452
453 case LevelSelection::SELECTED_STYLES:
454 m_this->m_contentType = STYLES;
455 m_this->m_idxs = std::vector<int>(selection.styles().begin(),
456 selection.styles().end());
457
458 // Reset to empty in case no style was selected
459 if (m_this->m_idxs.empty()) {
460 *m_this = SelectionData();
461 return;
462 }
463
464 break;
465 case LevelSelection::BOUNDARY_STROKES:
466 m_this->m_contentType = BOUNDARIES;
467 break;
468 default:
469 break;
470 }
471
472 // Discriminate frames selection modes
473 switch (selection.framesMode()) {
474 case LevelSelection::FRAMES_ALL:
475 m_this->m_framesType = ALL_FRAMES;
476 break;
477
478 case LevelSelection::FRAMES_SELECTED: {
479 m_this->m_framesType = SELECTED_FRAMES;
480
481 const std::set<TFrameId> &fids = TTool::getSelectedFrames();
482
483 std::set<int> s;
484 for (auto const &e : fids) {
485 s.insert(m_this->m_sl->fid2index(e));
486 }
487 m_this->m_frameIdxs = std::move(s);
488 break;
489 }
490 default:
491 break;
492 }
493
494 resetIfInvalid();
495 }
496 }
497
498 } locals = {this};
499
500 if (sel && !sel->isEmpty()) {
501 if (const TCellSelection *cSel = dynamic_cast<const TCellSelection *>(sel))
502 locals.initialize(*cSel);
503 else if (const TFilmstripSelection *fSel =
504 dynamic_cast<const TFilmstripSelection *>(sel))
505 locals.initialize(*fSel);
506 else if (const TColumnSelection *cSel =
507 dynamic_cast<const TColumnSelection *>(sel))
508 locals.initialize(*cSel);
509 else if (const StrokeSelection *sSel =
510 dynamic_cast<const StrokeSelection *>(sel))
511 locals.initialize(*sSel);
512 else if (const LevelSelection *vlSel =
513 dynamic_cast<const LevelSelection *>(sel))
514 locals.initialize(*vlSel);
515 }
516 }
517
518 //--------------------------------------------------------------
519
SelectionData(const FrameData & fd)520 AdjustThicknessPopup::SelectionData::SelectionData(const FrameData &fd)
521 : m_contentType(IMAGE)
522 , m_framesType(SELECTED_FRAMES)
523 , m_sl(fd.m_sl)
524 , m_frameIdxs(&fd.m_frameIdx, &fd.m_frameIdx + 1) {
525 if (!m_sl || m_sl->getType() != PLI_XSHLEVEL ||
526 m_sl->index2fid(*m_frameIdxs.begin()) < 0)
527 *this = SelectionData();
528 }
529
530 //--------------------------------------------------------------
531
operator bool() const532 AdjustThicknessPopup::SelectionData::operator bool() const {
533 return (m_contentType != NONE);
534 }
535
536 //--------------------------------------------------------------
537
getRange(int & startIdx,int & endIdx) const538 void AdjustThicknessPopup::SelectionData::getRange(int &startIdx,
539 int &endIdx) const {
540 if (m_contentType == NONE) return;
541
542 switch (m_framesType) {
543 case ALL_FRAMES:
544 assert(m_sl);
545
546 startIdx = 0;
547 endIdx = m_sl->getFrameCount() - 1;
548 break;
549
550 case SELECTED_FRAMES:
551 assert(!m_frameIdxs.empty());
552
553 startIdx = *m_frameIdxs.begin();
554 endIdx = *--m_frameIdxs.end();
555 break;
556 }
557 }
558
559 //**************************************************************************
560 // Adjust Thickness operation
561 //**************************************************************************
562
563 namespace {
564
565 typedef AdjustThicknessPopup::SelectionData SelectionData;
566
processFrame(const SelectionData & selData,int slFrameIndex,const double (& fromTransform)[2],const double (& toTransform)[2])567 TVectorImageP processFrame(const SelectionData &selData, int slFrameIndex,
568 const double (&fromTransform)[2],
569 const double (&toTransform)[2]) {
570 struct locals {
571 static void makeTransform(double (&transform)[2],
572 const double (&fromTransform)[2],
573 const double (&toTransform)[2], int startIdx,
574 int endIdx, int curIdx) {
575 double relPos = ::relativePosition(startIdx, endIdx, curIdx);
576
577 transform[0] =
578 tcg::numeric_ops::lerp(fromTransform[0], toTransform[0], relPos);
579 transform[1] =
580 tcg::numeric_ops::lerp(fromTransform[1], toTransform[1], relPos);
581 }
582
583 }; // locals
584
585 if (!selData || !selData.m_sl) return TVectorImageP();
586
587 // Retrieve input image
588 if (TVectorImageP viIn = selData.m_sl->getFullsampledFrame(
589 selData.m_sl->index2fid(slFrameIndex), false)) {
590 // Retrieve operations range
591 int startIdx, endIdx;
592 selData.getRange(startIdx, endIdx);
593
594 if (startIdx <= endIdx) {
595 // Allocate a conformant output, if necessary
596 TVectorImageP viOut = viIn->clone();
597
598 // Perform the operation preview
599 switch (selData.m_contentType) {
600 case SelectionData::IMAGE: {
601 double transform[2];
602 locals::makeTransform(transform, fromTransform, toTransform, startIdx,
603 endIdx, slFrameIndex);
604
605 ::transformThickness_image(viOut, transform);
606 break;
607 }
608
609 case SelectionData::STYLES: {
610 double transform[2];
611 locals::makeTransform(transform, fromTransform, toTransform, startIdx,
612 endIdx, slFrameIndex);
613
614 ::transformThickness_styles(viOut, transform, selData.m_idxs.data(),
615 selData.m_idxs.size());
616 break;
617 }
618
619 case SelectionData::BOUNDARIES: {
620 double transform[2];
621 locals::makeTransform(transform, fromTransform, toTransform, startIdx,
622 endIdx, slFrameIndex);
623
624 std::vector<int> strokes = getBoundaryStrokes(*viOut);
625
626 ::transformThickness_strokes(viOut, transform, strokes.data(),
627 strokes.size());
628 break;
629 }
630
631 case SelectionData::STROKES:
632 ::transformThickness_strokes(
633 viOut, fromTransform, selData.m_idxs.data(), selData.m_idxs.size());
634 break;
635
636 default:
637 assert(false);
638 }
639
640 return viOut;
641 }
642 }
643
644 return TVectorImageP();
645 }
646
647 } // namespace
648
649 //**************************************************************************
650 // AdjustThicknessPopup implementation
651 //**************************************************************************
652
AdjustThicknessPopup()653 AdjustThicknessPopup::AdjustThicknessPopup()
654 : DVGui::Dialog(TApp::instance()->getMainWindow(), true, false,
655 "AdjustThickness")
656 , m_validPreview(false) {
657 setWindowTitle(tr("Adjust Thickness"));
658 setLabelWidth(0);
659 setModal(false);
660
661 setTopMargin(0);
662 setTopSpacing(0);
663
664 beginVLayout();
665
666 QSplitter *splitter = new QSplitter(Qt::Vertical);
667 splitter->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding,
668 QSizePolicy::MinimumExpanding));
669 addWidget(splitter);
670
671 endVLayout();
672
673 //------------------------- Top Layout --------------------------
674
675 QScrollArea *scrollArea = new QScrollArea(splitter);
676 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
677 scrollArea->setWidgetResizable(true);
678 scrollArea->setMinimumWidth(450);
679 splitter->addWidget(scrollArea);
680 splitter->setStretchFactor(0, 1);
681
682 QFrame *topWidget = new QFrame(scrollArea);
683 scrollArea->setWidget(topWidget);
684
685 QGridLayout *topLayout = new QGridLayout(topWidget);
686 topWidget->setLayout(topLayout);
687
688 //-------------------------- Options ----------------------------
689
690 int row = 0;
691
692 topLayout->setColumnStretch(0, 1);
693
694 topLayout->addWidget(new QLabel(tr("Mode:")), row, 1, Qt::AlignRight);
695
696 m_thicknessMode = new QComboBox;
697 topLayout->addWidget(m_thicknessMode, row++, 2, Qt::AlignLeft);
698
699 m_thicknessMode->addItems(QStringList() << tr("Scale Thickness")
700 << tr("Add Thickness")
701 << tr("Constant Thickness"));
702
703 topLayout->addWidget(new QLabel(tr("Start:")), row, 1, Qt::AlignRight);
704
705 QHBoxLayout *paramsLayout = new QHBoxLayout;
706 topLayout->addLayout(paramsLayout, row++, 2, Qt::AlignLeft);
707 {
708 m_fromScale = new DVGui::MeasuredDoubleField;
709 m_fromDisplacement = new DVGui::MeasuredDoubleField;
710 m_toScale = new DVGui::MeasuredDoubleField;
711 m_toDisplacement = new DVGui::MeasuredDoubleField;
712
713 paramsLayout->addWidget(m_fromScale);
714 paramsLayout->addWidget(m_fromDisplacement);
715
716 paramsLayout->addWidget(new QLabel(tr("End:")));
717 paramsLayout->addWidget(m_toScale);
718 paramsLayout->addWidget(m_toDisplacement);
719
720 paramsLayout->addStretch(1);
721
722 m_fromScale->setMeasure("percentage");
723 m_toScale->setMeasure("percentage");
724
725 m_fromScale->setRange(0, dmax);
726 m_fromDisplacement->setRange(dmin, dmax);
727 m_toScale->setRange(0, dmax);
728 m_toDisplacement->setRange(dmin, dmax);
729
730 m_fromScale->setValue(1.0);
731 m_toScale->setValue(1.0);
732
733 m_fromDisplacement->setValue(0.0);
734 m_toDisplacement->setValue(0.0);
735 }
736
737 topLayout->setColumnStretch(3, 1);
738 topLayout->setRowStretch(2, 1); // Needed to justify at top
739
740 //------------------------- View Widget -------------------------
741
742 // NOTE: It's IMPORTANT that parent widget is supplied. It's somewhat
743 // used by QSplitter to decide the initial widget sizes...
744
745 m_viewer = new Swatch(splitter);
746 m_viewer->setMinimumHeight(150);
747 m_viewer->setFocusPolicy(Qt::WheelFocus);
748 splitter->addWidget(m_viewer);
749
750 m_okBtn = new QPushButton(tr("Apply"));
751 addButtonBarWidget(m_okBtn);
752
753 // Establish connections
754 bool ret = true;
755 ret = connect(m_thicknessMode, SIGNAL(currentIndexChanged(int)), this,
756 SLOT(onModeChanged())) &&
757 ret;
758 ret = connect(m_fromScale, SIGNAL(valueChanged(bool)), this,
759 SLOT(onParamsChanged())) &&
760 ret;
761 ret = connect(m_fromDisplacement, SIGNAL(valueChanged(bool)), this,
762 SLOT(onParamsChanged())) &&
763 ret;
764 ret = connect(m_toScale, SIGNAL(valueChanged(bool)), this,
765 SLOT(onParamsChanged())) &&
766 ret;
767 ret = connect(m_toDisplacement, SIGNAL(valueChanged(bool)), this,
768 SLOT(onParamsChanged())) &&
769 ret;
770 ret = connect(m_okBtn, SIGNAL(clicked()), this, SLOT(apply())) && ret;
771 assert(ret);
772
773 m_viewer->resize(0, 350);
774 resize(600, 500);
775 }
776
777 //--------------------------------------------------------------
778
showEvent(QShowEvent * se)779 void AdjustThicknessPopup::showEvent(QShowEvent *se) {
780 TApp *app = TApp::instance();
781
782 TSelectionHandle *selectionHandle = app->getCurrentSelection();
783 TXsheetHandle *xsheetHandle = app->getCurrentXsheet();
784 TFrameHandle *frameHandle = app->getCurrentFrame();
785 TColumnHandle *columnHandle = app->getCurrentColumn();
786
787 bool ret = true;
788 ret = connect(selectionHandle, SIGNAL(selectionChanged(TSelection *)), this,
789 SLOT(onSelectionChanged())) &&
790 ret;
791 ret = connect(selectionHandle,
792 SIGNAL(selectionSwitched(TSelection *, TSelection *)), this,
793 SLOT(onSelectionChanged())) &&
794 ret;
795 ret = connect(xsheetHandle, SIGNAL(xsheetChanged()), this,
796 SLOT(onXsheetChanged())) &&
797 ret;
798 ret = connect(xsheetHandle, SIGNAL(xsheetSwitched()), this,
799 SLOT(onXsheetChanged())) &&
800 ret;
801 ret = connect(frameHandle, SIGNAL(frameSwitched()), this,
802 SLOT(onFrameChanged())) &&
803 ret;
804 ret = connect(columnHandle, SIGNAL(columnIndexSwitched()), this,
805 SLOT(onFrameChanged())) &&
806 ret;
807 assert(ret);
808
809 onModeChanged();
810 onXsheetChanged();
811 }
812
813 //--------------------------------------------------------------
814
hideEvent(QHideEvent * he)815 void AdjustThicknessPopup::hideEvent(QHideEvent *he) {
816 Dialog::hideEvent(he);
817
818 TApp *app = TApp::instance();
819 app->getCurrentSelection()->disconnect(this);
820 app->getCurrentXsheet()->disconnect(this);
821 app->getCurrentFrame()->disconnect(this);
822 app->getCurrentColumn()->disconnect(this);
823
824 // Empty cached data
825 m_selectionData = SelectionData();
826 m_currentFrameData = FrameData();
827
828 m_previewedFrameData = FrameData();
829 m_viewer->image() = TVectorImageP(); // This in particular
830 }
831
832 //--------------------------------------------------------------
833
updateSelectionData()834 void AdjustThicknessPopup::updateSelectionData() {
835 m_selectionData =
836 SelectionData(TApp::instance()->getCurrentSelection()->getSelection());
837 }
838
839 //--------------------------------------------------------------
840
updateCurrentFrameData()841 void AdjustThicknessPopup::updateCurrentFrameData() {
842 m_currentFrameData = FrameData::getCurrent();
843 }
844
845 //--------------------------------------------------------------
846
updateSelectionGui()847 void AdjustThicknessPopup::updateSelectionGui() {
848 m_okBtn->setEnabled(bool(m_selectionData) ||
849 bool(SelectionData(m_currentFrameData)));
850 }
851
852 //--------------------------------------------------------------
853
onModeChanged()854 void AdjustThicknessPopup::onModeChanged() {
855 bool scale = (m_thicknessMode->currentIndex() == 0);
856
857 m_fromScale->setVisible(scale);
858 m_toScale->setVisible(scale);
859
860 m_fromDisplacement->setVisible(!scale);
861 m_toDisplacement->setVisible(!scale);
862
863 schedulePreviewUpdate();
864 }
865
866 //--------------------------------------------------------------
867
onXsheetChanged()868 void AdjustThicknessPopup::onXsheetChanged() {
869 updateSelectionData();
870 updateCurrentFrameData();
871
872 updateSelectionGui();
873 schedulePreviewUpdate();
874 }
875
876 //--------------------------------------------------------------
877
onSelectionChanged()878 void AdjustThicknessPopup::onSelectionChanged() {
879 updateSelectionData();
880
881 updateSelectionGui();
882 schedulePreviewUpdate();
883 }
884
885 //--------------------------------------------------------------
886
onFrameChanged()887 void AdjustThicknessPopup::onFrameChanged() {
888 updateCurrentFrameData();
889
890 if (m_currentFrameData != m_previewedFrameData) {
891 updateSelectionGui();
892 schedulePreviewUpdate();
893 }
894 }
895
896 //--------------------------------------------------------------
897
onParamsChanged()898 void AdjustThicknessPopup::onParamsChanged() { schedulePreviewUpdate(); }
899
900 //--------------------------------------------------------------
901
schedulePreviewUpdate()902 void AdjustThicknessPopup::schedulePreviewUpdate() {
903 m_validPreview = false;
904 QTimer::singleShot(0, this, SLOT(updatePreview()));
905 }
906
907 //--------------------------------------------------------------
908
getTransformParameters(double (& fromTransform)[2],double (& toTransform)[2])909 void AdjustThicknessPopup::getTransformParameters(double (&fromTransform)[2],
910 double (&toTransform)[2]) {
911 enum { SCALE, ADD, CONSTANT };
912
913 switch (m_thicknessMode->currentIndex()) {
914 case SCALE:
915 fromTransform[0] = 0.0;
916 fromTransform[1] = m_fromScale->getValue();
917
918 toTransform[0] = 0.0;
919 toTransform[1] = m_toScale->getValue();
920 break;
921
922 case ADD:
923 fromTransform[0] = m_fromDisplacement->getValue() * Stage::inch;
924 fromTransform[1] = 1.0;
925
926 toTransform[0] = m_toDisplacement->getValue() * Stage::inch;
927 toTransform[1] = 1.0;
928 break;
929
930 case CONSTANT:
931 fromTransform[0] = m_fromDisplacement->getValue() * Stage::inch;
932 fromTransform[1] = 0.0;
933
934 toTransform[0] = m_toDisplacement->getValue() * Stage::inch;
935 toTransform[1] = 0.0;
936 break;
937 }
938 }
939
940 //--------------------------------------------------------------
941
updatePreview()942 void AdjustThicknessPopup::updatePreview() {
943 if (!m_validPreview) {
944 m_validPreview = true;
945
946 // Re-process preview source
947 double fromTransform[2], toTransform[2];
948 getTransformParameters(fromTransform, toTransform);
949
950 if (m_selectionData) {
951 m_previewedFrameData =
952 FrameData(m_selectionData.m_sl, m_currentFrameData.m_frameIdx);
953 m_viewer->image() =
954 ::processFrame(m_selectionData, m_currentFrameData.m_frameIdx,
955 fromTransform, toTransform);
956 } else {
957 m_previewedFrameData = m_currentFrameData;
958 m_viewer->image() =
959 ::processFrame(m_currentFrameData, m_currentFrameData.m_frameIdx,
960 fromTransform, toTransform);
961 }
962
963 m_viewer->update();
964 }
965 }
966
967 //**************************************************************************
968 // AdjustThickness Undo
969 //**************************************************************************
970
971 namespace {
972
973 class AdjustThicknessUndo final : public TUndo {
974 public:
975 AdjustThicknessUndo(const SelectionData &selData, double (&fromTransform)[2],
976 double (&toTransform)[2]);
977
978 void redo() const override;
979 void undo() const override;
980
getSize() const981 int getSize() const override {
982 return (10 << 20);
983 } // 10 MB, flat - ie, at max 10 of these for a standard 100MB
984 // undo cache size.
985 private:
986 struct ImageBackup {
987 TFrameId m_fid;
988 TVectorImageP m_vi;
989
990 public:
ImageBackup__anone3633a5a0511::AdjustThicknessUndo::ImageBackup991 ImageBackup(const TFrameId &fid, const TVectorImageP &vi)
992 : m_fid(fid), m_vi(vi) {}
993 };
994
995 private:
996 SelectionData m_selData; //!< Selection to be processed.
997
998 double m_fromTransform[2], //!< Thickness transform start parameters.
999 m_toTransform[2]; //!< Thickness transform end parameters.
1000
1001 mutable std::vector<ImageBackup> m_originalImages; //!< Original images.
1002 };
1003
1004 //==============================================================
1005
AdjustThicknessUndo(const SelectionData & selData,double (& fromTransform)[2],double (& toTransform)[2])1006 AdjustThicknessUndo::AdjustThicknessUndo(const SelectionData &selData,
1007 double (&fromTransform)[2],
1008 double (&toTransform)[2])
1009 : m_selData(selData) {
1010 std::copy(fromTransform, fromTransform + 2, m_fromTransform);
1011 std::copy(toTransform, toTransform + 2, m_toTransform);
1012
1013 assert(m_selData && m_selData.m_sl);
1014 }
1015
1016 //--------------------------------------------------------------
1017
redo() const1018 void AdjustThicknessUndo::redo() const {
1019 auto const processFrame = [this](int frameIdx) {
1020 TXshSimpleLevel *sl = m_selData.m_sl.getPointer();
1021 assert(sl);
1022
1023 const TFrameId &fid = sl->index2fid(frameIdx);
1024
1025 // Backup input frame
1026 TVectorImageP viIn = sl->getFullsampledFrame(fid, false);
1027 if (!viIn) return;
1028
1029 m_originalImages.push_back(ImageBackup(fid, viIn));
1030
1031 // Process required frame
1032 TVectorImageP viOut = ::processFrame(
1033 m_selData, frameIdx, m_fromTransform, m_toTransform);
1034
1035 sl->setFrame(fid, viOut);
1036
1037 // Ensure the level data is invalidated suitably
1038 sl->setDirtyFlag(true);
1039 IconGenerator::instance()->invalidate(sl, fid);
1040 };
1041
1042 m_originalImages.clear();
1043
1044 // Iterate selected frames
1045 switch (m_selData.m_framesType) {
1046 case SelectionData::ALL_FRAMES:
1047 std::for_each(
1048 boost::make_counting_iterator(0),
1049 boost::make_counting_iterator(m_selData.m_sl->getFrameCount()),
1050 processFrame);
1051 break;
1052 case SelectionData::SELECTED_FRAMES:
1053 std::for_each(m_selData.m_frameIdxs.begin(), m_selData.m_frameIdxs.end(),
1054 processFrame);
1055 break;
1056 }
1057 }
1058
1059 //--------------------------------------------------------------
1060
undo() const1061 void AdjustThicknessUndo::undo() const {
1062 // Copy the backup images back to the level
1063 TXshSimpleLevel *sl = m_selData.m_sl.getPointer();
1064
1065 std::vector<ImageBackup>::const_iterator bt, bEnd = m_originalImages.end();
1066 for (bt = m_originalImages.begin(); bt != bEnd; ++bt) {
1067 sl->setFrame(bt->m_fid, bt->m_vi.getPointer());
1068
1069 sl->setDirtyFlag(true);
1070 IconGenerator::instance()->invalidate(sl, bt->m_fid);
1071 }
1072 }
1073
1074 } // namespace
1075
1076 //**************************************************************************
1077 // Apply stuff
1078 //**************************************************************************
1079
apply()1080 void AdjustThicknessPopup::apply() {
1081 if (!m_selectionData) {
1082 DVGui::error(QObject::tr("The current selection is invalid."));
1083 return;
1084 }
1085
1086 // Retrieve parameters
1087 double fromTransform[2], toTransform[2];
1088 getTransformParameters(fromTransform, toTransform);
1089
1090 std::unique_ptr<TUndo> undo(
1091 new AdjustThicknessUndo(m_selectionData, fromTransform, toTransform));
1092
1093 undo->redo();
1094
1095 TUndoManager::manager()->add(undo.release());
1096
1097 close();
1098 }
1099
1100 //**************************************************************************
1101 // Open Popup Command Handler instantiation
1102 //**************************************************************************
1103
1104 OpenPopupCommandHandler<AdjustThicknessPopup> openAdjustThicknessPopup(
1105 MI_AdjustThickness);
1106