

#include "toonzqt/studiopaletteviewer.h"

// TnzQt includes
#include "toonzqt/menubarcommand.h"
#include "toonzqt/paletteviewer.h"
#include "toonzqt/trepetitionguard.h"
#include "toonzqt/gutil.h"
#include "toonzqt/icongenerator.h"
#include "toonzqt/intfield.h"
#include "palettesscanpopup.h"
#include "palettedata.h"

// TnzLib includes
#include "toonz/studiopalettecmd.h"
#include "toonz/tpalettehandle.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/toonzscene.h"
#include "toonz/sceneproperties.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/preferences.h"

// TnzCore includes
#include "saveloadqsettings.h"
#include "tconvert.h"
#include "tundo.h"
#include "tsystem.h"

#include "../toonz/menubarcommandids.h"

// Qt includes
#include <QSettings>
#include <QHeaderView>
#include <QContextMenuEvent>
#include <QMenu>
#include <QUrl>
#include <QPainter>
#include <QVBoxLayout>
#include <QToolBar>
#include <QInputDialog>
#include <QPushButton>
#include <QDrag>
#include <QApplication>

#include <time.h>

using namespace std;
using namespace PaletteViewerGUI;
using namespace DVGui;

//=============================================================================
namespace {
//-----------------------------------------------------------------------------
/*! Return true if path is in folder \b rootPath of \b StudioPalette.
 */
bool isInStudioPaletteFolder(TFilePath path, TFilePath rootPath) {
  if (path.getType() != "tpl") return false;
  StudioPalette *studioPalette = StudioPalette::instance();
  std::vector<TFilePath> childrenPath;
  studioPalette->getChildren(childrenPath, rootPath);
  int i;
  for (i = 0; i < (int)childrenPath.size(); i++) {
    if (path == childrenPath[i])
      return true;
    else if (isInStudioPaletteFolder(path, childrenPath[i]))
      return true;
  }
  return false;
}

//-----------------------------------------------------------------------------
/*! Return true if path is in a \b StudioPalette folder.
 */
bool isInStudioPalette(TFilePath path) {
  if (path.getType() != "tpl") return false;
  StudioPalette *studioPalette = StudioPalette::instance();
  if (isInStudioPaletteFolder(path, studioPalette->getLevelPalettesRoot()))
    return true;
  if (isInStudioPaletteFolder(path, studioPalette->getProjectPalettesRoot()))
    return true;
  return false;
}

//-----------------------------------------------------------------------------
}  // namespace
//-----------------------------------------------------------------------------

//=============================================================================
// StudioPaletteTreeViewer
//-----------------------------------------------------------------------------

StudioPaletteTreeViewer::StudioPaletteTreeViewer(
    QWidget *parent, TPaletteHandle *studioPaletteHandle,
    TPaletteHandle *levelPaletteHandle, TXsheetHandle *xsheetHandle,
    TXshLevelHandle *currentLevelHandle)
    : QTreeWidget(parent)
    , m_dropItem(0)
    , m_studioPaletteHandle(studioPaletteHandle)
    , m_levelPaletteHandle(levelPaletteHandle)
    , m_currentLevelHandle(currentLevelHandle)
    , m_xsheetHandle(xsheetHandle)
    , m_folderIcon(QIcon())
    , m_levelPaletteIcon(QIcon())
    , m_studioPaletteIcon(QIcon())
    , m_startPos() {
  setIndentation(14);
  setAlternatingRowColors(true);

  header()->close();
  setUniformRowHeights(true);
  setIconSize(QSize(21, 18));

  QList<QTreeWidgetItem *> paletteItems;

  QString levelPaletteIcon = QString(":Resources/palette.svg");
  m_levelPaletteIcon.addPixmap(levelPaletteIcon, QIcon::Normal, QIcon::On);
  QString studioPaletteIcon = QString(":Resources/studiopalette.svg");
  m_studioPaletteIcon.addPixmap(studioPaletteIcon, QIcon::Normal, QIcon::On);

  StudioPalette *studioPalette = StudioPalette::instance();

  TFilePath levelPalettePath = studioPalette->getLevelPalettesRoot();
  paletteItems.append(createRootItem(levelPalettePath));

  TFilePath projectPalettePath = studioPalette->getProjectPalettesRoot();
  if (TSystem::doesExistFileOrLevel(projectPalettePath))
    paletteItems.append(createRootItem(projectPalettePath));

  insertTopLevelItems(0, paletteItems);

  bool ret = connect(this, SIGNAL(itemChanged(QTreeWidgetItem *, int)),
                     SLOT(onItemChanged(QTreeWidgetItem *, int)));
  ret      = ret && connect(this, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
                       SLOT(onItemClicked(QTreeWidgetItem *, int)));
  ret =
      ret &&
      connect(this,
              SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)),
              SLOT(onCurrentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
  ret = ret && connect(this, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this,
                       SLOT(onTreeItemExpanded(QTreeWidgetItem *)));

  // refresh tree with shortcut key
  QAction *refreshAct = CommandManager::instance()->getAction(MI_RefreshTree);
  ret                 = ret && connect(refreshAct, SIGNAL(triggered()), this,
                       SLOT(onRefreshTreeShortcutTriggered()));
  addAction(refreshAct);

  m_palettesScanPopup = new PalettesScanPopup();

  setAcceptDrops(true);

  // Per la selezione multipla
  setSelectionMode(QAbstractItemView::ExtendedSelection);

  StudioPalette::instance()->addListener(this);
  TProjectManager::instance()->addListener(this);

  refresh();

  assert(ret);
}

//-----------------------------------------------------------------------------

StudioPaletteTreeViewer::~StudioPaletteTreeViewer() {
  StudioPalette::instance()->removeListener(this);
  TProjectManager::instance()->removeListener(this);
}

//-----------------------------------------------------------------------------
void StudioPaletteTreeViewer::setCurrentLevelHandle(
    TXshLevelHandle *currentLevelHandle) {
  m_currentLevelHandle = currentLevelHandle;
}

//---------------------------------------------------------------------------

void StudioPaletteTreeViewer::setLevelPaletteHandle(
    TPaletteHandle *paletteHandle) {
  m_levelPaletteHandle = paletteHandle;
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::setStdPaletteHandle(
    TPaletteHandle *studioPaletteHandle) {
  m_studioPaletteHandle = studioPaletteHandle;
}

//-----------------------------------------------------------------------------

QTreeWidgetItem *StudioPaletteTreeViewer::createRootItem(TFilePath path) {
  QString rootName = QString::fromStdWString(path.getWideName());
  if (rootName != "Global Palettes") rootName = "Project Palettes";
  QTreeWidgetItem *rootItem =
      new QTreeWidgetItem((QTreeWidget *)0, QStringList(rootName));
  rootItem->setIcon(0, createQIcon("folder", true));
  rootItem->setData(1, Qt::UserRole, toQString(path));

  refreshItem(rootItem);

  return rootItem;
}

//-----------------------------------------------------------------------------

bool StudioPaletteTreeViewer::isRootItem(QTreeWidgetItem *item) {
  assert(item);
  TFilePath path = getItemPath(item);

  StudioPalette *studioPalette = StudioPalette::instance();
  if (path == studioPalette->getLevelPalettesRoot() ||
      path == studioPalette->getProjectPalettesRoot())
    return true;

  return false;
}

//-----------------------------------------------------------------------------

QTreeWidgetItem *StudioPaletteTreeViewer::createItem(const TFilePath path) {
  StudioPalette *studioPalette = StudioPalette::instance();
  QString itemName             = toQString(TFilePath(path.getWideName()));
  QTreeWidgetItem *item =
      new QTreeWidgetItem((QTreeWidget *)0, QStringList(itemName));
  item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable |
                 Qt::ItemIsDragEnabled | Qt::ItemIsEnabled);
  if (studioPalette->isPalette(path)) {
    if (studioPalette->hasGlobalName(path))
      item->setIcon(0, m_studioPaletteIcon);
    else
      item->setIcon(0, m_levelPaletteIcon);
    item->setFlags(item->flags() | Qt::ItemNeverHasChildren);
  } else if (studioPalette->isFolder(path)) {
    item->setIcon(0, createQIcon("folder", true));
    item->setFlags(item->flags() | Qt::ItemIsDropEnabled);
  }
  item->setData(1, Qt::UserRole, toQString(path));

  return item;
}

//-----------------------------------------------------------------------------

TFilePath StudioPaletteTreeViewer::getItemPath(QTreeWidgetItem *item) {
  TFilePath path =
      (item) ? TFilePath(item->data(1, Qt::UserRole).toString().toStdWString())
             : TFilePath();
  return path;
}

//-----------------------------------------------------------------------------

TFilePath StudioPaletteTreeViewer::getCurrentFolderPath() {
  return getItemPath(currentItem());
}

//-----------------------------------------------------------------------------

QTreeWidgetItem *StudioPaletteTreeViewer::getItem(const TFilePath path) {
  QList<QTreeWidgetItem *> oldItems =
      findItems(QString(""), Qt::MatchContains, 0);
  if (oldItems.isEmpty()) return 0;
  int i;
  for (i = 0; i < (int)oldItems.size(); i++) {
    TFilePath oldItemPath(
        oldItems[i]->data(1, Qt::UserRole).toString().toStdWString());
    if (oldItemPath == path)
      return oldItems[i];
    else {
      QTreeWidgetItem *item = getFolderItem(oldItems[i], path);
      if (item) return item;
    }
  }
  return 0;
}

//-----------------------------------------------------------------------------

QTreeWidgetItem *StudioPaletteTreeViewer::getFolderItem(QTreeWidgetItem *parent,
                                                        const TFilePath path) {
  int childrenCount = parent->childCount();
  int i;
  for (i = 0; i < childrenCount; i++) {
    QTreeWidgetItem *item = parent->child(i);
    if (getItemPath(item) == path)
      return item;
    else {
      item = getFolderItem(item, path);
      if (item) return item;
    }
  }
  return 0;
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::resetDropItem() {
  if (!m_dropItem) return;
  m_dropItem = 0;
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::refresh() {
  m_openedItems.clear();

  StudioPalette *studioPalette = StudioPalette::instance();

  TFilePath levelPalettePath = studioPalette->getLevelPalettesRoot();
  refreshItem(getItem(levelPalettePath));

  TFilePath projectPalettePath = studioPalette->getProjectPalettesRoot();
  if (!TSystem::doesExistFileOrLevel(projectPalettePath)) return;
  refreshItem(getItem(projectPalettePath));

  // refresh all expanded items
  QList<QTreeWidgetItem *> items =
      findItems(QString(""), Qt::MatchContains | Qt::MatchRecursive, 0);
  if (items.isEmpty()) return;

  for (int i = 0; i < (int)items.size(); i++)
    if (items[i]->isExpanded()) refreshItem(items[i]);
}

//-----------------------------------------------------------------------------
/*!When expand a tree, prepare the child items of it
 */
void StudioPaletteTreeViewer::onTreeItemExpanded(QTreeWidgetItem *item) {
  if (!item) return;

  // if this item was not yet opened, then get the children of this item
  if (!m_openedItems.contains(item)) refreshItem(item);

  // expand the item
  item->setExpanded(!item->isExpanded());
}

//-----------------------------------------------------------------------------
/*! Refresh tree only when this widget has focus
 */
void StudioPaletteTreeViewer::onRefreshTreeShortcutTriggered() {
  if (hasFocus()) refresh();
}

//-----------------------------------------------------------------------------
/*! Update the content of item
 */

void StudioPaletteTreeViewer::refreshItem(QTreeWidgetItem *item) {
  struct Locals {
    bool isUpper(const TFilePath &fp1, const TFilePath &fp2) {
      bool fp1IsFolder = StudioPalette::instance()->isFolder(fp1);
      bool fp2IsFolder = StudioPalette::instance()->isFolder(fp2);
      if (fp1IsFolder == fp2IsFolder)
        return fp1 < fp2;
      else
        return fp1IsFolder;
    }
  } locals;

  TFilePath folderPath = getItemPath(item);
  assert(folderPath != TFilePath());
  // correct only tpl files and folders
  std::vector<TFilePath> childrenPath;
  StudioPalette::instance()->getChildren(childrenPath, folderPath);
  int currentChildCount = item->childCount();
  std::vector<QTreeWidgetItem *> currentChildren;
  int i;
  for (i = 0; i < currentChildCount; i++)
    currentChildren.push_back(item->child(i));

  int childrenPathCount = childrenPath.size();
  int itemIndex         = 0;
  int pathIndex         = 0;
  while (itemIndex < currentChildCount || pathIndex < childrenPathCount) {
    TFilePath path =
        (pathIndex < childrenPathCount) ? childrenPath[pathIndex] : TFilePath();

    QTreeWidgetItem *currentItem =
        (itemIndex < currentChildCount) ? currentChildren[itemIndex] : 0;
    TFilePath currentItemPath = getItemPath(currentItem);

    if (path == currentItemPath) {
      itemIndex++;
      pathIndex++;
    } else if ((!path.isEmpty() && locals.isUpper(path, currentItemPath)) ||
               currentItemPath.isEmpty()) {
      currentItem = createItem(path);
      item->insertChild(pathIndex, currentItem);
      pathIndex++;
    } else {
      assert(locals.isUpper(currentItemPath, path) || path.isEmpty());
      assert(currentItem);
      item->removeChild(currentItem);
      itemIndex++;
    }
  }
  m_openedItems.insert(item);
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::resetProjectPaletteFolder() {
  int projectPaletteIndex = 1;
  TFilePath projectPalettePath =
      StudioPalette::instance()->getProjectPalettesRoot();
  // Prendo l'item del project palette.
  QTreeWidgetItem *projectPaletteItem = topLevelItem(projectPaletteIndex);
  if (projectPaletteItem) {
    // Se il path dell'item e' uguale a quello del project palette corrente
    // ritorno.
    if (getItemPath(projectPaletteItem) == projectPalettePath) return;
    // Altrimenti lo rimuovo.
    removeItemWidget(projectPaletteItem, 0);
    delete projectPaletteItem;
    // clear the item list in order to search the folder from scratch
    m_openedItems.clear();
    // Toonz Palette is not changed, so resurrect the ToonzPaletteRoot
    m_openedItems.insert(topLevelItem(0));
  }
  if (!TSystem::doesExistFileOrLevel(projectPalettePath)) return;
  // Creo il nuovo item con il nuovo project folder e lo inserisco nell'albero.
  // Items in the ProjectPaletteRoot are refreshed here. Stored in openedItems
  // as well.
  QTreeWidgetItem *newProjectPaletteItem = createRootItem(projectPalettePath);
  insertTopLevelItem(projectPaletteIndex, newProjectPaletteItem);

  setCurrentItem(0);
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::onItemClicked(QTreeWidgetItem *item, int column) {
  if (currentItem() && m_studioPaletteHandle && m_currentPalette.getPointer()) {
    // if(m_studioPaletteHandle->getPalette() != m_currentPalette.getPointer())
    {
      m_studioPaletteHandle->setPalette(m_currentPalette.getPointer());
      m_studioPaletteHandle->notifyPaletteSwitched();
      StudioPaletteCmd::updateAllLinkedStyles(m_levelPaletteHandle,
                                              m_xsheetHandle);
    }
  }
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::onItemChanged(QTreeWidgetItem *item, int column) {
  if (item != currentItem() || isRootItem(item)) return;
  wstring name      = item->text(column).toStdWString();
  TFilePath oldPath = getCurrentFolderPath();
  if (oldPath.isEmpty() || name.empty() || oldPath.getWideName() == name)
    return;
  TFilePath newPath(oldPath.getParentDir() +
                    TFilePath(name + ::to_wstring(oldPath.getDottedType())));
  try {
    StudioPaletteCmd::movePalette(newPath, oldPath);
  } catch (TException &e) {
    error(QString(::to_string(e.getMessage()).c_str()));
    item->setText(column, QString::fromStdWString(oldPath.getWideName()));
  } catch (...) {
    error("Can't rename file");
    item->setText(column, QString::fromStdWString(oldPath.getWideName()));
  }
  refreshItem(getItem(oldPath.getParentDir()));
  setCurrentItem(getItem(newPath));
}

//-----------------------------------------------------------------------------
/*! Called when the current palette is switched
 */
void StudioPaletteTreeViewer::onCurrentItemChanged(QTreeWidgetItem *current,
                                                   QTreeWidgetItem *previous) {
  TFilePath oldPath = getItemPath(previous);
  TFilePath newPath = getCurrentFolderPath();
  if (!m_studioPaletteHandle) return;

  if (m_currentPalette.getPointer() && m_currentPalette->getDirtyFlag()) {
    TFilePath oldPath = StudioPalette::instance()->getPalettePath(
        m_currentPalette->getGlobalName());
    if (oldPath == newPath) return;
    wstring gname = m_currentPalette->getGlobalName();
    QString question =
        tr("The current palette %1\nin the studio palette has been modified. "
           "Do you want to "
           "save your changes?")
            .arg(QString::fromStdWString(oldPath.getWideString()));
    int ret =
        DVGui::MsgBox(question, tr("Save"), tr("Discard"), tr("Cancel"), 0);
    if (ret == 3) {
      setCurrentItem(getItem(oldPath));
      return;
    }
    if (ret == 1) {
      try {
        // If the palette is level palette (i.e. NOT stdio palette), just
        // overwrite it
        if (gname.empty())
          StudioPalette::instance()->save(oldPath,
                                          m_currentPalette.getPointer());
        else
          StudioPalette::instance()->setPalette(
              oldPath, m_currentPalette.getPointer(), false);
      } catch (TSystemException se) {
        DVGui::warning(QString::fromStdWString(se.getMessage()));
        setCurrentItem(previous);
        return;
      } catch (...) {
        DVGui::warning(
            QString::fromStdWString(oldPath.getWideString() + L"\n") +
            tr("Failed to save palette."));
        setCurrentItem(previous);
        return;
      }
    }
    m_currentPalette->setDirtyFlag(false);
  }
  // load palette here
  m_currentPalette = StudioPalette::instance()->getPalette(newPath, false);
  m_studioPaletteHandle->setPalette(m_currentPalette.getPointer());
  m_studioPaletteHandle->notifyPaletteSwitched();
  StudioPaletteCmd::updateAllLinkedStyles(m_levelPaletteHandle, m_xsheetHandle);
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::addNewPalette() {
  if (!currentItem()) {
    error("Error: No folder selected.");
    return;
  }
  TFilePath newPath;
  try {
    newPath = StudioPaletteCmd::createPalette(getCurrentFolderPath(), "", 0);
  } catch (TException &e) {
    error("Can't create palette: " +
          QString(::to_string(e.getMessage()).c_str()));
  } catch (...) {
    error("Can't create palette");
  }
  refreshItem(currentItem());
  setCurrentItem(getItem(newPath));
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::addNewFolder() {
  if (!currentItem()) {
    error("Error: No folder selected.");
    return;
  }
  TFilePath newPath;
  try {
    newPath = StudioPaletteCmd::addFolder(getCurrentFolderPath());
  } catch (TException &e) {
    error("Can't create palette folder: " +
          QString(::to_string(e.getMessage()).c_str()));
  } catch (...) {
    error("Can't create palette folder");
  }
  refreshItem(currentItem());
  setCurrentItem(getItem(newPath));
}

//-----------------------------------------------------------------------------
/*! Convert level palette to studio palette.
 */
void StudioPaletteTreeViewer::convertToStudioPalette() {
  TFilePath path               = getItemPath(currentItem());
  StudioPalette *studioPalette = StudioPalette::instance();
  if (studioPalette->isPalette(path)) {
    TPalette *palette = studioPalette->getPalette(path);

    if (!palette) {
      error("Can't touch palette");
      return;
    }

    if (m_currentPalette->getPaletteName() != palette->getPaletteName()) {
      error("Can't touch palette");
      return;
    }

    QString question =
        tr("Convert %1 to Studio Palette and Overwrite. \nAre you sure ?")
            .arg(QString::fromStdWString(path.getWideString()));
    int ret = DVGui::MsgBox(question, tr("Convert"), tr("Cancel"));
    if (ret == 0 || ret == 2) return;

    // apply global name
    time_t ltime;
    time(&ltime);
    wstring gname = std::to_wstring(ltime) + L"_" + std::to_wstring(rand());
    m_currentPalette->setGlobalName(gname);
    studioPalette->setStylesGlobalNames(m_currentPalette.getPointer());
    studioPalette->save(path, m_currentPalette.getPointer());

    m_currentPalette->setDirtyFlag(false);
    currentItem()->setIcon(0, m_studioPaletteIcon);

  } else
    error("Can't find palette");
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::deleteItem(QTreeWidgetItem *item) {
  QTreeWidgetItem *parent = item->parent();
  if (!parent) return;

  if (item->childCount() > 0) {
    QString question;
    question = tr("This folder is not empty. Delete anyway?");
    int ret  = DVGui::MsgBox(question, tr("Delete"), tr("Cancel"));
    if (ret == 0 || ret == 2) return;
  }

  TFilePath path               = getItemPath(item);
  StudioPalette *studioPalette = StudioPalette::instance();
  if (studioPalette->isFolder(path)) {
    try {
      StudioPaletteCmd::deleteFolder(path);
    } catch (TException &e) {
      error("Can't delete folder: " +
            QString(::to_string(e.getMessage()).c_str()));
    } catch (...) {
      error("Can't delete folder");
    }
  } else {
    assert(studioPalette->isPalette(path));
    try {
      StudioPaletteCmd::deletePalette(path);
    } catch (TException &e) {
      error("Can't delete palette: " +
            QString(::to_string(e.getMessage()).c_str()));
    } catch (...) {
      error("Can't delete palette");
    }
  }

  refreshItem(parent);
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::deleteItems() {
  QList<QTreeWidgetItem *> items = selectedItems();
  int count                      = items.size();

  if (count == 0) {
    error("Nothing to delete");
    return;
  }
  int i;
  TUndoManager::manager()->beginBlock();
  for (i = 0; i < count; i++) deleteItem(items[i]);
  TUndoManager::manager()->endBlock();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::searchForPalette() {
  m_palettesScanPopup->setCurrentFolder(getCurrentFolderPath());
  int ret = m_palettesScanPopup->exec();
  if (ret == QDialog::Accepted) refresh();
}

//-----------------------------------------------------------------------------

class InvalidateIconsUndo final : public TUndo {
  TPaletteP m_targetPalette, m_oldPalette, m_newPalette;
  TXshLevelHandle *m_levelHandle;

public:
  InvalidateIconsUndo(TXshLevelHandle *levelHandle)
      : m_levelHandle(levelHandle) {}

  void undo() const override {
    TXshLevel *level = m_levelHandle->getLevel();
    if (level) {
      std::vector<TFrameId> fids;
      level->getFids(fids);
      invalidateIcons(level, fids);
    }
  }
  void redo() const override { undo(); }

  int getSize() const override { return sizeof(*this); }
};

//----------------------------------------------------------------------

//-----------------------------------------------------------------------------

AdjustPaletteDialog::AdjustPaletteDialog()
    : Dialog(0, true, true, "Adjust Current Level to This Palette") {
  setWindowTitle(tr("Adjust Current Level to This Palette"));

  beginVLayout();
  m_tolerance = new IntField(this);
  m_tolerance->setRange(0, 255);
  m_tolerance->setValue(0);
  addWidget(tr("Tolerance"), m_tolerance);
  endVLayout();

  QPushButton *okBtn = new QPushButton(tr("Apply"), this);
  okBtn->setDefault(true);
  QPushButton *cancelBtn = new QPushButton(tr("Cancel"), this);
  bool ret = connect(okBtn, SIGNAL(clicked()), this, SLOT(accept()));
  ret      = ret && connect(cancelBtn, SIGNAL(clicked()), this, SLOT(reject()));
  assert(ret);

  addButtonBarWidget(okBtn, cancelBtn);
}

int AdjustPaletteDialog::getTolerance() { return m_tolerance->getValue(); }
//-------------------------------------------------------------------------------------------------

void StudioPaletteTreeViewer::loadInCurrentPaletteAndAdaptLevel() {
  QList<QTreeWidgetItem *> items = selectedItems();
  assert(items.size() == 1);

  TPalette *palette = m_levelPaletteHandle->getPalette();
  if (!palette) return;

  // prevent crash when the command is applied to the palette level
  if (!m_currentLevelHandle->getSimpleLevel()) return;

  TPalette *newPalette =
      StudioPalette::instance()->getPalette(getItemPath(items[0]), true);
  if (!newPalette) return;

  AdjustPaletteDialog apd;

  if (apd.exec() != QDialog::Accepted) return;

  /* awful patch: since in StudioPaletteCmd(defined in toonzlib) I cannot use
the invalidateIcons(defined in toonzqt)
i do invalidate icons from here using a "fake TUndo", named InvalidateIconsUndo.
And, since I need to refresh icons at the end of the processing, i have to put
that fake undo twice, one before and one after.
this way , when the user do either an undo or a redo operation, I am ensured
that the last operation is the icon refresh...
*/
  TUndoManager::manager()->beginBlock();

  TUndoManager::manager()->add(new InvalidateIconsUndo(m_currentLevelHandle));

  StudioPaletteCmd::loadIntoCurrentPalette(m_levelPaletteHandle, newPalette,
                                           m_currentLevelHandle,
                                           apd.getTolerance());

  TUndoManager::manager()->add(new InvalidateIconsUndo(m_currentLevelHandle));

  TUndoManager::manager()->endBlock();

  InvalidateIconsUndo(m_currentLevelHandle).undo();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::loadInCurrentPalette() {
  QList<QTreeWidgetItem *> items = selectedItems();
  int count                      = items.size();
  if (count == 0) return;

  TPalette *palette = m_levelPaletteHandle->getPalette();
  if (!palette) return;

  if (palette->isLocked()) {
    DVGui::warning("Palette is Locked!");
    return;
  }

  TPalette *newPalette =
      StudioPalette::instance()->getPalette(getItemPath(items[0]), false);
  if (!newPalette) return;
  if (m_xsheetHandle) {
    int ret = DVGui::eraseStylesInDemand(palette, m_xsheetHandle, newPalette);
    if (ret == 0) return;
  }

  StudioPaletteCmd::loadIntoCurrentPalette(m_levelPaletteHandle, newPalette);
  m_currentLevelHandle->notifyLevelChange();

  TXshLevel *level = m_currentLevelHandle->getLevel();
  if (level) {
    std::vector<TFrameId> fids;
    level->getFids(fids);
    invalidateIcons(level, fids);
  }

  int i;
  for (i = 1; i < count; i++) {
    TFilePath path = getItemPath(items[i]);
    StudioPaletteCmd::mergeIntoCurrentPalette(m_levelPaletteHandle, path);
  }
  // in order to update the title bar of palette viewer
  m_levelPaletteHandle->getPalette()->setDirtyFlag(true);
  m_levelPaletteHandle->notifyPaletteChanged();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::replaceCurrentPalette() {
  QList<QTreeWidgetItem *> items = selectedItems();
  int count                      = items.size();
  if (count == 0) return;

  // exec confirmation dialog
  TPalette *current = m_levelPaletteHandle->getPalette();
  if (!current) return;

  QString label;
  if (count != 1)  // replacing to multiple palettes
    label = tr("Replacing all selected palettes with the palette \"%1\". \nAre "
               "you sure ?")
                .arg(QString::fromStdWString(current->getPaletteName()));
  else {
    TPalette *dstPalette =
        StudioPalette::instance()->getPalette(getItemPath(items[0]));
    if (!dstPalette) return;
    label = tr("Replacing the palette \"%1\" with the palette \"%2\". \nAre "
               "you sure ?")
                .arg(QString::fromStdWString(dstPalette->getPaletteName()))
                .arg(QString::fromStdWString(current->getPaletteName()));
  }

  int ret = DVGui::MsgBox(label, tr("Replace"), tr("Cancel"), 1);
  if (ret == 0 || ret == 2) return;

  TUndoManager::manager()->beginBlock();
  int i;
  for (i = 0; i < count; i++)
    StudioPaletteCmd::replaceWithCurrentPalette(
        m_levelPaletteHandle, m_studioPaletteHandle, getItemPath(items[i]));
  TUndoManager::manager()->endBlock();

  if (m_currentPalette) m_currentPalette->setDirtyFlag(false);
  // in order to update display
  onCurrentItemChanged(currentItem(), currentItem());
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::mergeToCurrentPalette() {
  QList<QTreeWidgetItem *> items = selectedItems();
  int count                      = items.size();
  if (count == 0) return;

  TUndoManager::manager()->beginBlock();
  int i;
  for (i = 0; i < count; i++)
    StudioPaletteCmd::mergeIntoCurrentPalette(m_levelPaletteHandle,
                                              getItemPath(items[i]));
  TUndoManager::manager()->endBlock();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::paintEvent(QPaintEvent *event) {
  QTreeWidget::paintEvent(event);
  QPainter p(viewport());
  if (m_dropItem) {
    p.setPen(QColor(50, 105, 200));
    p.drawRect(visualItemRect(m_dropItem));
  }
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::contextMenuEvent(QContextMenuEvent *event) {
  TFilePath path = getCurrentFolderPath();

  StudioPalette *studioPalette = StudioPalette::instance();

  // Menu' per la selezione singola
  QList<QTreeWidgetItem *> items = selectedItems();
  int count                      = items.size();
  if (count == 1) {
    // Verify if click position is in a row containing an item.
    QRect rect = visualItemRect(currentItem());
    if (!QRect(0, rect.y(), width(), rect.height()).contains(event->pos()))
      return;

    bool isFolder = (studioPalette->isFolder(path));

    QMenu menu(this);
    if (isFolder) {
      createMenuAction(menu, "", tr("New Palette"), "addNewPalette()");
      createMenuAction(menu, "", tr("New Folder"), "addNewFolder()");
    }

    if (studioPalette->isFolder(path) &&
        studioPalette->getLevelPalettesRoot() != path &&
        studioPalette->getProjectPalettesRoot() != path) {
      menu.addSeparator();
      createMenuAction(menu, "", tr("Delete Folder"), "deleteItems()");
    } else if (studioPalette->isPalette(path)) {
      if (m_studioPaletteHandle->getPalette()) {
        createMenuAction(menu, "MI_LoadIntoCurrentPalette",
                         tr("Load into Current Palette"),
                         "loadInCurrentPalette()");
        createMenuAction(menu, "MI_AdjustCurrentLevelToPalette",
                         tr("Adjust Current Level to This Palette"),
                         "loadInCurrentPaletteAndAdaptLevel()");
        createMenuAction(menu, "MI_MergeToCurrentPalette",
                         tr("Merge to Current Palette"),
                         "mergeToCurrentPalette()");
        if (!m_studioPaletteHandle->getPalette()->isLocked()) {
          createMenuAction(menu, "MI_ReplaceWithCurrentPalette",
                           tr("Replace with Current Palette"),
                           "replaceCurrentPalette()");
          menu.addSeparator();
          createMenuAction(menu, "MI_DeletePalette", tr("Delete Palette"),
                           "deleteItems()");
        }
      }
      if (!studioPalette->hasGlobalName(path)) {
        menu.addSeparator();
        createMenuAction(menu, "",
                         tr("Convert to Studio Palette and Overwrite"),
                         "convertToStudioPalette()");
      }
    }

    if (isFolder) {
      menu.addSeparator();
      createMenuAction(menu, "", tr("Search for Palettes"),
                       "searchForPalette()");
    }
    menu.exec(event->globalPos());
    return;
  }

  // Menu' per la selezione multipla
  // Verify if click position is in a row containing an item.
  bool areAllPalette      = true;
  bool isClickInSelection = false;
  int i;
  for (i = 0; i < count; i++) {
    QTreeWidgetItem *item = items[i];
    QRect rect            = visualItemRect(item);
    if (QRect(0, rect.y(), width(), rect.height()).contains(event->pos()))
      isClickInSelection = true;
    TFilePath path = getItemPath(item);
    if (studioPalette->isFolder(path)) areAllPalette = false;
  }
  if (!isClickInSelection) return;

  QMenu menu(this);
  if (areAllPalette) {
    createMenuAction(menu, "", tr("Load into Current Palette"),
                     "loadInCurrentPalette()");
    createMenuAction(menu, "", tr("Merge to Current Palette"),
                     "mergeToCurrentPalette()");
    createMenuAction(menu, "", tr("Replace with Current Palette"),
                     "replaceCurrentPalette()");
    menu.addSeparator();
  }
  createMenuAction(menu, "", tr("Delete"), "deleteItems()");

  menu.exec(event->globalPos());
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::createMenuAction(QMenu &menu, const char *id,
                                               QString name, const char *slot) {
  QAction *act = menu.addAction(name);
  string slotName(slot);
  slotName = string("1") + slotName;
  connect(act, SIGNAL(triggered()), slotName.c_str());
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::mousePressEvent(QMouseEvent *event) {
  QTreeWidget::mousePressEvent(event);
  // If left button is not pressed return
  if (event->button() == Qt::LeftButton) m_startPos = event->pos();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::mouseMoveEvent(QMouseEvent *event) {
  // If left button is not pressed return; is not drag event.
  if (!(event->buttons() & Qt::LeftButton)) return;
  if (!m_startPos.isNull() && (m_startPos - event->pos()).manhattanLength() >=
                                  QApplication::startDragDistance())
    startDragDrop();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::mouseReleaseEvent(QMouseEvent *event) {
  QTreeWidget::mouseReleaseEvent(event);
  if (event->button() == Qt::LeftButton) m_startPos = QPoint();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::startDragDrop() {
  TRepetitionGuard guard;
  if (!guard.hasLock()) return;

  QDrag *drag         = new QDrag(this);
  QMimeData *mimeData = new QMimeData;
  QList<QUrl> urls;

  QList<QTreeWidgetItem *> items = selectedItems();
  int i;

  for (i = 0; i < items.size(); i++) {
    // Sposto solo le palette.
    TFilePath path = getItemPath(items[i]);
    if (!path.isEmpty() &&
        (path.getType() == "tpl" || path.getType() == "pli" ||
         path.getType() == "tlv" || path.getType() == "tnz"))
      urls.append(pathToUrl(path));
  }
  if (urls.isEmpty()) return;
  mimeData->setUrls(urls);
  drag->setMimeData(mimeData);
  Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
  viewport()->update();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::dragEnterEvent(QDragEnterEvent *event) {
  const QMimeData *mimeData      = event->mimeData();
  const PaletteData *paletteData = dynamic_cast<const PaletteData *>(mimeData);

  if (acceptResourceDrop(mimeData->urls())) {
    QList<QUrl> urls = mimeData->urls();
    int count        = urls.size();
    if (count == 0) return;

    // Controllo che almeno un url del drag sia una palette da spostare.
    bool isPalette = false;
    int i;
    for (i = 0; i < count; i++) {
      QUrl url = urls[i];
      TFilePath path(url.toLocalFile().toStdWString());
      if (!path.isEmpty() && isInStudioPalette(path) &&
          (path.getType() == "tpl" || path.getType() == "pli" ||
           path.getType() == "tlv" || path.getType() == "tnz")) {
        isPalette = true;
        break;
      }
    }
    if (!isPalette) return;

    event->acceptProposedAction();
  } else if (paletteData && paletteData->hasOnlyPalette())
    event->acceptProposedAction();
  viewport()->update();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::dragMoveEvent(QDragMoveEvent *event) {
  QTreeWidgetItem *item = itemAt(event->pos());
  TFilePath newPath     = getItemPath(item);

  if (item) {
    // drop will not be executed on the same item
    const QMimeData *mimeData = event->mimeData();
    if (mimeData->hasUrls() && mimeData->urls().size() == 1) {
      TFilePath path =
          TFilePath(mimeData->urls()[0].toLocalFile().toStdWString());
      if (path == getItemPath(item)) {
        m_dropItem = 0;
        event->ignore();
        viewport()->update();
        return;
      }
    }
    // when dragging over other items, drop destination will be the parent
    // folder of it
    if (item->flags() & Qt::ItemNeverHasChildren) {
      item = item->parent();
    }
    m_dropItem = item;
    event->acceptProposedAction();
  } else {
    m_dropItem = 0;
    event->ignore();
  }
  viewport()->update();
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::dropEvent(QDropEvent *event) {
  TFilePath newPath = getItemPath(m_dropItem);

  resetDropItem();

  if (newPath.isEmpty()) return;

  const QMimeData *mimeData      = event->mimeData();
  const PaletteData *paletteData = dynamic_cast<const PaletteData *>(mimeData);
  if (paletteData) {
    if (paletteData->hasOnlyPalette()) {
      TPalette *palette = paletteData->getPalette();
      if (!palette) return;

      try {
        StudioPaletteCmd::createPalette(
            newPath, ::to_string(palette->getPaletteName()), palette);
      } catch (TException &e) {
        error("Can't create palette: " +
              QString(::to_string(e.getMessage()).c_str()));
      } catch (...) {
        error("Can't create palette");
      }
    }
    return;
  }

  if (!mimeData->hasUrls() || mimeData->urls().size() == 0) return;

  QList<QUrl> urls = mimeData->urls();

  // make the list of palette paths which will be actually moved
  QList<TFilePath> palettePaths;
  for (int i = 0; i < urls.size(); i++) {
    TFilePath path = TFilePath(urls[i].toLocalFile().toStdWString());
    if (path != newPath && path.getParentDir() != newPath)
      palettePaths.append(path);
  }
  if (palettePaths.isEmpty()) return;

  // open the confirmation dialog in order to prevent unintended move
  QString pltName;
  if (palettePaths.size() == 1)
    pltName = tr("the palette \"%1\"")
                  .arg(QString::fromStdWString(palettePaths[0].getWideName()));
  else
    pltName = tr("the selected palettes");
  QString dstName = QString::fromStdWString(newPath.getWideName());

  QString question =
      tr("Move %1 to \"%2\". Are you sure ?").arg(pltName).arg(dstName);
  int ret = DVGui::MsgBox(question, tr("Move"), tr("Cancel"));
  if (ret == 0 || ret == 2) return;

  bool paletteMoved = false;
  TUndoManager::manager()->beginBlock();
  for (int i = 0; i < palettePaths.size(); i++) {
    TFilePath path = palettePaths[i];
    if (isInStudioPalette(path)) {
      TFilePath newPalettePath =
          newPath +
          TFilePath(path.getWideName() + ::to_wstring(path.getDottedType()));
      try {
        StudioPaletteCmd::movePalette(newPalettePath, path);
        paletteMoved = true;
      } catch (TException &e) {
        error("Can't rename palette: " +
              QString(::to_string(e.getMessage()).c_str()));
      } catch (...) {
        error("Can't rename palette");
      }
    }
  }
  TUndoManager::manager()->endBlock();
  if (paletteMoved) {
    event->setDropAction(Qt::MoveAction);
    event->accept();
  }
}

//-----------------------------------------------------------------------------

void StudioPaletteTreeViewer::dragLeaveEvent(QDragLeaveEvent *event) {
  resetDropItem();
  update();
}

//=============================================================================
// StudioPaletteViewer
//-----------------------------------------------------------------------------

StudioPaletteViewer::StudioPaletteViewer(QWidget *parent,
                                         TPaletteHandle *studioPaletteHandle,
                                         TPaletteHandle *levelPaletteHandle,
                                         TFrameHandle *frameHandle,
                                         TXsheetHandle *xsheetHandle,
                                         TXshLevelHandle *currentLevelHandle)
    : QSplitter(parent) {
  // style sheet
  setObjectName("StudioPaletteViewer");
  setFrameStyle(QFrame::StyledPanel);

  setAcceptDrops(true);
  setOrientation(Qt::Vertical);

  // First Splitter Widget
  QWidget *treeWidget      = new QWidget(this);
  QVBoxLayout *treeVLayout = new QVBoxLayout(treeWidget);
  treeVLayout->setMargin(0);
  treeVLayout->setSpacing(0);

  m_studioPaletteTreeViewer = new StudioPaletteTreeViewer(
      treeWidget, studioPaletteHandle, levelPaletteHandle, xsheetHandle,
      currentLevelHandle);

  treeVLayout->addWidget(m_studioPaletteTreeViewer);
  treeWidget->setLayout(treeVLayout);

  // Second Splitter Widget
  m_studioPaletteViewer =
      new PaletteViewer(this, PaletteViewerGUI::STUDIO_PALETTE);
  m_studioPaletteViewer->setObjectName("PaletteViewerInStudioPalette");
  m_studioPaletteViewer->setXsheetHandle(xsheetHandle);
  m_studioPaletteViewer->setPaletteHandle(studioPaletteHandle);
  m_studioPaletteViewer->setFrameHandle(frameHandle);

  addWidget(treeWidget);
  addWidget(m_studioPaletteViewer);

  setFocusProxy(m_studioPaletteViewer);
}

//-----------------------------------------------------------------------------

StudioPaletteViewer::~StudioPaletteViewer() {}

//-----------------------------------------------------------------------------
/*! In order to save current palette from the tool button in the PageViewer.
 */
TFilePath StudioPaletteViewer::getCurrentItemPath() {
  return m_studioPaletteTreeViewer->getCurrentItemPath();
}

//-----------------------------------------------------------------------------

int StudioPaletteViewer::getViewMode() const {
  return m_studioPaletteViewer->getViewMode();
}

//-----------------------------------------------------------------------------

void StudioPaletteViewer::setViewMode(int mode) {
  m_studioPaletteViewer->setViewMode(
      (PaletteViewerGUI::PageViewer::ViewMode)mode);
}

//-----------------------------------------------------------------------------

void StudioPaletteViewer::save(QSettings &settings) const {
  m_studioPaletteViewer->save(settings);
}

void StudioPaletteViewer::load(QSettings &settings) {
  m_studioPaletteViewer->load(settings);
}