Blob Blame Raw


#include "castviewer.h"

// Tnz6 includes
#include "castselection.h"
#include "tapp.h"
#include "menubarcommandids.h"
#include "floatingpanelcommand.h"
#include "iocommand.h"
#include "filmstripcommand.h"
#include "flipbook.h"

// TnzLib includes
#include "toonz/tscenehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/levelset.h"
#include "toonz/toonzscene.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshleveltypes.h"

// TnzQt includes
#include "toonzqt/dvdialog.h"
#include "toonzqt/gutil.h"
#include "toonzqt/trepetitionguard.h"
#include "toonzqt/icongenerator.h"
#include "toonzqt/infoviewer.h"
#include "historytypes.h"

// TnzCore includes
#include "tundo.h"
#include "tfiletype.h"
#include "tsystem.h"

// Qt includes
#include <QDrag>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QDragLeaveEvent>
#include <QHeaderView>
#include <QPainter>
#include <QBoxLayout>
#include <QLabel>
#include <QByteArray>
#include <QDesktopServices>
#include <QUrl>
#include <QMenu>
#include <QDir>

const char *AudioFolderName = "Audio";

using namespace DVGui;

namespace {

// Se il widget del cast viewer viene spostato in toonzqt (e quindi fatto
// dipendere dallo sceneHandle)
// questo undo puo' essere spostato in un nuovo file levelsetcommand in
// toonzlib.
class MoveLevelToFolderUndo final : public TUndo {
  TLevelSet *m_levelSet;
  std::wstring m_levelName;
  TFilePath m_oldFolder;
  TFilePath m_newFolder;

public:
  MoveLevelToFolderUndo(TLevelSet *levelSet, std::wstring levelName,
                        TFilePath newFolder)
      : m_levelSet(levelSet), m_levelName(levelName), m_newFolder(newFolder) {
    TXshLevel *level = m_levelSet->getLevel(m_levelName);
    m_oldFolder      = m_levelSet->getFolder(level);
  }

  void undo() const override {
    TXshLevel *level = m_levelSet->getLevel(m_levelName);
    m_levelSet->moveLevelToFolder(m_oldFolder, level);
    TApp::instance()->getCurrentScene()->notifyCastChange();
  }

  void redo() const override {
    TXshLevel *level = m_levelSet->getLevel(m_levelName);
    m_levelSet->moveLevelToFolder(m_newFolder, level);
    TApp::instance()->getCurrentScene()->notifyCastChange();
  }

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

  QString getHistoryString() override {
    return QObject::tr("Move Level to Cast Folder");
  }
};
}  // namespace

//=============================================================================
//
// CastTreeViewer
//
//-----------------------------------------------------------------------------

CastTreeViewer::CastTreeViewer(QWidget *parent)
    : QTreeWidget(parent), m_dropTargetItem(0) {
  setObjectName("OnePixelMarginFrame");
  // setObjectName("BrowserTreeView");
  // setStyleSheet("#BrowserTreeView {border: 0px; margin: 1px;
  // qproperty-autoFillBackground: true;}");

  header()->close();
  setIconSize(QSize(16, 16));
  setUniformRowHeights(true);
  // m_treeViewer->setColumnCount(1);

  setAcceptDrops(true);
  connect(this, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this,
          SLOT(onItemChanged(QTreeWidgetItem *, int)));
  connect(this,
          SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)),
          this, SLOT(onFolderChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
  connect(TApp::instance()->getCurrentScene(), SIGNAL(sceneSwitched()), this,
          SLOT(rebuildCastTree()));
  connect(TApp::instance()->getCurrentScene(),
          SIGNAL(castFolderAdded(const TFilePath &)), this,
          SLOT(onCastFolderAdded(const TFilePath &)));

  connect(TApp::instance()->getCurrentScene(), SIGNAL(nameSceneChanged()), this,
          SLOT(onSceneNameChanged()));

  // Connect all possible changes that can alter the
  // bottom horizontal scrollbar to resize contents...
  connect(this, SIGNAL(expanded(const QModelIndex &)), this,
          SLOT(resizeToConts()));

  connect(this, SIGNAL(collapsed(const QModelIndex &)), this,
          SLOT(resizeToConts()));

  connect(this->model(), SIGNAL(layoutChanged()), this, SLOT(resizeToConts()));

  rebuildCastTree();
}

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

//! Resizes viewport to contents
void CastTreeViewer::resizeToConts(void) { resizeColumnToContents(0); }

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

void CastTreeViewer::resizeEvent(QResizeEvent *event) {
  resizeColumnToContents(0);
  QTreeView::resizeEvent(event);
}

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

TLevelSet *CastTreeViewer::getLevelSet() {
  return TApp::instance()->getCurrentScene()->getScene()->getLevelSet();
}

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

void CastTreeViewer::enableCommands() {
  enableCommand(this, MI_Clear, &CastTreeViewer::deleteFolder);
}

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

void CastTreeViewer::onFolderChanged(QTreeWidgetItem *current,
                                     QTreeWidgetItem *previous) {
  // makes the selection current; serves to intercept the MI_Clear command
  makeCurrent();
}

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

void CastTreeViewer::populateFolder(QTreeWidgetItem *folder) {
  TFilePath folderPath(
      folder->data(1, Qt::DisplayRole).toString().toStdWString());
  std::vector<TFilePath> folders;
  getLevelSet()->listFolders(folders, folderPath);
  int i;
  for (i = 0; i < (int)folders.size(); i++) {
    TFilePath fp   = folders[i];
    QString fpQstr = QString::fromStdWString(fp.getWideString());
    QString name =
        QString::fromStdWString(fp.withoutParentDir().getWideString());
    QTreeWidgetItem *child =
        new QTreeWidgetItem((QTreeWidgetItem *)0, QStringList(name) << fpQstr);
    if (name != AudioFolderName)
      child->setFlags(child->flags() | Qt::ItemIsEditable);
    folder->addChild(child);
    populateFolder(child);
  }
}

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

void CastTreeViewer::onSceneNameChanged() {
  QTreeWidgetItem *root = topLevelItem(0);
  if (!root) return;
  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  QString rootName  = QString("Root");
  if (scene) {
    std::wstring name =
        (scene->isUntitled()) ? L"Untitled" : scene->getSceneName();
    rootName = rootName.fromStdWString(name);
  }
  root->setText(0, rootName);
}

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

void CastTreeViewer::onCastFolderAdded(const TFilePath &path) {
  QTreeWidgetItem *root = topLevelItem(0)->child(0);
  assert(root->data(1, Qt::DisplayRole).toString() ==
         toQString(getLevelSet()->getDefaultFolder()));
  QString childName     = QString::fromStdWString(path.getWideName());
  QString childPathQstr = QString::fromStdWString(path.getWideString());
  QTreeWidgetItem *childItem =
      new QTreeWidgetItem(root, QStringList(childName) << childPathQstr);
  childItem->setFlags(childItem->flags() | Qt::ItemIsEditable);
  root->addChild(childItem);
  setCurrentItem(childItem);
}

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

void CastTreeViewer::rebuildCastTree() {
  clear();
  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  QString rootName  = QString("Root");
  if (scene) {
    std::wstring name =
        (scene->isUntitled()) ? L"Untitled" : scene->getSceneName();
    rootName = rootName.fromStdWString(name);
  }

  QTreeWidgetItem *root =
      new QTreeWidgetItem((QTreeWidgetItem *)0, QStringList(rootName));
  static QIcon clapboard = createQIcon("clapboard");
  root->setIcon(0, clapboard);
  insertTopLevelItem(0, root);
  populateFolder(root);
}

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

QSize CastTreeViewer::sizeHint() const { return QSize(100, 100); }

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

void CastTreeViewer::paintEvent(QPaintEvent *event) {
  QTreeWidget::paintEvent(event);
  QPainter p(viewport());
  if (m_dropTargetItem) {
    p.setPen(QColor(50, 105, 200));
    p.drawRect(visualItemRect(m_dropTargetItem).adjusted(0, 0, -1, 0));
  }
}

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

void CastTreeViewer::dragEnterEvent(QDragEnterEvent *e) {
  m_dropFilePath = TFilePath();
  if (acceptResourceOrFolderDrop(e->mimeData()->urls())) {
    if (e->mimeData()->urls().size() != 1) return;
    TFilePath path(e->mimeData()->urls()[0].toLocalFile().toStdWString());

    if (path.getType() == "tnz")
      m_dropFilePath = path;
    else
      return;
  }
  if (!e->mimeData()->hasFormat("application/vnd.toonz.levels") &&
      m_dropFilePath == TFilePath())
    return;
  m_dropTargetItem = itemAt(e->pos());
  if (m_dropTargetItem &&
      m_dropTargetItem->data(0, Qt::DisplayRole).toString() == AudioFolderName)
    m_dropTargetItem = 0;

  e->acceptProposedAction();
  viewport()->update();
}

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

void CastTreeViewer::dragMoveEvent(QDragMoveEvent *event) {
  if (!event->mimeData()->hasFormat("application/vnd.toonz.levels") ||
      m_dropFilePath != TFilePath())
    return;

  m_dropTargetItem  = itemAt(event->pos());
  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  QString rootName  = QString("Root");
  if (scene) {
    std::wstring name =
        (scene->isUntitled()) ? L"Untitled" : scene->getSceneName();
    rootName = rootName.fromStdWString(name);
  }
  if ((m_dropTargetItem &&
       m_dropTargetItem->data(0, Qt::DisplayRole).toString() ==
           AudioFolderName) ||
      (m_dropFilePath != TFilePath() &&
       m_dropTargetItem->data(0, Qt::DisplayRole).toString() == rootName))
    m_dropTargetItem = 0;

  if (!m_dropTargetItem)
    event->ignore();
  else
    event->acceptProposedAction();
  viewport()->update();
}

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

void CastTreeViewer::dropEvent(QDropEvent *event) {
  m_dropTargetItem = 0;
  if (m_dropFilePath != TFilePath()) {
    IoCmd::loadScene(m_dropFilePath);
    m_dropFilePath   = TFilePath();
    m_dropTargetItem = 0;
    update();
    return;
  }

  if (!event->mimeData()->hasFormat("application/vnd.toonz.levels")) return;
  m_dropTargetItem = itemAt(event->pos());
  if (!m_dropTargetItem) return;
  TFilePath folderPath(
      m_dropTargetItem->data(1, Qt::DisplayRole).toString().toStdWString());
  m_dropTargetItem = 0;
  update();

  if (folderPath.getName() == AudioFolderName) {
    event->ignore();
    return;
  }
  event->acceptProposedAction();

  TLevelSet *levelSet = getLevelSet();

  const CastItems *castItems =
      dynamic_cast<const CastItems *>(event->mimeData());
  assert(castItems);
  if (!castItems) return;
  int i;
  TUndoManager::manager()->beginBlock();
  for (i = 0; i < castItems->getItemCount(); i++) {
    CastItem *item = castItems->getItem(i);
    if (LevelCastItem *li = dynamic_cast<LevelCastItem *>(item)) {
      TXshLevel *level       = li->getLevel();
      std::wstring levelName = level->getName();
      MoveLevelToFolderUndo *undo =
          new MoveLevelToFolderUndo(levelSet, levelName, folderPath);
      levelSet->moveLevelToFolder(folderPath, level);
      TUndoManager::manager()->add(undo);
    } else if (SoundCastItem *si = dynamic_cast<SoundCastItem *>(item)) {
    }
  }
  TUndoManager::manager()->endBlock();
  emit itemMovedToFolder();
}

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

void CastTreeViewer::dragLeaveEvent(QDragLeaveEvent *event) {
  m_dropTargetItem = 0;
  m_dropFilePath   = TFilePath();
  update();
}

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

void CastTreeViewer::onItemChanged(QTreeWidgetItem *item, int column) {
  if (column != 0) return;
  if (item->isSelected()) {
    TFilePath oldPath(item->data(1, Qt::DisplayRole).toString().toStdWString());
    TFilePath newPath =
        getLevelSet()->renameFolder(oldPath, item->text(0).toStdWString());
    item->setText(1, QString::fromStdWString(newPath.getWideString()));
  }
}

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

TFilePath CastTreeViewer::getCurrentFolder() const {
  if (!currentItem()) return TFilePath();
  QVariant data = currentItem()->data(1, Qt::DisplayRole);
  if (data == QVariant()) return TFilePath();
  return TFilePath(data.toString().toStdWString());
}

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

void CastTreeViewer::folderUp() {
  QTreeWidgetItem *item = currentItem();
  if (item && item->parent() != (QTreeWidgetItem *)0)
    setCurrentItem(item->parent());
}

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

void CastTreeViewer::newFolder() {
  QTreeWidgetItem *parentItem = currentItem();
  if (parentItem == (QTreeWidgetItem *)0) return;
  QString parentName = parentItem->data(0, Qt::DisplayRole).toString();
  if (parentName == AudioFolderName) return;

  TFilePath parentPath =
      TFilePath(parentItem->data(1, Qt::DisplayRole).toString().toStdWString());
  QString childName("New Folder");
  TFilePath childPath   = parentPath + childName.toStdWString();
  QString childPathQstr = QString::fromStdWString(childPath.getWideString());
  QTreeWidgetItem *childItem =
      new QTreeWidgetItem(parentItem, QStringList(childName) << childPathQstr);
  childItem->setFlags(childItem->flags() | Qt::ItemIsEditable);
  parentItem->addChild(childItem);
  setCurrentItem(childItem);
  editItem(childItem);
  TLevelSet *levelSet =
      TApp::instance()->getCurrentScene()->getScene()->getLevelSet();
  levelSet->createFolder(parentPath, childName.toStdWString());
  TApp::instance()->getCurrentScene()->setDirtyFlag(true);
}

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

void CastTreeViewer::deleteFolder() {
  QTreeWidgetItem *item = currentItem();
  if (!item || !item->parent()) return;
  QString itemName = item->data(0, Qt::DisplayRole).toString();
  if (itemName == AudioFolderName) return;
  int ret = DVGui::MsgBox(tr("Delete folder ") + item->text(0) + "?", tr("Yes"),
                          tr("No"), 1);
  if (ret == 2 || ret == 0) return;
  QTreeWidgetItem *parentItem = item->parent();

  TFilePath childPath(item->data(1, Qt::DisplayRole).toString().toStdWString());
  TLevelSet *levelSet =
      TApp::instance()->getCurrentScene()->getScene()->getLevelSet();
  levelSet->removeFolder(childPath);
  TApp::instance()->getCurrentScene()->setDirtyFlag(true);

  parentItem->removeChild(item);
  setCurrentItem(parentItem);
}

//=============================================================================
//
// CastBrowser
//
//-----------------------------------------------------------------------------

CastBrowser::CastBrowser(QWidget *parent, Qt::WindowFlags flags)
    : QSplitter(parent)
    , m_treeViewer(0)
    , m_folderName(0)
    , m_itemViewer(0)
    , m_castItems(new CastItems()) {
  // style sheet
  setObjectName("CastBrowser");
  setFrameStyle(QFrame::StyledPanel);

  m_treeViewer = new CastTreeViewer(this);
  m_treeViewer->resize(300, m_treeViewer->size().height());

  QFrame *box = new QFrame(this);
  box->setFrameStyle(QFrame::StyledPanel);
  QVBoxLayout *boxLayout = new QVBoxLayout(box);
  boxLayout->setMargin(0);
  boxLayout->setSpacing(0);

  m_folderName = new QLabel("", box);
  m_folderName->setObjectName("CastBrowserFolderName");
  m_folderName->setFrameStyle(QFrame::Box);
  m_itemViewer = new DvItemViewer(box, false, true, DvItemViewer::Cast);
  DvItemViewerPanel *viewerPanel = m_itemViewer->getPanel();
  viewerPanel->setMissingTextColor(QColor(200, 0, 0));
  CastSelection *castSelection = new CastSelection();
  castSelection->setBrowser(this);
  viewerPanel->setSelection(castSelection);
  viewerPanel->addColumn(DvItemListModel::FrameCount, 50);
  m_itemViewer->setModel(this);

  DvItemViewerTitleBar *titleBar = new DvItemViewerTitleBar(m_itemViewer, box);
  // titleBar->hide();
  DvItemViewerButtonBar *buttonBar =
      new DvItemViewerButtonBar(m_itemViewer, box);

  boxLayout->addWidget(m_folderName);
  boxLayout->addWidget(titleBar);
  boxLayout->addWidget(m_itemViewer);
  boxLayout->addWidget(buttonBar);

  boxLayout->setAlignment(buttonBar, Qt::AlignBottom);
  box->setLayout(boxLayout);

  addWidget(m_treeViewer);
  addWidget(box);

  setStretchFactor(1, 2);

  TSceneHandle *sceneHandle = TApp::instance()->getCurrentScene();

  connect(sceneHandle, SIGNAL(sceneSwitched()), this, SLOT(refresh()));
  connect(sceneHandle, SIGNAL(castChanged()), this, SLOT(refresh()));

  TXsheetHandle *xhseetHandle = TApp::instance()->getCurrentXsheet();
  connect(xhseetHandle, SIGNAL(xsheetChanged()), m_itemViewer, SLOT(update()));

  connect(buttonBar, SIGNAL(folderUp()), m_treeViewer, SLOT(folderUp()));
  connect(buttonBar, SIGNAL(newFolder()), m_treeViewer, SLOT(newFolder()));

  connect(m_treeViewer,
          SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)),
          this, SLOT(folderChanged(QTreeWidgetItem *, QTreeWidgetItem *)));

  connect(m_treeViewer, SIGNAL(itemMovedToFolder()), this, SLOT(refresh()));
}

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

CastBrowser::~CastBrowser() {}

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

void CastBrowser::sortByDataModel(DataType dataType, bool isDiscendent) {
  if (dataType != getCurrentOrderType()) {
    // Sort by another dataType
    int i;
    for (i = 1; i < m_castItems->getItemCount(); i++) {
      int index = i;
      while (index > 0 && compareData(dataType, index - 1, index) > 0) {
        m_castItems->swapItem(index - 1, index);
        index = index - 1;
      }
    }
    setIsDiscendentOrder(true);
    setOrderType(dataType);
  }
  // Sort by order (invert current)
  if (isDiscendentOrder() != isDiscendent) {
    setIsDiscendentOrder(isDiscendent);
    std::vector<CastItem *> items;
    for (int i = 0; i < m_castItems->getItemCount(); i++)
      items.push_back(m_castItems->getItem(i)->clone());
    m_castItems->clear();
    for (int i = items.size() - 1; i >= 0; i--) m_castItems->addItem(items[i]);
  }

  m_itemViewer->getPanel()->update();
}

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

void CastBrowser::folderChanged(QTreeWidgetItem *current,
                                QTreeWidgetItem *previous) {
  refresh();
}

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

void CastBrowser::refresh() {
  if (isHidden()) return;
  refreshData();
  m_itemViewer->refresh();
}

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

/*
void CastBrowser::getLevels(std::vector<TXshLevel*> &levels) const
{
  TLevelSet *levelSet = CastTreeViewer::getLevelSet();
  TFilePath folder = m_treeViewer->getCurrentFolder();
  if(folder != TFilePath())
    levelSet->listLevels(levels, folder);
  else
    levelSet->listLevels(levels);
}
*/

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

void CastBrowser::refreshData() {
  m_castItems->clear();
  TLevelSet *levelSet = CastTreeViewer::getLevelSet();
  TFilePath folder    = m_treeViewer->getCurrentFolder();
  m_folderName->setText(toQString(folder));
  std::vector<TXshLevel *> levels;
  if (folder != TFilePath())
    levelSet->listLevels(levels, folder);
  else
    levelSet->listLevels(levels);

  int i;
  for (i = 0; i < (int)levels.size(); i++) {
    if (levels[i]->getSimpleLevel())
      m_castItems->addItem(new LevelCastItem(
          levels[i], m_itemViewer->getPanel()->getIconSize()));
    else if (levels[i]->getPaletteLevel())
      m_castItems->addItem(
          new PaletteCastItem(levels[i]->getPaletteLevel(),
                              m_itemViewer->getPanel()->getIconSize()));
    else if (levels[i]->getSoundLevel())
      m_castItems->addItem(new SoundCastItem(
          levels[i]->getSoundLevel(), m_itemViewer->getPanel()->getIconSize()));
  }
}

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

int CastBrowser::getItemCount() const { return m_castItems->getItemCount(); }

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

QVariant CastBrowser::getItemData(int index, DataType dataType,
                                  bool isSelected) {
  if (index < 0 || index >= getItemCount()) return QVariant();
  CastItem *item = m_castItems->getItem(index);
  if (dataType == Name)
    return item->getName();
  else if (dataType == Thumbnail)
    return item->getPixmap(isSelected);
  else if (dataType == ToolTip)
    return item->getToolTip();
  else if (dataType == FrameCount)
    return item->getFrameCount();
  else if (dataType == VersionControlStatus) {
    if (!item->exists())
      return VC_Missing;
    else
      return 0;  // No version control status displayed
  } else
    return QVariant();
}

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

/*
TXshSimpleLevel *CastBrowser::getSelectedSimpleLevel() const
{
  const std::set<int> &indices = m_itemViewer->getSelectedIndices();
  if(indices.empty()) return 0;

  int index = *indices.begin();

  std::vector<TXshLevel*> levels;
  getLevels(levels);

  if(index<0 || index>=(int)levels.size()) return 0;
  TXshLevel*level = levels[index];
  if(!level) return 0;
  else return level->getSimpleLevel();
}
*/

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

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

  const std::set<int> &indices = m_itemViewer->getSelectedIndices();
  if (indices.empty()) return;
  QDrag *drag = new QDrag(this);

  QVariant v = getItemData(*indices.begin(), Thumbnail);
  if (v != QVariant()) {
    QPixmap dropThumbnail = v.value<QPixmap>();
    if (!dropThumbnail.isNull()) drag->setPixmap(dropThumbnail);
  }
  drag->setMimeData(m_castItems->getSelectedItems(indices));
  Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
}

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

bool CastBrowser::acceptDrop(const QMimeData *data) const {
  return acceptResourceOrFolderDrop(data->urls());
}

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

bool CastBrowser::drop(const QMimeData *data) {
  if (!acceptDrop(data)) return false;

  if (data->hasUrls()) {
    IoCmd::LoadResourceArguments args;

    for (const QUrl &url : data->urls()) {
      TFilePath fp(url.toLocalFile().toStdWString());
      args.resourceDatas.push_back(fp);
    }

    args.castFolder = m_treeViewer->getCurrentFolder();

    IoCmd::loadResources(args);
  }

  refresh();
  return true;
}

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

QMenu *CastBrowser::getContextMenu(QWidget *parent, int index) {
  const std::set<int> &indices = m_itemViewer->getSelectedIndices();
  if (indices.empty()) {
    QMenu *menu = new QMenu(parent);
    menu->addAction(CommandManager::instance()->getAction(MI_RemoveUnused));
    return menu;
  }
  // controllo cosa c'e' nella selezione.
  // => audioSelected, vectorLevelSelected, paletteSelected
  std::set<int>::const_iterator it;
  bool audioSelected       = false;
  bool paletteSelected     = false;
  bool vectorLevelSelected = false;
  bool meshLevelSelected   = false;
  bool otherFileSelected   = false;
  int levelSelectedCount   = 0;
  for (it = indices.begin(); it != indices.end(); ++it) {
    int index = *it;
    if (index < 0 || index >= m_castItems->getItemCount())
      continue;  // should never happen
    TXshSimpleLevel *sl = m_castItems->getItem(index)->getSimpleLevel();
    if (!sl) {
      if (m_castItems->getItem(index)->getPaletteLevel())
        paletteSelected = true;
      else
        audioSelected = true;
      continue;
    }
    levelSelectedCount++;
    if (sl->getType() == PLI_XSHLEVEL)
      vectorLevelSelected = true;
    else if (sl->getType() == MESH_XSHLEVEL)
      meshLevelSelected = true;
    else
      otherFileSelected = true;
  }
  QMenu *menu        = new QMenu(parent);
  CommandManager *cm = CommandManager::instance();

  menu->addAction(cm->getAction(MI_LevelSettings));

  menu->addAction(cm->getAction(MI_ExposeResource));
  menu->addAction(cm->getAction(MI_ShowFolderContents));
  if (!audioSelected && !paletteSelected && !meshLevelSelected)
    menu->addAction(cm->getAction(MI_ViewFile));
  menu->addAction(cm->getAction(MI_FileInfo));

  // MI_EditLevel solo se e' stato selezionato un singolo diverso da livelli
  // palette a livelli audio
  if (indices.size() == 1 && !audioSelected && !paletteSelected)
    menu->addAction(cm->getAction(MI_EditLevel));
  if (!paletteSelected) menu->addAction(cm->getAction(MI_SaveLevel));
  menu->addSeparator();
  // MI_ConvertToVectors if only non-vector layers were selected
  if (!audioSelected && !paletteSelected && !vectorLevelSelected)
    menu->addAction(cm->getAction(MI_ConvertToVectors));
  menu->addSeparator();
  menu->addAction(cm->getAction(MI_RemoveLevel));
  menu->addAction(cm->getAction(MI_RemoveUnused));

  return menu;
}

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

void CastBrowser::expose() {
  std::set<int> selectedIndices =
      m_itemViewer->getPanel()->getSelectedIndices();
  TUndoManager::manager()->beginBlock();
  for (int index : selectedIndices) {
    CastItem *item         = m_castItems->getItem(index);
    TXshSimpleLevel *sl    = item->getSimpleLevel();
    TXshPaletteLevel *pl   = item->getPaletteLevel();
    TXshSoundLevel *soundl = item->getSoundLevel();
    if (sl)
      FilmstripCmd::moveToScene(sl);
    else if (pl)
      FilmstripCmd::moveToScene(pl);
    else if (soundl)
      FilmstripCmd::moveToScene(soundl);
  }
  TUndoManager::manager()->endBlock();
}

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

void CastBrowser::edit() {
  CastSelection *castSelection =
      dynamic_cast<CastSelection *>(TSelection::getCurrent());
  if (!castSelection) return;
  std::vector<TXshLevel *> levels;
  castSelection->getSelectedLevels(levels);
  if ((int)levels.size() == 1) {
    TXshLevel *l = levels[0];
    if (l) {
      TXshSimpleLevel *sl = l->getSimpleLevel();
      if (sl) {
        TApp::instance()->getCurrentLevel()->setLevel(sl);
        return;
      }
    }
    error(tr("It is not possible to edit the selected file."));
  } else
    error(tr("It is not possible to edit more than one file at once."));
}

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

void CastBrowser::showFolderContents() {
  int i = 0;
  CastSelection *castSelection =
      dynamic_cast<CastSelection *>(TSelection::getCurrent());
  if (!castSelection) return;
  std::vector<TXshLevel *> levels;
  castSelection->getSelectedLevels(levels);
  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  TFilePath folderPath, filePath;
  for (i = 0; i < levels.size(); i++) {
    folderPath = levels[i]->getPath().getParentDir();
    filePath   = levels[i]->getPath();

    folderPath = scene->decodeFilePath(folderPath);
    filePath   = scene->decodeFilePath(filePath);

    if (!TSystem::doesExistFileOrLevel(filePath)) {
      error(
          tr("It is not possible to show the folder containing the selected "
             "file, as the file has not been saved yet."));
    } else {
      if (TSystem::isUNC(folderPath))
        QDesktopServices::openUrl(
            QUrl(QString::fromStdWString(folderPath.getWideString())));
      else
        QDesktopServices::openUrl(QUrl::fromLocalFile(
            QString::fromStdWString(folderPath.getWideString())));
    }
  }
}
//-----------------------------------------------------------------------------

void CastBrowser::viewFile() {
  CastSelection *castSelection =
      dynamic_cast<CastSelection *>(TSelection::getCurrent());
  if (!castSelection) return;
  std::vector<TXshLevel *> levels;
  castSelection->getSelectedLevels(levels);
  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  TFilePath filePath;
  if (levels.empty()) return;
  int i = 0;
  for (i = 0; i < levels.size(); i++) {
    filePath = levels[i]->getPath();

    filePath = scene->decodeFilePath(filePath);
    if (!TSystem::doesExistFileOrLevel(filePath)) {
      error(
          tr("It is not possible to view the selected file, as the file has "
             "not been saved yet."));
    } else {
      if (!TFileType::isViewable(TFileType::getInfo(filePath))) return;

      if (Preferences::instance()->isDefaultViewerEnabled() &&
          (filePath.getType() == "mov" || filePath.getType() == "avi" ||
           filePath.getType() == "3gp"))
        QDesktopServices::openUrl(QUrl("file:///" + toQString(filePath)));
      else
        ::viewFile(filePath);
    }
  }
}

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

void CastBrowser::viewFileInfo() {
  CastSelection *castSelection =
      dynamic_cast<CastSelection *>(TSelection::getCurrent());
  if (!castSelection) return;
  std::vector<TXshLevel *> levels;
  castSelection->getSelectedLevels(levels);
  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  int i             = 0;

  if (levels.empty()) {
    return;
  } else {
    for (i = 0; i < levels.size(); i++) {
      TFilePath filePath;
      filePath = levels[i]->getPath();

      filePath = scene->decodeFilePath(filePath);
      if (!TSystem::doesExistFileOrLevel(filePath)) {
        error(
            tr("It is not possible to show the info of the selected file, as "
               "the file has not been saved yet."));
      } else {
        InfoViewer *infoViewer = 0;
        infoViewer             = new InfoViewer(this);
        infoViewer->setItem(0, 0, filePath);
      }
    }
  }
}
//=============================================================================

OpenFloatingPanel openCastPane(MI_OpenFileBrowser2, "SceneCast",
                               QObject::tr("Scene Cast"));