1 #include "menubarpopup.h"
2 
3 // Tnz includes
4 #include "tapp.h"
5 #include "mainwindow.h"
6 #include "menubar.h"
7 #include "shortcutpopup.h"
8 
9 // TnzQt includes
10 #include "toonzqt/gutil.h"
11 
12 // TnzLib includes
13 #include "toonz/toonzfolders.h"
14 
15 // TnzCore includes
16 #include "tsystem.h"
17 
18 // Qt includes
19 #include <QMainWindow>
20 #include <QPushButton>
21 #include <QVBoxLayout>
22 #include <QHBoxLayout>
23 #include <QGridLayout>
24 #include <QHeaderView>
25 #include <QtDebug>
26 #include <QXmlStreamReader>
27 #include <QXmlStreamWriter>
28 #include <QDataStream>
29 #include <QMimeData>
30 #include <QDrag>
31 #include <QMouseEvent>
32 #include <QPainter>
33 #include <QApplication>
34 #include <QLabel>
35 
36 //=============================================================================
37 // MenuBarCommandItem
38 //-----------------------------------------------------------------------------
39 
40 class MenuBarCommandItem final : public QTreeWidgetItem {
41   QAction* m_action;
42 
43 public:
MenuBarCommandItem(QTreeWidgetItem * parent,QAction * action)44   MenuBarCommandItem(QTreeWidgetItem* parent, QAction* action)
45       : QTreeWidgetItem(parent, UserType), m_action(action) {
46     setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled |
47              Qt::ItemNeverHasChildren);
48     setText(0, m_action->text().remove("&"));
49     setToolTip(0, QObject::tr("[Drag] to move position"));
50   }
getAction() const51   QAction* getAction() const { return m_action; }
52 };
53 
54 //=============================================================================
55 // MenuBarSeparatorItem
56 //-----------------------------------------------------------------------------
57 
58 class MenuBarSeparatorItem final : public QTreeWidgetItem {
59 public:
MenuBarSeparatorItem(QTreeWidgetItem * parent)60   MenuBarSeparatorItem(QTreeWidgetItem* parent)
61       : QTreeWidgetItem(parent, UserType) {
62     setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled |
63              Qt::ItemNeverHasChildren);
64     setText(0, QObject::tr("----Separator----"));
65     setToolTip(0, QObject::tr("[Drag] to move position"));
66   }
67 };
68 
69 //=============================================================================
70 // MenuBarSubmenuItem
71 //-----------------------------------------------------------------------------
72 
73 class MenuBarSubmenuItem final : public QTreeWidgetItem {
74   /*- title before translation -*/
75   QString m_orgTitle;
76 
77 public:
MenuBarSubmenuItem(QTreeWidgetItem * parent,QString & title)78   MenuBarSubmenuItem(QTreeWidgetItem* parent, QString& title)
79       : QTreeWidgetItem(parent, UserType), m_orgTitle(title) {
80     setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled |
81              Qt::ItemIsDropEnabled | Qt::ItemIsEnabled);
82     /*- Menu title will be translated if the title is registered in translation
83      * file -*/
84     setText(0, StackedMenuBar::tr(title.toStdString().c_str()));
85     QIcon subMenuIcon(createQIcon("folder", true));
86     setIcon(0, subMenuIcon);
87     setToolTip(0, QObject::tr(
88                       "[Drag] to move position, [Double Click] to edit title"));
89   }
90 
getOrgTitle() const91   QString getOrgTitle() const { return m_orgTitle; }
setOrgTitle(const QString title)92   void setOrgTitle(const QString title) { m_orgTitle = title; }
93 };
94 
95 //=============================================================================
96 // MenuBarTree
97 //-----------------------------------------------------------------------------
98 
MenuBarTree(TFilePath & path,QWidget * parent)99 MenuBarTree::MenuBarTree(TFilePath& path, QWidget* parent)
100     : QTreeWidget(parent), m_path(path) {
101   setObjectName("SolidLineFrame");
102   setAlternatingRowColors(true);
103   setDragEnabled(true);
104   setDropIndicatorShown(true);
105   setDefaultDropAction(Qt::MoveAction);
106   setDragDropMode(QAbstractItemView::DragDrop);
107   setIconSize(QSize(21, 18));
108 
109   setColumnCount(1);
110   header()->close();
111 
112   /*- Load m_path if it does exist. If not, then load from the template. -*/
113   TFilePath fp;
114   if (TFileStatus(path).isWritable())
115     fp = m_path;
116   else {
117     fp = m_path.withParentDir(ToonzFolder::getTemplateRoomsDir());
118     if (!TFileStatus(path).isReadable())
119       fp = ToonzFolder::getTemplateRoomsDir() + "menubar_template.xml";
120   }
121 
122   loadMenuTree(fp);
123 
124   bool ret = connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this,
125                      SLOT(onItemChanged(QTreeWidgetItem*, int)));
126   assert(ret);
127 }
128 
129 //-----------------------------------------------------------------------------
130 
loadMenuTree(const TFilePath & fp)131 void MenuBarTree::loadMenuTree(const TFilePath& fp) {
132   QFile file(toQString(fp));
133   if (!file.open(QFile::ReadOnly | QFile::Text)) {
134     qDebug() << "Cannot read file" << file.errorString();
135     return;
136   }
137 
138   QXmlStreamReader reader(&file);
139 
140   if (reader.readNextStartElement()) {
141     if (reader.name() == "menubar") {
142       while (reader.readNextStartElement()) {
143         if (reader.name() == "menu") {
144           QString title = reader.attributes().value("title").toString();
145           MenuBarSubmenuItem* menu = new MenuBarSubmenuItem(0, title);
146           addTopLevelItem(menu);
147           loadMenuRecursive(reader, menu);
148         } else if (reader.name() == "command") {
149           QString cmdName = reader.readElementText();
150 
151           QAction* action = CommandManager::instance()->getAction(
152               cmdName.toStdString().c_str());
153           if (action) {
154             MenuBarCommandItem* item = new MenuBarCommandItem(0, action);
155             addTopLevelItem(item);
156           }
157         } else
158           reader.skipCurrentElement();
159       }
160     } else
161       reader.raiseError(QObject::tr("Incorrect file"));
162   }
163 
164   if (reader.hasError()) {
165     qDebug() << "Cannot read menubar xml";
166   }
167 }
168 
169 //-----------------------------------------------------------------------------
170 
loadMenuRecursive(QXmlStreamReader & reader,QTreeWidgetItem * parentItem)171 void MenuBarTree::loadMenuRecursive(QXmlStreamReader& reader,
172                                     QTreeWidgetItem* parentItem) {
173   while (reader.readNextStartElement()) {
174     if (reader.name() == "menu") {
175       QString title = reader.attributes().value("title").toString();
176       MenuBarSubmenuItem* subMenu = new MenuBarSubmenuItem(parentItem, title);
177       loadMenuRecursive(reader, subMenu);
178     } else if (reader.name() == "command") {
179       QString cmdName = reader.readElementText();
180       QAction* action =
181           CommandManager::instance()->getAction(cmdName.toStdString().c_str());
182       if (action)
183         MenuBarCommandItem* item = new MenuBarCommandItem(parentItem, action);
184     } else if (reader.name() == "command_debug") {
185 #ifndef NDEBUG
186       QString cmdName = reader.readElementText();
187       QAction* action =
188           CommandManager::instance()->getAction(cmdName.toStdString().c_str());
189       if (action)
190         MenuBarCommandItem* item = new MenuBarCommandItem(parentItem, action);
191 #else
192       reader.skipCurrentElement();
193 #endif
194     } else if (reader.name() == "separator") {
195       MenuBarSeparatorItem* sep = new MenuBarSeparatorItem(parentItem);
196       reader.skipCurrentElement();
197     } else
198       reader.skipCurrentElement();
199   }
200 }
201 
202 //-----------------------------------------------------------------------------
203 
saveMenuTree()204 void MenuBarTree::saveMenuTree() {
205   QFile file(toQString(m_path));
206   if (!file.open(QFile::WriteOnly | QFile::Text)) {
207     qDebug() << "Cannot read file" << file.errorString();
208     return;
209   }
210 
211   QXmlStreamWriter writer(&file);
212   writer.setAutoFormatting(true);
213   writer.writeStartDocument();
214 
215   writer.writeStartElement("menubar");
216   { saveMenuRecursive(writer, invisibleRootItem()); }
217   writer.writeEndElement();  // menubar
218 
219   writer.writeEndDocument();
220 }
221 
222 //-----------------------------------------------------------------------------
223 
saveMenuRecursive(QXmlStreamWriter & writer,QTreeWidgetItem * parentItem)224 void MenuBarTree::saveMenuRecursive(QXmlStreamWriter& writer,
225                                     QTreeWidgetItem* parentItem) {
226   for (int c = 0; c < parentItem->childCount(); c++) {
227     MenuBarCommandItem* command =
228         dynamic_cast<MenuBarCommandItem*>(parentItem->child(c));
229     MenuBarSeparatorItem* sep =
230         dynamic_cast<MenuBarSeparatorItem*>(parentItem->child(c));
231     MenuBarSubmenuItem* subMenu =
232         dynamic_cast<MenuBarSubmenuItem*>(parentItem->child(c));
233     if (command)
234       writer.writeTextElement(
235           "command",
236           QString::fromStdString(CommandManager::instance()->getIdFromAction(
237               command->getAction())));
238     else if (sep)
239       writer.writeEmptyElement("separator");
240     else if (subMenu) {
241       writer.writeStartElement("menu");
242       // save original title instead of translated one
243       writer.writeAttribute("title", subMenu->getOrgTitle());
244 
245       saveMenuRecursive(writer, subMenu);
246 
247       writer.writeEndElement();  // menu
248     } else {
249     }
250   }
251 }
252 
253 //-----------------------------------------------------------------------------
254 
dropMimeData(QTreeWidgetItem * parent,int index,const QMimeData * data,Qt::DropAction action)255 bool MenuBarTree::dropMimeData(QTreeWidgetItem* parent, int index,
256                                const QMimeData* data, Qt::DropAction action) {
257   if (data->hasText()) {
258     QString txt = data->text();
259     QTreeWidgetItem* item;
260     if (txt == "separator")
261       item = new MenuBarSeparatorItem(0);
262     else {
263       QAction* act =
264           CommandManager::instance()->getAction(txt.toStdString().c_str());
265       if (!act) return false;
266       item = new MenuBarCommandItem(0, act);
267     }
268 
269     if (parent)
270       parent->insertChild(index, item);
271     else
272       insertTopLevelItem(index, item);
273 
274     return true;
275   }
276 
277   return false;
278 }
279 
280 //-----------------------------------------------------------------------------
281 
mimeTypes() const282 QStringList MenuBarTree::mimeTypes() const {
283   QStringList qstrList;
284   qstrList.append("text/plain");
285   return qstrList;
286 }
287 
288 //-----------------------------------------------------------------------------
289 
contextMenuEvent(QContextMenuEvent * event)290 void MenuBarTree::contextMenuEvent(QContextMenuEvent* event) {
291   QTreeWidgetItem* item = itemAt(event->pos());
292   if (item != currentItem()) setCurrentItem(item);
293   QMenu* menu = new QMenu(this);
294   QAction* action;
295   if (!item || indexOfTopLevelItem(item) >= 0)
296     action = menu->addAction(tr("Insert Menu"));
297   else
298     action = menu->addAction(tr("Insert Submenu"));
299 
300   connect(action, SIGNAL(triggered()), this, SLOT(insertMenu()));
301 
302   if (item) {
303     action = menu->addAction(tr("Remove \"%1\"").arg(item->text(0)));
304     connect(action, SIGNAL(triggered()), this, SLOT(removeItem()));
305   }
306 
307   menu->exec(event->globalPos());
308   delete menu;
309 }
310 
311 //-----------------------------------------------------------------------------
312 
insertMenu()313 void MenuBarTree::insertMenu() {
314   QTreeWidgetItem* item       = currentItem();
315   QString title               = tr("New Menu");
316   MenuBarSubmenuItem* insItem = new MenuBarSubmenuItem(0, title);
317   if (!item)
318     addTopLevelItem(insItem);
319   else if (indexOfTopLevelItem(item) >= 0)
320     insertTopLevelItem(indexOfTopLevelItem(item), insItem);
321   else
322     item->parent()->insertChild(item->parent()->indexOfChild(item), insItem);
323 }
324 
325 //-----------------------------------------------------------------------------
326 
removeItem()327 void MenuBarTree::removeItem() {
328   QTreeWidgetItem* item = currentItem();
329   if (!item) return;
330 
331   if (indexOfTopLevelItem(item) >= 0)
332     takeTopLevelItem(indexOfTopLevelItem(item));
333   else
334     item->parent()->removeChild(item);
335 
336   delete item;
337 }
338 
339 //-----------------------------------------------------------------------------
340 
onItemChanged(QTreeWidgetItem * item,int column)341 void MenuBarTree::onItemChanged(QTreeWidgetItem* item, int column) {
342   MenuBarSubmenuItem* submenuItem = dynamic_cast<MenuBarSubmenuItem*>(item);
343   if (!submenuItem) return;
344   submenuItem->setOrgTitle(submenuItem->text(0));
345 }
346 
347 //=============================================================================
348 // CommandListTree
349 //-----------------------------------------------------------------------------
350 
CommandListTree(QWidget * parent)351 CommandListTree::CommandListTree(QWidget* parent) : QTreeWidget(parent) {
352   setObjectName("SolidLineFrame");
353   setAlternatingRowColors(true);
354   setDragEnabled(true);
355   setDragDropMode(QAbstractItemView::DragOnly);
356   setColumnCount(1);
357   setIconSize(QSize(21, 18));
358   header()->close();
359 
360   QIcon menuFolderIcon(createQIcon("folder_project", true));
361   invisibleRootItem()->setIcon(0, menuFolderIcon);
362 
363   QTreeWidgetItem* menuCommandFolder = new QTreeWidgetItem(this);
364   menuCommandFolder->setFlags(Qt::ItemIsEnabled);
365   menuCommandFolder->setText(0, ShortcutTree::tr("Menu Commands"));
366   menuCommandFolder->setExpanded(true);
367   menuCommandFolder->setIcon(0, invisibleRootItem()->icon(0));
368 
369   addFolder(ShortcutTree::tr("File"), MenuFileCommandType, menuCommandFolder);
370   addFolder(ShortcutTree::tr("Edit"), MenuEditCommandType, menuCommandFolder);
371   addFolder(ShortcutTree::tr("Scan & Cleanup"), MenuScanCleanupCommandType,
372             menuCommandFolder);
373   addFolder(ShortcutTree::tr("Level"), MenuLevelCommandType, menuCommandFolder);
374   addFolder(ShortcutTree::tr("Xsheet"), MenuXsheetCommandType,
375             menuCommandFolder);
376   addFolder(ShortcutTree::tr("Cells"), MenuCellsCommandType, menuCommandFolder);
377   addFolder(ShortcutTree::tr("Play"), MenuPlayCommandType, menuCommandFolder);
378   addFolder(ShortcutTree::tr("Render"), MenuRenderCommandType,
379             menuCommandFolder);
380   addFolder(ShortcutTree::tr("View"), MenuViewCommandType, menuCommandFolder);
381   addFolder(ShortcutTree::tr("Windows"), MenuWindowsCommandType,
382             menuCommandFolder);
383   addFolder(ShortcutTree::tr("Help"), MenuHelpCommandType, menuCommandFolder);
384 
385   addFolder(ShortcutTree::tr("Tools"), ToolCommandType);
386 
387   sortItems(0, Qt::AscendingOrder);
388 
389   MenuBarSeparatorItem* sep = new MenuBarSeparatorItem(0);
390   sep->setToolTip(0, QObject::tr("[Drag&Drop] to copy separator to menu bar"));
391   addTopLevelItem(sep);
392 }
393 
394 //-----------------------------------------------------------------------------
395 
addFolder(const QString & title,int commandType,QTreeWidgetItem * parentFolder)396 void CommandListTree::addFolder(const QString& title, int commandType,
397                                 QTreeWidgetItem* parentFolder) {
398   QTreeWidgetItem* folder;
399   if (!parentFolder)
400     folder = new QTreeWidgetItem(this);
401   else
402     folder = new QTreeWidgetItem(parentFolder);
403   assert(folder);
404   folder->setText(0, title);
405   folder->setIcon(0, invisibleRootItem()->icon(0));
406 
407   std::vector<QAction*> actions;
408   CommandManager::instance()->getActions((CommandType)commandType, actions);
409   for (int i = 0; i < (int)actions.size(); i++) {
410     MenuBarCommandItem* item = new MenuBarCommandItem(folder, actions[i]);
411     item->setToolTip(0, QObject::tr("[Drag&Drop] to copy command to menu bar"));
412   }
413 }
414 
415 //-----------------------------------------------------------------------------
416 
mousePressEvent(QMouseEvent * event)417 void CommandListTree::mousePressEvent(QMouseEvent* event) {
418   setCurrentItem(itemAt(event->pos()));
419   MenuBarCommandItem* commandItem =
420       dynamic_cast<MenuBarCommandItem*>(itemAt(event->pos()));
421   MenuBarSeparatorItem* separatorItem =
422       dynamic_cast<MenuBarSeparatorItem*>(itemAt(event->pos()));
423 
424   if (commandItem || separatorItem) {
425     std::string dragStr;
426     QString dragPixmapTxt;
427     if (commandItem) {
428       dragStr =
429           CommandManager::instance()->getIdFromAction(commandItem->getAction());
430       dragPixmapTxt = commandItem->getAction()->text();
431       dragPixmapTxt.remove("&");
432     } else {
433       dragStr       = "separator";
434       dragPixmapTxt = tr("----Separator----");
435     }
436 
437     QMimeData* mimeData = new QMimeData;
438     mimeData->setText(QString::fromStdString(dragStr));
439 
440     QFontMetrics fm(QApplication::font());
441     QPixmap pix(fm.boundingRect(dragPixmapTxt).adjusted(-2, -2, 2, 2).size());
442     QPainter painter(&pix);
443     painter.fillRect(pix.rect(), Qt::white);
444     painter.setPen(Qt::black);
445     painter.drawText(pix.rect(), Qt::AlignCenter, dragPixmapTxt);
446 
447     QDrag* drag = new QDrag(this);
448     drag->setMimeData(mimeData);
449     drag->setPixmap(pix);
450 
451     drag->exec(Qt::CopyAction);
452   }
453 
454   QTreeWidget::mousePressEvent(event);
455 }
456 
457 //=============================================================================
458 // MenuBarPopup
459 //-----------------------------------------------------------------------------
460 
MenuBarPopup(Room * room)461 MenuBarPopup::MenuBarPopup(Room* room)
462     : Dialog(TApp::instance()->getMainWindow(), true, false,
463              "CustomizeMenuBar") {
464   setWindowTitle(tr("Customize Menu Bar of Room \"%1\"").arg(room->getName()));
465 
466   /*- get menubar setting file path -*/
467   std::string mbFileName = room->getPath().getName() + "_menubar.xml";
468   TFilePath mbPath       = ToonzFolder::getMyRoomsDir() + mbFileName;
469 
470   m_commandListTree = new CommandListTree(this);
471   m_menuBarTree     = new MenuBarTree(mbPath, this);
472 
473   QPushButton* okBtn     = new QPushButton(tr("OK"), this);
474   QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this);
475 
476   okBtn->setFocusPolicy(Qt::NoFocus);
477   cancelBtn->setFocusPolicy(Qt::NoFocus);
478 
479   QLabel* menuBarLabel =
480       new QLabel(tr("%1 Menu Bar").arg(room->getName()), this);
481   QLabel* menuItemListLabel = new QLabel(tr("Menu Items"), this);
482 
483   QFont f("Arial", 15, QFont::Bold);
484   menuBarLabel->setFont(f);
485   menuItemListLabel->setFont(f);
486 
487   QLabel* noticeLabel = new QLabel(
488       tr("N.B. If you put unique title to submenu, it may not be translated to "
489          "another language.\nN.B. Duplicated commands will be ignored. Only "
490          "the last one will appear in the menu bar."),
491       this);
492   QFont nf("Arial", 9, QFont::Normal);
493   nf.setItalic(true);
494   noticeLabel->setFont(nf);
495 
496   //--- layout
497   QVBoxLayout* mainLay = new QVBoxLayout();
498   m_topLayout->setMargin(0);
499   m_topLayout->setSpacing(0);
500   {
501     QGridLayout* mainUILay = new QGridLayout();
502     mainUILay->setMargin(5);
503     mainUILay->setHorizontalSpacing(8);
504     mainUILay->setVerticalSpacing(5);
505     {
506       mainUILay->addWidget(menuBarLabel, 0, 0);
507       mainUILay->addWidget(menuItemListLabel, 0, 1);
508       mainUILay->addWidget(m_menuBarTree, 1, 0);
509       mainUILay->addWidget(m_commandListTree, 1, 1);
510 
511       mainUILay->addWidget(noticeLabel, 2, 0, 1, 2);
512     }
513     mainUILay->setRowStretch(0, 0);
514     mainUILay->setRowStretch(1, 1);
515     mainUILay->setRowStretch(2, 0);
516     mainUILay->setColumnStretch(0, 1);
517     mainUILay->setColumnStretch(1, 1);
518 
519     m_topLayout->addLayout(mainUILay, 1);
520   }
521 
522   m_buttonLayout->setMargin(0);
523   m_buttonLayout->setSpacing(30);
524   {
525     m_buttonLayout->addStretch(1);
526     m_buttonLayout->addWidget(okBtn, 0);
527     m_buttonLayout->addWidget(cancelBtn, 0);
528     m_buttonLayout->addStretch(1);
529   }
530 
531   //--- signal/slot connections
532 
533   bool ret = connect(okBtn, SIGNAL(clicked()), this, SLOT(onOkPressed()));
534   ret      = ret && connect(cancelBtn, SIGNAL(clicked()), this, SLOT(reject()));
535   assert(ret);
536 }
537 
538 //-----------------------------------------------------------------------------
539 
onOkPressed()540 void MenuBarPopup::onOkPressed() {
541   m_menuBarTree->saveMenuTree();
542 
543   accept();
544 }