#include "insertfxpopup.h"
// Tnz6 includes
#include "menubarcommandids.h"
#include "tapp.h"
// TnzQt includes
#include "toonzqt/menubarcommand.h"
#include "toonzqt/gutil.h"
#include "toonzqt/fxselection.h"
#include "toonzqt/tselectionhandle.h"
#include "toonzqt/pluginloader.h" // inter-module plugin loader accessor
// TnzLib includes
#include "toonz/tscenehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/tfxhandle.h"
#include "toonz/toonzscene.h"
#include "toonz/txsheet.h"
#include "toonz/fxdag.h"
#include "toonz/tcolumnfx.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/toonzfolders.h"
#include "toonz/scenefx.h"
#include "toonz/fxcommand.h"
#include "tw/stringtable.h"
// TnzBase includes
#include "tdoubleparam.h"
#include "tparamcontainer.h"
#include "tmacrofx.h"
#include "tfx.h"
#include "texternfx.h"
// TnzCore includes
#include "tsystem.h"
// Qt includes
#include <QPushButton>
#include <QTreeWidget>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QMenu>
#include <QContextMenuEvent>
#include <QMainWindow>
#include <memory>
using namespace DVGui;
//=============================================================================
namespace {
//-----------------------------------------------------------------------------
TFx *createFxByName(const std::string &fxId) {
if (fxId.find("_ext_") == 0) {
return TExternFx::create(fxId.substr(5));
}
if (fxId.find("_plg_") == 0) {
return PluginLoader::create_host(fxId);
}
return TFx::create(fxId);
}
//-----------------------------------------------------------------------------
TFx *createPresetFxByName(TFilePath path) {
const std::string &id = path.getParentDir().getName();
TFx *fx = createFxByName(id);
if (fx) {
TIStream is(path);
fx->loadPreset(is);
fx->setName(path.getWideName());
}
return fx;
}
//-----------------------------------------------------------------------------
TFx *createMacroFxByPath(TFilePath path) {
TIStream is(path);
TPersist *p = 0;
is >> p;
TMacroFx *fx = dynamic_cast<TMacroFx *>(p);
if (!fx) return 0;
fx->setName(path.getWideName());
// Assign a unic ID to each fx in the macro!
TApp *app = TApp::instance();
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
if (!xsh) return fx;
FxDag *fxDag = xsh->getFxDag();
if (!fxDag) return fx;
std::vector<TFxP> fxs;
fxs = fx->getFxs();
QMap<std::wstring, std::wstring> oldNewId;
int i;
for (i = 0; i < fxs.size(); i++) {
std::wstring oldId = fxs[i]->getFxId();
fxDag->assignUniqueId(fxs[i].getPointer());
oldNewId[oldId] = fxs[i]->getFxId();
}
QStack<QPair<std::string, TFxPort *>> newPortNames;
// Devo cambiare il nome alle porte: contengono l'id dei vecchi effetti
for (i = fx->getInputPortCount() - 1; i >= 0; i--) {
std::string oldPortName = fx->getInputPortName(i);
std::string inFxOldId = oldPortName;
inFxOldId.erase(0, inFxOldId.find_last_of("_") + 1);
assert(oldNewId.contains(::to_wstring(inFxOldId)));
std::string inFxNewId = ::to_string(oldNewId[::to_wstring(inFxOldId)]);
std::string newPortName = oldPortName;
newPortName.erase(newPortName.find_last_of("_") + 1,
newPortName.size() - 1);
newPortName.append(inFxNewId);
TFxPort *fxPort = fx->getInputPort(i);
newPortNames.append(QPair<std::string, TFxPort *>(newPortName, fxPort));
fx->removeInputPort(oldPortName);
}
while (!newPortNames.isEmpty()) {
QPair<std::string, TFxPort *> newPort = newPortNames.pop();
fx->addInputPort(newPort.first, *newPort.second);
}
return fx;
}
} // anonymous namespace
//-----------------------------------------------------------------------------
//=============================================================================
/*! \class InsertFxPopup
\brief The InsertFxPopup class provides a dialog to browse fx
and add it to
current scene.
Inherits \b Dialog.
*/
InsertFxPopup::InsertFxPopup()
: Dialog(TApp::instance()->getMainWindow(), true, false, "InsertFx")
, m_folderIcon(QIcon())
, m_presetIcon(QIcon())
, m_fxIcon(QIcon()) {
setWindowTitle(tr("FX Browser"));
setModal(false);
setTopMargin(0);
setTopSpacing(0);
m_fxTree = new QTreeWidget();
m_fxTree->setIconSize(QSize(21, 17));
m_fxTree->setColumnCount(1);
m_fxTree->header()->close();
m_fxTree->setObjectName("FxTreeView");
m_fxTree->setAlternatingRowColors(true);
QString open = QString(":Resources/folder_close.svg");
QString close = QString(":Resources/folder_open.svg");
m_folderIcon.addFile(close, QSize(22, 22), QIcon::Normal, QIcon::On);
m_folderIcon.addFile(open, QSize(22, 22), QIcon::Normal, QIcon::Off);
QString presetOpen = QString(":Resources/folderpreset_close.svg");
QString presetClose = QString(":Resources/folderpreset_open.svg");
m_presetIcon.addFile(presetClose, QSize(22, 22), QIcon::Normal, QIcon::On);
m_presetIcon.addFile(presetOpen, QSize(22, 22), QIcon::Normal, QIcon::Off);
m_fxIcon = QIcon(QString(":Resources/fx.svg"));
QList<QTreeWidgetItem *> fxItems;
TFilePath path = TFilePath(ToonzFolder::getProfileFolder() + "layouts" +
"fxs" + "fxs.lst");
m_presetFolder = TFilePath(ToonzFolder::getFxPresetFolder() + "presets");
loadFx(path);
loadMacro();
// add 'Plugins' directory
auto plugins =
new QTreeWidgetItem((QTreeWidget *)NULL, QStringList("Plugins"));
plugins->setIcon(0, m_folderIcon);
m_fxTree->addTopLevelItem(plugins);
// create vendor / Fx
// send a special setup for the menu item
std::map<std::string, QTreeWidgetItem *> vendors =
PluginLoader::create_menu_items(
[&](QTreeWidgetItem *firstlevel_item) {
plugins->addChild(firstlevel_item);
firstlevel_item->setIcon(0, m_folderIcon);
},
[&](QTreeWidgetItem *secondlevel_item) {
secondlevel_item->setIcon(0, m_fxIcon);
});
m_fxTree->insertTopLevelItems(0, fxItems);
connect(m_fxTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)),
SLOT(onItemDoubleClicked(QTreeWidgetItem *, int)));
addWidget(m_fxTree);
QPushButton *insertBtn = new QPushButton(tr("Insert"), this);
insertBtn->setFixedSize(65, 25);
connect(insertBtn, SIGNAL(clicked()), this, SLOT(onInsert()));
insertBtn->setDefault(true);
m_buttonLayout->addWidget(insertBtn);
QPushButton *addBtn = new QPushButton(tr("Add"), this);
addBtn->setFixedSize(65, 25);
connect(addBtn, SIGNAL(clicked()), this, SLOT(onAdd()));
m_buttonLayout->addWidget(addBtn);
QPushButton *replaceBtn = new QPushButton(tr("Replace"), this);
replaceBtn->setFixedHeight(25);
connect(replaceBtn, SIGNAL(clicked()), this, SLOT(onReplace()));
m_buttonLayout->addWidget(replaceBtn);
}
//-------------------------------------------------------------------
void InsertFxPopup::makeItem(QTreeWidgetItem *parent, std::string fxId) {
QTreeWidgetItem *fxItem = new QTreeWidgetItem(
(QTreeWidget *)0,
QStringList(QString::fromStdWString(TStringTable::translate(fxId))));
fxItem->setData(0, Qt::UserRole, QVariant(QString::fromStdString(fxId)));
parent->addChild(fxItem);
fxItem->setIcon(0, loadPreset(fxItem) ? m_presetIcon : m_fxIcon);
}
//-------------------------------------------------------------------
void InsertFxPopup::loadFolder(QTreeWidgetItem *parent) {
while (!m_is->eos()) {
std::string tagName;
if (m_is->matchTag(tagName)) {
// Found a sub-folder
QString folderName = QString::fromStdString(tagName);
std::unique_ptr<QTreeWidgetItem> folder(
new QTreeWidgetItem((QTreeWidget *)0, QStringList(folderName)));
folder->setIcon(0, m_folderIcon);
loadFolder(folder.get());
m_is->closeChild();
if (folder->childCount()) {
if (parent)
parent->addChild(folder.release());
else
m_fxTree->addTopLevelItem(folder.release());
}
} else {
// Found an fx
std::string fxName;
*m_is >> fxName;
makeItem(parent, fxName);
}
}
}
//-------------------------------------------------------------------
bool InsertFxPopup::loadFx(TFilePath fp) {
TIStream is(fp);
if (!is) return false;
m_is = &is;
try {
std::string tagName;
if (m_is->matchTag(tagName) && tagName == "fxs") {
loadFolder(0);
m_is->closeChild();
}
} catch (...) {
m_is = 0;
return false;
}
m_is = 0;
return true;
}
//-------------------------------------------------------------------
bool InsertFxPopup::loadPreset(QTreeWidgetItem *item) {
QString str = item->data(0, Qt::UserRole).toString();
TFilePath presetsFilepath(m_presetFolder + str.toStdWString());
int i;
for (i = item->childCount() - 1; i >= 0; i--)
item->removeChild(item->child(i));
if (TFileStatus(presetsFilepath).isDirectory()) {
TFilePathSet presets = TSystem::readDirectory(presetsFilepath);
if (!presets.empty()) {
for (TFilePathSet::iterator it2 = presets.begin(); it2 != presets.end();
++it2) {
TFilePath presetPath = *it2;
QString name(presetPath.getName().c_str());
QTreeWidgetItem *presetItem =
new QTreeWidgetItem((QTreeWidget *)0, QStringList(name));
presetItem->setData(0, Qt::UserRole, QVariant(toQString(presetPath)));
item->addChild(presetItem);
presetItem->setIcon(0, m_fxIcon);
}
} else
return false;
} else
return false;
return true;
}
//-------------------------------------------------------------------
void InsertFxPopup::loadMacro() {
TFilePath fp = m_presetFolder + TFilePath("macroFx");
try {
if (TFileStatus(fp).isDirectory()) {
TFilePathSet macros = TSystem::readDirectory(fp);
if (macros.empty()) return;
QTreeWidgetItem *macroFolder =
new QTreeWidgetItem((QTreeWidget *)0, QStringList(tr("Macro")));
macroFolder->setData(0, Qt::UserRole, QVariant(toQString(fp)));
macroFolder->setIcon(0, m_folderIcon);
m_fxTree->addTopLevelItem(macroFolder);
for (TFilePathSet::iterator it = macros.begin(); it != macros.end();
++it) {
TFilePath macroPath = *it;
QString name(macroPath.getName().c_str());
QTreeWidgetItem *macroItem =
new QTreeWidgetItem((QTreeWidget *)0, QStringList(name));
macroItem->setData(0, Qt::UserRole, QVariant(toQString(macroPath)));
macroItem->setIcon(0, m_fxIcon);
macroFolder->addChild(macroItem);
}
}
} catch (...) {
}
}
//-----------------------------------------------------------------------------
void InsertFxPopup::onItemDoubleClicked(QTreeWidgetItem *w, int c) {
if (w->childCount() == 0) // E' una foglia
onInsert();
}
//-----------------------------------------------------------------------------
void InsertFxPopup::onInsert() {
TFx *fx = createFx();
if (fx) {
TApp *app = TApp::instance();
TXsheetHandle *xshHandle = app->getCurrentXsheet();
QList<TFxP> fxs;
QList<TFxCommand::Link> links;
FxSelection *selection =
dynamic_cast<FxSelection *>(app->getCurrentSelection()->getSelection());
if (selection) {
fxs = selection->getFxs();
links = selection->getLinks();
}
TFxCommand::insertFx(fx, fxs, links, app,
app->getCurrentColumn()->getColumnIndex(),
app->getCurrentFrame()->getFrameIndex());
xshHandle->notifyXsheetChanged();
}
}
//-----------------------------------------------------------------------------
void InsertFxPopup::onAdd() {
TFx *fx = createFx();
if (fx) {
TApp *app = TApp::instance();
TXsheetHandle *xshHandle = app->getCurrentXsheet();
QList<TFxP> fxs;
FxSelection *selection =
dynamic_cast<FxSelection *>(app->getCurrentSelection()->getSelection());
if (selection) fxs = selection->getFxs();
TFxCommand::addFx(fx, fxs, app, app->getCurrentColumn()->getColumnIndex(),
app->getCurrentFrame()->getFrameIndex());
xshHandle->notifyXsheetChanged();
}
}
//-----------------------------------------------------------------------------
void InsertFxPopup::onReplace() {
TFx *fx = createFx();
if (fx) {
TApp *app = TApp::instance();
TXsheetHandle *xshHandle = app->getCurrentXsheet();
QList<TFxP> fxs;
FxSelection *selection =
dynamic_cast<FxSelection *>(app->getCurrentSelection()->getSelection());
if (selection) fxs = selection->getFxs();
TFxCommand::replaceFx(fx, fxs, app->getCurrentXsheet(),
app->getCurrentFx());
xshHandle->notifyXsheetChanged();
}
}
//-----------------------------------------------------------------------------
TFx *InsertFxPopup::createFx() {
TApp *app = TApp::instance();
ToonzScene *scene = app->getCurrentScene()->getScene();
TXsheet *xsh = scene->getXsheet();
QTreeWidgetItem *item = m_fxTree->currentItem();
QString text = item->data(0, Qt::UserRole).toString();
if (text.isEmpty()) return 0;
TFx *fx;
TFilePath path = TFilePath(text.toStdWString());
if (TFileStatus(path).doesExist() &&
TFileStatus(path.getParentDir()).isDirectory()) {
std::string folder = path.getParentDir().getName();
if (folder == "macroFx") // Devo caricare una macro
fx = createMacroFxByPath(path);
else // Verifico se devo caricare un preset
{
folder = path.getParentDir().getParentDir().getName();
if (folder == "presets") // Devo caricare un preset
fx = createPresetFxByName(path);
}
} else
fx = createFxByName(text.toStdString());
if (fx)
return fx;
else
return 0;
}
//-----------------------------------------------------------------------------
void InsertFxPopup::showEvent(QShowEvent *) {
updatePresets();
connect(TApp::instance()->getCurrentFx(), SIGNAL(fxPresetSaved()),
SLOT(updatePresets()));
}
//-----------------------------------------------------------------------------
void InsertFxPopup::hideEvent(QHideEvent *e) {
disconnect(TApp::instance()->getCurrentFx(), SIGNAL(fxPresetSaved()), this,
SLOT(updatePresets()));
Dialog::hideEvent(e);
}
//-----------------------------------------------------------------------------
void InsertFxPopup::contextMenuEvent(QContextMenuEvent *event) {
QTreeWidgetItem *item = m_fxTree->currentItem();
QString itemRole = item->data(0, Qt::UserRole).toString();
TFilePath path = TFilePath(itemRole.toStdWString());
if (TFileStatus(path).doesExist() &&
TFileStatus(path.getParentDir()).isDirectory()) {
QMenu *menu = new QMenu(this);
std::string folder = path.getParentDir().getName();
if (folder == "macroFx") // Menu' macro
{
QAction *remove = new QAction(tr("Remove Macro FX"), menu);
connect(remove, SIGNAL(triggered()), this, SLOT(removePreset()));
menu->addAction(remove);
} else // Verifico se devo caricare un preset
{
folder = path.getParentDir().getParentDir().getName();
if (folder == "presets") // Menu' preset
{
QAction *remove = new QAction(tr("Remove Preset"), menu);
connect(remove, SIGNAL(triggered()), this, SLOT(removePreset()));
menu->addAction(remove);
}
}
menu->exec(event->globalPos());
}
}
//-------------------------------------------------------------------
void InsertFxPopup::updatePresets() {
int i;
for (i = 0; i < m_fxTree->topLevelItemCount(); i++) {
QTreeWidgetItem *folder = m_fxTree->topLevelItem(i);
TFilePath path =
TFilePath(folder->data(0, Qt::UserRole).toString().toStdWString());
if (folder->text(0).toStdString() == "Plugins") {
continue;
}
if (path.getName() == "macroFx") {
int j;
for (j = folder->childCount() - 1; j >= 0; j--)
folder->removeChild(folder->child(j));
m_fxTree->removeItemWidget(folder, 0);
delete folder;
} else if (path.getParentDir().getName() == "macroFx")
continue;
else
for (int i = 0; i < folder->childCount(); i++) {
bool isPresetLoaded = loadPreset(folder->child(i));
if (isPresetLoaded)
folder->child(i)->setIcon(0, m_presetIcon);
else
folder->child(i)->setIcon(0, m_fxIcon);
}
}
loadMacro();
update();
}
//-----------------------------------------------------------------------------
void InsertFxPopup::removePreset() {
QTreeWidgetItem *item = m_fxTree->currentItem();
QString itemRole = item->data(0, Qt::UserRole).toString();
TFilePath path = TFilePath(itemRole.toStdWString());
QString question = QString(
tr("Are you sure you want to delete %1?").arg(path.getName().c_str()));
int ret = DVGui::MsgBox(question, tr("Yes"), tr("No"), 1);
if (ret == 2 || ret == 0) return;
try {
TSystem::deleteFile(path);
} catch (...) {
error(QString(tr("It is not possible to delete %1.").arg(toQString(path))));
return;
}
m_fxTree->removeItemWidget(item, 0);
delete item;
TApp::instance()->getCurrentFx()->notifyFxPresetRemoved();
}
//=============================================================================
OpenPopupCommandHandler<InsertFxPopup> openInsertFxPopup(MI_InsertFx);