Blob Blame Raw
#include "menubarpopup.h"

// Tnz includes
#include "tapp.h"
#include "mainwindow.h"
#include "menubar.h"
#include "shortcutpopup.h"
#include "commandbarpopup.h"

// TnzQt includes
#include "toonzqt/gutil.h"

// TnzLib includes
#include "toonz/toonzfolders.h"

// TnzCore includes
#include "tsystem.h"

// Qt includes
#include <QMainWindow>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QHeaderView>
#include <QtDebug>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QDataStream>
#include <QMimeData>
#include <QDrag>
#include <QMouseEvent>
#include <QPainter>
#include <QApplication>
#include <QLabel>

//=============================================================================
// MenuBarSubmenuItem
//-----------------------------------------------------------------------------

class MenuBarSubmenuItem final : public QTreeWidgetItem {
  /*- title before translation -*/
  QString m_orgTitle;

public:
  MenuBarSubmenuItem(QTreeWidgetItem* parent, QString& title)
      : QTreeWidgetItem(parent, UserType), m_orgTitle(title) {
    setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled |
             Qt::ItemIsDropEnabled | Qt::ItemIsEnabled);
    /*- Menu title will be translated if the title is registered in translation
     * file -*/
    setText(0, StackedMenuBar::tr(title.toStdString().c_str()));
    QIcon subMenuIcon(createQIcon("folder", true));
    setIcon(0, subMenuIcon);
    setToolTip(0, QObject::tr(
                      "[Drag] to move position, [Double Click] to edit title"));
  }

  QString getOrgTitle() const { return m_orgTitle; }
  void setOrgTitle(const QString title) { m_orgTitle = title; }
};

//=============================================================================
// MenuBarTree
//-----------------------------------------------------------------------------

MenuBarTree::MenuBarTree(TFilePath& path, QWidget* parent)
    : QTreeWidget(parent), m_path(path) {
  setObjectName("SolidLineFrame");
  setAlternatingRowColors(true);
  setDragEnabled(true);
  setDropIndicatorShown(true);
  setDefaultDropAction(Qt::MoveAction);
  setDragDropMode(QAbstractItemView::DragDrop);
  setIconSize(QSize(21, 18));

  setColumnCount(1);
  header()->close();

  /*- Load m_path if it does exist. If not, then load from the template. -*/
  TFilePath fp;
  if (TFileStatus(path).isWritable())
    fp = m_path;
  else {
    fp = m_path.withParentDir(ToonzFolder::getTemplateRoomsDir());
    if (!TFileStatus(path).isReadable())
      fp = ToonzFolder::getTemplateRoomsDir() + "menubar_template.xml";
  }

  loadMenuTree(fp);

  bool ret = connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this,
                     SLOT(onItemChanged(QTreeWidgetItem*, int)));
  assert(ret);
}

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

void MenuBarTree::loadMenuTree(const TFilePath& fp) {
  QFile file(toQString(fp));
  if (!file.open(QFile::ReadOnly | QFile::Text)) {
    qDebug() << "Cannot read file" << file.errorString();
    return;
  }

  QXmlStreamReader reader(&file);

  if (reader.readNextStartElement()) {
    if (reader.name() == "menubar") {
      while (reader.readNextStartElement()) {
        if (reader.name() == "menu") {
          QString title = reader.attributes().value("title").toString();
          MenuBarSubmenuItem* menu = new MenuBarSubmenuItem(0, title);
          addTopLevelItem(menu);
          loadMenuRecursive(reader, menu);
        } else if (reader.name() == "command") {
          QString cmdName = reader.readElementText();

          QAction* action = CommandManager::instance()->getAction(
              cmdName.toStdString().c_str());
          if (action) {
            CommandItem* item = new CommandItem(0, action);
            addTopLevelItem(item);
          }
        } else
          reader.skipCurrentElement();
      }
    } else
      reader.raiseError(QObject::tr("Incorrect file"));
  }

  if (reader.hasError()) {
    qDebug() << "Cannot read menubar xml";
  }
}

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

void MenuBarTree::loadMenuRecursive(QXmlStreamReader& reader,
                                    QTreeWidgetItem* parentItem) {
  while (reader.readNextStartElement()) {
    if (reader.name() == "menu") {
      QString title = reader.attributes().value("title").toString();
      MenuBarSubmenuItem* subMenu = new MenuBarSubmenuItem(parentItem, title);
      loadMenuRecursive(reader, subMenu);
    } else if (reader.name() == "command") {
      QString cmdName = reader.readElementText();
      QAction* action =
          CommandManager::instance()->getAction(cmdName.toStdString().c_str());
      if (action) CommandItem* item = new CommandItem(parentItem, action);
    } else if (reader.name() == "command_debug") {
#ifndef NDEBUG
      QString cmdName = reader.readElementText();
      QAction* action =
          CommandManager::instance()->getAction(cmdName.toStdString().c_str());
      if (action) CommandItem* item = new CommandItem(parentItem, action);
#else
      reader.skipCurrentElement();
#endif
    } else if (reader.name() == "separator") {
      SeparatorItem* sep = new SeparatorItem(parentItem);
      reader.skipCurrentElement();
    } else
      reader.skipCurrentElement();
  }
}

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

void MenuBarTree::saveMenuTree() {
  QFile file(toQString(m_path));
  if (!file.open(QFile::WriteOnly | QFile::Text)) {
    qDebug() << "Cannot read file" << file.errorString();
    return;
  }

  QXmlStreamWriter writer(&file);
  writer.setAutoFormatting(true);
  writer.writeStartDocument();

  writer.writeStartElement("menubar");
  { saveMenuRecursive(writer, invisibleRootItem()); }
  writer.writeEndElement();  // menubar

  writer.writeEndDocument();
}

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

void MenuBarTree::saveMenuRecursive(QXmlStreamWriter& writer,
                                    QTreeWidgetItem* parentItem) {
  for (int c = 0; c < parentItem->childCount(); c++) {
    CommandItem* command = dynamic_cast<CommandItem*>(parentItem->child(c));
    SeparatorItem* sep   = dynamic_cast<SeparatorItem*>(parentItem->child(c));
    MenuBarSubmenuItem* subMenu =
        dynamic_cast<MenuBarSubmenuItem*>(parentItem->child(c));
    if (command)
      writer.writeTextElement(
          "command",
          QString::fromStdString(CommandManager::instance()->getIdFromAction(
              command->getAction())));
    else if (sep)
      writer.writeEmptyElement("separator");
    else if (subMenu) {
      writer.writeStartElement("menu");
      // save original title instead of translated one
      writer.writeAttribute("title", subMenu->getOrgTitle());

      saveMenuRecursive(writer, subMenu);

      writer.writeEndElement();  // menu
    } else {
    }
  }
}

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

bool MenuBarTree::dropMimeData(QTreeWidgetItem* parent, int index,
                               const QMimeData* data, Qt::DropAction action) {
  if (data->hasText()) {
    QString txt = data->text();
    QTreeWidgetItem* item;
    if (txt == "separator")
      item = new SeparatorItem(0);
    else {
      QAction* act =
          CommandManager::instance()->getAction(txt.toStdString().c_str());
      if (!act) return false;
      item = new CommandItem(0, act);
    }

    if (parent)
      parent->insertChild(index, item);
    else
      insertTopLevelItem(index, item);

    return true;
  }

  return false;
}

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

QStringList MenuBarTree::mimeTypes() const {
  QStringList qstrList;
  qstrList.append("text/plain");
  return qstrList;
}

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

void MenuBarTree::contextMenuEvent(QContextMenuEvent* event) {
  QTreeWidgetItem* item = itemAt(event->pos());
  if (item != currentItem()) setCurrentItem(item);
  QMenu* menu = new QMenu(this);
  QAction* action;
  if (!item || indexOfTopLevelItem(item) >= 0)
    action = menu->addAction(tr("Insert Menu"));
  else
    action = menu->addAction(tr("Insert Submenu"));

  connect(action, SIGNAL(triggered()), this, SLOT(insertMenu()));

  if (item) {
    action = menu->addAction(tr("Remove \"%1\"").arg(item->text(0)));
    connect(action, SIGNAL(triggered()), this, SLOT(removeItem()));
  }

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

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

void MenuBarTree::insertMenu() {
  QTreeWidgetItem* item       = currentItem();
  QString title               = tr("New Menu");
  MenuBarSubmenuItem* insItem = new MenuBarSubmenuItem(0, title);
  if (!item)
    addTopLevelItem(insItem);
  else if (indexOfTopLevelItem(item) >= 0)
    insertTopLevelItem(indexOfTopLevelItem(item), insItem);
  else
    item->parent()->insertChild(item->parent()->indexOfChild(item), insItem);
}

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

void MenuBarTree::removeItem() {
  QTreeWidgetItem* item = currentItem();
  if (!item) return;

  if (indexOfTopLevelItem(item) >= 0)
    takeTopLevelItem(indexOfTopLevelItem(item));
  else
    item->parent()->removeChild(item);

  delete item;
}

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

void MenuBarTree::onItemChanged(QTreeWidgetItem* item, int column) {
  MenuBarSubmenuItem* submenuItem = dynamic_cast<MenuBarSubmenuItem*>(item);
  if (!submenuItem) return;
  submenuItem->setOrgTitle(submenuItem->text(0));
}

//=============================================================================
// MenuBarPopup
//-----------------------------------------------------------------------------

MenuBarPopup::MenuBarPopup(Room* room)
    : Dialog(TApp::instance()->getMainWindow(), true, false,
             "CustomizeMenuBar") {
  setWindowTitle(tr("Customize Menu Bar of Room \"%1\"").arg(room->getName()));

  /*- get menubar setting file path -*/
  std::string mbFileName = room->getPath().getName() + "_menubar.xml";
  TFilePath mbPath       = ToonzFolder::getMyRoomsDir() + mbFileName;

  QLabel* menuBarLabel =
      new QLabel(tr("%1 Menu Bar").arg(room->getName()), this);
  QLabel* menuItemListLabel = new QLabel(tr("Menu Items"), this);

  m_commandListTree = new CommandListTree(menuBarLabel->text(), this);
  m_menuBarTree     = new MenuBarTree(mbPath, this);

  QPushButton* okBtn     = new QPushButton(tr("OK"), this);
  QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this);

  okBtn->setFocusPolicy(Qt::NoFocus);
  cancelBtn->setFocusPolicy(Qt::NoFocus);

  QFont f("Arial", 15, QFont::Bold);
  menuBarLabel->setFont(f);
  menuItemListLabel->setFont(f);

  QLineEdit* searchEdit = new QLineEdit(this);

  QLabel* noticeLabel = new QLabel(
      tr("N.B. If you put unique title to submenu, it may not be translated to "
         "another language.\nN.B. Duplicated commands will be ignored. Only "
         "the last one will appear in the menu bar."),
      this);
  QFont nf("Arial", 9, QFont::Normal);
  nf.setItalic(true);
  noticeLabel->setFont(nf);

  //--- layout
  QVBoxLayout* mainLay = new QVBoxLayout();
  m_topLayout->setMargin(0);
  m_topLayout->setSpacing(0);
  {
    QGridLayout* mainUILay = new QGridLayout();
    mainUILay->setMargin(5);
    mainUILay->setHorizontalSpacing(8);
    mainUILay->setVerticalSpacing(5);
    {
      mainUILay->addWidget(menuBarLabel, 0, 0);
      mainUILay->addWidget(menuItemListLabel, 0, 1);
      mainUILay->addWidget(m_menuBarTree, 1, 0, 2, 1);

      QHBoxLayout* searchLay = new QHBoxLayout();
      searchLay->setMargin(0);
      searchLay->setSpacing(5);
      {
        searchLay->addWidget(new QLabel(tr("Search:"), this), 0);
        searchLay->addWidget(searchEdit);
      }
      mainUILay->addLayout(searchLay, 1, 1);
      mainUILay->addWidget(m_commandListTree, 2, 1);

      mainUILay->addWidget(noticeLabel, 3, 0, 1, 2);
    }
    mainUILay->setRowStretch(0, 0);
    mainUILay->setRowStretch(1, 1);
    mainUILay->setRowStretch(2, 0);
    mainUILay->setColumnStretch(0, 1);
    mainUILay->setColumnStretch(1, 1);

    m_topLayout->addLayout(mainUILay, 1);
  }

  m_buttonLayout->setMargin(0);
  m_buttonLayout->setSpacing(30);
  {
    m_buttonLayout->addStretch(1);
    m_buttonLayout->addWidget(okBtn, 0);
    m_buttonLayout->addWidget(cancelBtn, 0);
    m_buttonLayout->addStretch(1);
  }

  //--- signal/slot connections

  bool ret = connect(okBtn, SIGNAL(clicked()), this, SLOT(onOkPressed()));
  ret      = ret && connect(cancelBtn, SIGNAL(clicked()), this, SLOT(reject()));
  ret = ret && connect(searchEdit, SIGNAL(textChanged(const QString&)), this,
                       SLOT(onSearchTextChanged(const QString&)));
  assert(ret);
}

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

void MenuBarPopup::onOkPressed() {
  m_menuBarTree->saveMenuTree();

  accept();
}

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

void MenuBarPopup::onSearchTextChanged(const QString& text) {
  static bool busy = false;
  if (busy) return;
  busy = true;
  m_commandListTree->searchItems(text);
  busy = false;
}