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