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