1 
2 
3 #include "insertfxpopup.h"
4 
5 // Tnz6 includes
6 #include "menubarcommandids.h"
7 #include "tapp.h"
8 
9 // TnzQt includes
10 #include "toonzqt/menubarcommand.h"
11 #include "toonzqt/gutil.h"
12 #include "toonzqt/fxselection.h"
13 #include "toonzqt/tselectionhandle.h"
14 #include "toonzqt/pluginloader.h"  // inter-module plugin loader accessor
15 
16 // TnzLib includes
17 #include "toonz/tscenehandle.h"
18 #include "toonz/txsheethandle.h"
19 #include "toonz/tframehandle.h"
20 #include "toonz/tcolumnhandle.h"
21 #include "toonz/tfxhandle.h"
22 #include "toonz/toonzscene.h"
23 #include "toonz/txsheet.h"
24 #include "toonz/fxdag.h"
25 #include "toonz/tcolumnfx.h"
26 #include "toonz/txshlevelcolumn.h"
27 #include "toonz/tcolumnfxset.h"
28 #include "toonz/tstageobjecttree.h"
29 #include "toonz/txshzeraryfxcolumn.h"
30 #include "toonz/toonzfolders.h"
31 #include "toonz/scenefx.h"
32 #include "toonz/fxcommand.h"
33 
34 #include "tw/stringtable.h"
35 
36 // TnzBase includes
37 #include "tdoubleparam.h"
38 #include "tparamcontainer.h"
39 #include "tmacrofx.h"
40 #include "tfx.h"
41 #include "texternfx.h"
42 
43 // TnzCore includes
44 #include "tsystem.h"
45 
46 // Qt includes
47 #include <QPushButton>
48 #include <QTreeWidget>
49 #include <QHBoxLayout>
50 #include <QHeaderView>
51 #include <QMenu>
52 #include <QContextMenuEvent>
53 #include <QMainWindow>
54 #include <QLineEdit>
55 #include <QLabel>
56 
57 #include <memory>
58 
59 using namespace DVGui;
60 
61 //=============================================================================
62 namespace {
63 //-----------------------------------------------------------------------------
64 
createFxByName(const std::string & fxId)65 TFx *createFxByName(const std::string &fxId) {
66   if (fxId.find("_ext_") == 0) {
67     return TExternFx::create(fxId.substr(5));
68   }
69   if (fxId.find("_plg_") == 0) {
70     return PluginLoader::create_host(fxId);
71   }
72   return TFx::create(fxId);
73 }
74 
75 //-----------------------------------------------------------------------------
76 
createPresetFxByName(TFilePath path)77 TFx *createPresetFxByName(TFilePath path) {
78   const std::string &id = path.getParentDir().getName();
79 
80   TFx *fx = createFxByName(id);
81   if (fx) {
82     TIStream is(path);
83     fx->loadPreset(is);
84     fx->setName(path.getWideName());
85   }
86 
87   return fx;
88 }
89 
90 //-----------------------------------------------------------------------------
91 
createMacroFxByPath(TFilePath path)92 TFx *createMacroFxByPath(TFilePath path) {
93   TIStream is(path);
94   TPersist *p = 0;
95   is >> p;
96   TMacroFx *fx = dynamic_cast<TMacroFx *>(p);
97   if (!fx) return 0;
98   fx->setName(path.getWideName());
99   // Assign a unic ID to each fx in the macro!
100   TApp *app    = TApp::instance();
101   TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
102   if (!xsh) return fx;
103   FxDag *fxDag = xsh->getFxDag();
104   if (!fxDag) return fx;
105   std::vector<TFxP> fxs;
106   fxs = fx->getFxs();
107   QMap<std::wstring, std::wstring> oldNewId;
108   int i;
109   for (i = 0; i < fxs.size(); i++) {
110     std::wstring oldId = fxs[i]->getFxId();
111     fxDag->assignUniqueId(fxs[i].getPointer());
112     oldNewId[oldId] = fxs[i]->getFxId();
113   }
114 
115   QStack<QPair<std::string, TFxPort *>> newPortNames;
116 
117   // Devo cambiare il nome alle porte: contengono l'id dei vecchi effetti
118   for (i = fx->getInputPortCount() - 1; i >= 0; i--) {
119     std::string oldPortName = fx->getInputPortName(i);
120     std::string inFxOldId   = oldPortName;
121     inFxOldId.erase(0, inFxOldId.find_last_of("_") + 1);
122     assert(oldNewId.contains(::to_wstring(inFxOldId)));
123     std::string inFxNewId   = ::to_string(oldNewId[ ::to_wstring(inFxOldId)]);
124     std::string newPortName = oldPortName;
125     newPortName.erase(newPortName.find_last_of("_") + 1,
126                       newPortName.size() - 1);
127     newPortName.append(inFxNewId);
128     TFxPort *fxPort = fx->getInputPort(i);
129     newPortNames.append(QPair<std::string, TFxPort *>(newPortName, fxPort));
130     fx->removeInputPort(oldPortName);
131   }
132   while (!newPortNames.isEmpty()) {
133     QPair<std::string, TFxPort *> newPort = newPortNames.pop();
134     fx->addInputPort(newPort.first, *newPort.second);
135   }
136   return fx;
137 }
138 
139 }  // anonymous namespace
140 //-----------------------------------------------------------------------------
141 
142 //=============================================================================
143 // FxTree
144 //=============================================================================
145 
displayAll(QTreeWidgetItem * item)146 void FxTree::displayAll(QTreeWidgetItem *item) {
147   int childCount = item->childCount();
148   for (int i = 0; i < childCount; ++i) {
149     displayAll(item->child(i));
150   }
151   item->setHidden(false);
152   item->setExpanded(false);
153 }
154 
155 //-------------------------------------------------------------------
156 
hideAll(QTreeWidgetItem * item)157 void FxTree::hideAll(QTreeWidgetItem *item) {
158   int childCount = item->childCount();
159   for (int i = 0; i < childCount; ++i) {
160     hideAll(item->child(i));
161   }
162   item->setHidden(true);
163   item->setExpanded(false);
164 }
165 
166 //-------------------------------------------------------------------
167 
searchItems(const QString & searchWord)168 void FxTree::searchItems(const QString &searchWord) {
169   // if search word is empty, show all items
170   if (searchWord.isEmpty()) {
171     int itemCount = topLevelItemCount();
172     for (int i = 0; i < itemCount; ++i) {
173       displayAll(topLevelItem(i));
174     }
175     update();
176     return;
177   }
178 
179   // hide all items first
180   int itemCount = topLevelItemCount();
181   for (int i = 0; i < itemCount; ++i) {
182     hideAll(topLevelItem(i));
183   }
184 
185   QList<QTreeWidgetItem *> foundItems =
186       findItems(searchWord, Qt::MatchContains | Qt::MatchRecursive, 0);
187   if (foundItems.isEmpty()) {  // if nothing is found, do nothing but update
188     update();
189     return;
190   }
191 
192   // for each item found, show it and show its parent
193   for (auto item : foundItems) {
194     while (item) {
195       item->setHidden(false);
196       item->setExpanded(true);
197       item = item->parent();
198     }
199   }
200 
201   update();
202 }
203 
204 //=============================================================================
205 /*! \class InsertFxPopup
206                 \brief The InsertFxPopup class provides a dialog to browse fx
207    and add it to
208                 current scene.
209 
210                 Inherits \b Dialog.
211 */
InsertFxPopup()212 InsertFxPopup::InsertFxPopup()
213     : Dialog(TApp::instance()->getMainWindow(), true, false, "InsertFx")
214     , m_folderIcon(QIcon())
215     , m_presetIcon(QIcon())
216     , m_fxIcon(QIcon()) {
217   setWindowTitle(tr("FX Browser"));
218 
219   setModal(false);
220 
221   setTopMargin(0);
222   setTopSpacing(0);
223 
224   QHBoxLayout *searchLay = new QHBoxLayout();
225   QLineEdit *searchEdit  = new QLineEdit(this);
226 
227   searchLay->setMargin(0);
228   searchLay->setSpacing(5);
229   searchLay->addWidget(new QLabel(tr("Search:"), this), 0);
230   searchLay->addWidget(searchEdit);
231   addLayout(searchLay);
232   connect(searchEdit, SIGNAL(textChanged(const QString &)), this,
233           SLOT(onSearchTextChanged(const QString &)));
234 
235   m_fxTree = new FxTree();
236   m_fxTree->setIconSize(QSize(21, 18));
237   m_fxTree->setColumnCount(1);
238   m_fxTree->header()->close();
239 
240   m_fxTree->setObjectName("FxTreeView");
241   m_fxTree->setAlternatingRowColors(true);
242 
243   m_presetIcon = createQIcon("folder_preset", true);
244   m_fxIcon     = createQIcon("fx");
245 
246   QList<QTreeWidgetItem *> fxItems;
247 
248   TFilePath path = TFilePath(ToonzFolder::getProfileFolder() + "layouts" +
249                              "fxs" + "fxs.lst");
250   m_presetFolder = TFilePath(ToonzFolder::getFxPresetFolder() + "presets");
251   loadFx(path);
252   loadMacro();
253 
254   // add 'Plugins' directory
255   auto plugins =
256       new QTreeWidgetItem((QTreeWidget *)NULL, QStringList("Plugins"));
257   plugins->setIcon(0, createQIcon("folder", true));
258   m_fxTree->addTopLevelItem(plugins);
259 
260   // create vendor / Fx
261 
262   // send a special setup for the menu item
263   std::map<std::string, QTreeWidgetItem *> vendors =
264       PluginLoader::create_menu_items(
265           [&](QTreeWidgetItem *firstlevel_item) {
266             plugins->addChild(firstlevel_item);
267             firstlevel_item->setIcon(0, createQIcon("folder"));
268           },
269           [&](QTreeWidgetItem *secondlevel_item) {
270             secondlevel_item->setIcon(0, m_fxIcon);
271           });
272 
273   m_fxTree->insertTopLevelItems(0, fxItems);
274   connect(m_fxTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)),
275           SLOT(onItemDoubleClicked(QTreeWidgetItem *, int)));
276 
277   addWidget(m_fxTree);
278 
279   QPushButton *insertBtn = new QPushButton(tr("Insert"), this);
280   insertBtn->setFixedSize(65, 25);
281   insertBtn->setObjectName("PushButton_NoPadding");
282   connect(insertBtn, SIGNAL(clicked()), this, SLOT(onInsert()));
283   insertBtn->setDefault(true);
284   m_buttonLayout->addWidget(insertBtn);
285 
286   QPushButton *addBtn = new QPushButton(tr("Add"), this);
287   addBtn->setFixedSize(65, 25);
288   addBtn->setObjectName("PushButton_NoPadding");
289   connect(addBtn, SIGNAL(clicked()), this, SLOT(onAdd()));
290   m_buttonLayout->addWidget(addBtn);
291 
292   QPushButton *replaceBtn = new QPushButton(tr("Replace"), this);
293   replaceBtn->setFixedHeight(25);
294   replaceBtn->setObjectName("PushButton_NoPadding");
295   connect(replaceBtn, SIGNAL(clicked()), this, SLOT(onReplace()));
296   m_buttonLayout->addWidget(replaceBtn);
297 }
298 
299 //-------------------------------------------------------------------
300 
onSearchTextChanged(const QString & text)301 void InsertFxPopup::onSearchTextChanged(const QString &text) {
302   static bool busy = false;
303   if (busy) return;
304   busy = true;
305   m_fxTree->searchItems(text);
306   busy = false;
307 }
308 
309 //-------------------------------------------------------------------
310 
makeItem(QTreeWidgetItem * parent,std::string fxId)311 void InsertFxPopup::makeItem(QTreeWidgetItem *parent, std::string fxId) {
312   QTreeWidgetItem *fxItem = new QTreeWidgetItem(
313       (QTreeWidget *)0,
314       QStringList(QString::fromStdWString(TStringTable::translate(fxId))));
315 
316   fxItem->setData(0, Qt::UserRole, QVariant(QString::fromStdString(fxId)));
317   parent->addChild(fxItem);
318 
319   fxItem->setIcon(0, loadPreset(fxItem) ? m_presetIcon : m_fxIcon);
320 }
321 
322 //-------------------------------------------------------------------
323 
loadFolder(QTreeWidgetItem * parent)324 void InsertFxPopup::loadFolder(QTreeWidgetItem *parent) {
325   while (!m_is->eos()) {
326     std::string tagName;
327     if (m_is->matchTag(tagName)) {
328       // Found a sub-folder
329       QString folderName = QString::fromStdString(tagName);
330 
331       std::unique_ptr<QTreeWidgetItem> folder(
332           new QTreeWidgetItem((QTreeWidget *)0, QStringList(folderName)));
333       folder->setIcon(0, createQIcon("folder", true));
334 
335       loadFolder(folder.get());
336       m_is->closeChild();
337 
338       if (folder->childCount()) {
339         if (parent)
340           parent->addChild(folder.release());
341         else
342           m_fxTree->addTopLevelItem(folder.release());
343       }
344     } else {
345       // Found an fx
346       std::string fxName;
347       *m_is >> fxName;
348 
349       makeItem(parent, fxName);
350     }
351   }
352 }
353 
354 //-------------------------------------------------------------------
355 
loadFx(TFilePath fp)356 bool InsertFxPopup::loadFx(TFilePath fp) {
357   TIStream is(fp);
358   if (!is) return false;
359   m_is = &is;
360   try {
361     std::string tagName;
362     if (m_is->matchTag(tagName) && tagName == "fxs") {
363       loadFolder(0);
364       m_is->closeChild();
365     }
366   } catch (...) {
367     m_is = 0;
368     return false;
369   }
370   m_is = 0;
371 
372   return true;
373 }
374 
375 //-------------------------------------------------------------------
376 
loadPreset(QTreeWidgetItem * item)377 bool InsertFxPopup::loadPreset(QTreeWidgetItem *item) {
378   QString str = item->data(0, Qt::UserRole).toString();
379   TFilePath presetsFilepath(m_presetFolder + str.toStdWString());
380   int i;
381   for (i = item->childCount() - 1; i >= 0; i--)
382     item->removeChild(item->child(i));
383   if (TFileStatus(presetsFilepath).isDirectory()) {
384     TFilePathSet presets = TSystem::readDirectory(presetsFilepath);
385     if (!presets.empty()) {
386       for (TFilePathSet::iterator it2 = presets.begin(); it2 != presets.end();
387            ++it2) {
388         TFilePath presetPath = *it2;
389         QString name(presetPath.getName().c_str());
390         QTreeWidgetItem *presetItem =
391             new QTreeWidgetItem((QTreeWidget *)0, QStringList(name));
392         presetItem->setData(0, Qt::UserRole, QVariant(toQString(presetPath)));
393         item->addChild(presetItem);
394         presetItem->setIcon(0, m_fxIcon);
395       }
396     } else
397       return false;
398   } else
399     return false;
400 
401   return true;
402 }
403 
404 //-------------------------------------------------------------------
405 
loadMacro()406 void InsertFxPopup::loadMacro() {
407   TFilePath fp = m_presetFolder + TFilePath("macroFx");
408   try {
409     if (TFileStatus(fp).isDirectory()) {
410       TFilePathSet macros = TSystem::readDirectory(fp);
411       if (macros.empty()) return;
412 
413       QTreeWidgetItem *macroFolder =
414           new QTreeWidgetItem((QTreeWidget *)0, QStringList(tr("Macro")));
415       macroFolder->setData(0, Qt::UserRole, QVariant(toQString(fp)));
416       macroFolder->setIcon(0, createQIcon("folder", true));
417       m_fxTree->addTopLevelItem(macroFolder);
418       for (TFilePathSet::iterator it = macros.begin(); it != macros.end();
419            ++it) {
420         TFilePath macroPath = *it;
421         QString name(macroPath.getName().c_str());
422         QTreeWidgetItem *macroItem =
423             new QTreeWidgetItem((QTreeWidget *)0, QStringList(name));
424         macroItem->setData(0, Qt::UserRole, QVariant(toQString(macroPath)));
425         macroItem->setIcon(0, m_fxIcon);
426         macroFolder->addChild(macroItem);
427       }
428     }
429   } catch (...) {
430   }
431 }
432 
433 //-----------------------------------------------------------------------------
434 
onItemDoubleClicked(QTreeWidgetItem * w,int c)435 void InsertFxPopup::onItemDoubleClicked(QTreeWidgetItem *w, int c) {
436   if (w->childCount() == 0)  // E' una foglia
437     onInsert();
438 }
439 
440 //-----------------------------------------------------------------------------
441 
onInsert()442 void InsertFxPopup::onInsert() {
443   TFx *fx = createFx();
444   if (fx) {
445     TApp *app                = TApp::instance();
446     TXsheetHandle *xshHandle = app->getCurrentXsheet();
447     QList<TFxP> fxs;
448     QList<TFxCommand::Link> links;
449     FxSelection *selection =
450         dynamic_cast<FxSelection *>(app->getCurrentSelection()->getSelection());
451     if (selection) {
452       fxs   = selection->getFxs();
453       links = selection->getLinks();
454     }
455     TFxCommand::insertFx(fx, fxs, links, app,
456                          app->getCurrentColumn()->getColumnIndex(),
457                          app->getCurrentFrame()->getFrameIndex());
458     xshHandle->notifyXsheetChanged();
459   }
460 }
461 
462 //-----------------------------------------------------------------------------
463 
onAdd()464 void InsertFxPopup::onAdd() {
465   TFx *fx = createFx();
466   if (fx) {
467     TApp *app                = TApp::instance();
468     TXsheetHandle *xshHandle = app->getCurrentXsheet();
469     QList<TFxP> fxs;
470     FxSelection *selection =
471         dynamic_cast<FxSelection *>(app->getCurrentSelection()->getSelection());
472     if (selection) fxs = selection->getFxs();
473     TFxCommand::addFx(fx, fxs, app, app->getCurrentColumn()->getColumnIndex(),
474                       app->getCurrentFrame()->getFrameIndex());
475     xshHandle->notifyXsheetChanged();
476   }
477 }
478 
479 //-----------------------------------------------------------------------------
480 
onReplace()481 void InsertFxPopup::onReplace() {
482   TFx *fx = createFx();
483   if (fx) {
484     TApp *app                = TApp::instance();
485     TXsheetHandle *xshHandle = app->getCurrentXsheet();
486     QList<TFxP> fxs;
487     FxSelection *selection =
488         dynamic_cast<FxSelection *>(app->getCurrentSelection()->getSelection());
489     if (selection) fxs = selection->getFxs();
490     TFxCommand::replaceFx(fx, fxs, app->getCurrentXsheet(),
491                           app->getCurrentFx());
492     xshHandle->notifyXsheetChanged();
493   }
494 }
495 
496 //-----------------------------------------------------------------------------
497 
createFx()498 TFx *InsertFxPopup::createFx() {
499   TApp *app         = TApp::instance();
500   ToonzScene *scene = app->getCurrentScene()->getScene();
501   TXsheet *xsh      = scene->getXsheet();
502 
503   QTreeWidgetItem *item = m_fxTree->currentItem();
504   if (item == NULL) return 0;
505 
506   QString text = item->data(0, Qt::UserRole).toString();
507   if (text.isEmpty()) return 0;
508 
509   TFx *fx;
510 
511   TFilePath path = TFilePath(text.toStdWString());
512 
513   if (TFileStatus(path).doesExist() &&
514       TFileStatus(path.getParentDir()).isDirectory()) {
515     std::string folder = path.getParentDir().getName();
516     if (folder == "macroFx")  // Devo caricare una macro
517       fx = createMacroFxByPath(path);
518     else  // Verifico se devo caricare un preset
519     {
520       folder = path.getParentDir().getParentDir().getName();
521       if (folder == "presets")  // Devo caricare un preset
522         fx = createPresetFxByName(path);
523     }
524   } else
525     fx = createFxByName(text.toStdString());
526 
527   if (fx)
528     return fx;
529   else
530     return 0;
531 }
532 
533 //-----------------------------------------------------------------------------
534 
showEvent(QShowEvent *)535 void InsertFxPopup::showEvent(QShowEvent *) {
536   updatePresets();
537   connect(TApp::instance()->getCurrentFx(), SIGNAL(fxPresetSaved()),
538           SLOT(updatePresets()));
539 }
540 
541 //-----------------------------------------------------------------------------
542 
hideEvent(QHideEvent * e)543 void InsertFxPopup::hideEvent(QHideEvent *e) {
544   disconnect(TApp::instance()->getCurrentFx(), SIGNAL(fxPresetSaved()), this,
545              SLOT(updatePresets()));
546   Dialog::hideEvent(e);
547 }
548 
549 //-----------------------------------------------------------------------------
550 
contextMenuEvent(QContextMenuEvent * event)551 void InsertFxPopup::contextMenuEvent(QContextMenuEvent *event) {
552   QTreeWidgetItem *item = m_fxTree->currentItem();
553   QString itemRole      = item->data(0, Qt::UserRole).toString();
554 
555   TFilePath path = TFilePath(itemRole.toStdWString());
556   if (TFileStatus(path).doesExist() &&
557       TFileStatus(path.getParentDir()).isDirectory()) {
558     QMenu *menu        = new QMenu(this);
559     std::string folder = path.getParentDir().getName();
560     if (folder == "macroFx")  // Menu' macro
561     {
562       QAction *remove = new QAction(tr("Remove Macro FX"), menu);
563       connect(remove, SIGNAL(triggered()), this, SLOT(removePreset()));
564       menu->addAction(remove);
565     } else  // Verifico se devo caricare un preset
566     {
567       folder = path.getParentDir().getParentDir().getName();
568       if (folder == "presets")  // Menu' preset
569       {
570         QAction *remove = new QAction(tr("Remove Preset"), menu);
571         connect(remove, SIGNAL(triggered()), this, SLOT(removePreset()));
572         menu->addAction(remove);
573       }
574     }
575     menu->exec(event->globalPos());
576   }
577 }
578 
579 //-------------------------------------------------------------------
580 
updatePresets()581 void InsertFxPopup::updatePresets() {
582   int i;
583   for (i = 0; i < m_fxTree->topLevelItemCount(); i++) {
584     QTreeWidgetItem *folder = m_fxTree->topLevelItem(i);
585     TFilePath path =
586         TFilePath(folder->data(0, Qt::UserRole).toString().toStdWString());
587     if (folder->text(0).toStdString() == "Plugins") {
588       continue;
589     }
590     if (path.getName() == "macroFx") {
591       int j;
592       for (j = folder->childCount() - 1; j >= 0; j--)
593         folder->removeChild(folder->child(j));
594       m_fxTree->removeItemWidget(folder, 0);
595       delete folder;
596     } else if (path.getParentDir().getName() == "macroFx")
597       continue;
598     else
599       for (int i = 0; i < folder->childCount(); i++) {
600         bool isPresetLoaded = loadPreset(folder->child(i));
601         if (isPresetLoaded)
602           folder->child(i)->setIcon(0, m_presetIcon);
603         else
604           folder->child(i)->setIcon(0, m_fxIcon);
605       }
606   }
607   loadMacro();
608 
609   update();
610 }
611 
612 //-----------------------------------------------------------------------------
613 
removePreset()614 void InsertFxPopup::removePreset() {
615   QTreeWidgetItem *item = m_fxTree->currentItem();
616   QString itemRole      = item->data(0, Qt::UserRole).toString();
617 
618   TFilePath path = TFilePath(itemRole.toStdWString());
619 
620   QString question = QString(
621       tr("Are you sure you want to delete %1?").arg(path.getName().c_str()));
622   int ret = DVGui::MsgBox(question, tr("Yes"), tr("No"), 1);
623   if (ret == 2 || ret == 0) return;
624 
625   try {
626     TSystem::deleteFile(path);
627   } catch (...) {
628     error(QString(tr("It is not possible to delete %1.").arg(toQString(path))));
629     return;
630   }
631   m_fxTree->removeItemWidget(item, 0);
632   delete item;
633   TApp::instance()->getCurrentFx()->notifyFxPresetRemoved();
634 }
635 
636 //=============================================================================
637 
638 OpenPopupCommandHandler<InsertFxPopup> openInsertFxPopup(MI_InsertFx);
639