1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "ObjectViewTreeController.h"
23 
24 #include <QEvent>
25 #include <QMenu>
26 
27 #include <U2Core/AppContext.h>
28 #include <U2Core/Counter.h>
29 #include <U2Core/ProjectModel.h>
30 #include <U2Core/Task.h>
31 #include <U2Core/U2SafePoints.h>
32 
33 // TODO:
34 //  track factory registry and show only the states with factories available
35 //  delete view state if factory refuses create view
36 
37 namespace U2 {
38 
ObjectViewTreeController(QTreeWidget * w)39 ObjectViewTreeController::ObjectViewTreeController(QTreeWidget *w)
40     : QObject(w), tree(w) {
41     bookmarkStateIcon = QIcon(":core/images/bookmark_item.png");
42     bookmarkActiveIcon = QIcon(":core/images/bookmark.png");
43     bookmarkInactiveIcon = QIcon(":core/images/bookmark_inactive.png");
44 
45     tree->headerItem()->setHidden(true);
46     tree->setSelectionMode(QAbstractItemView::SingleSelection);
47     tree->setContextMenuPolicy(Qt::CustomContextMenu);
48     tree->setObjectName(ACTION_BOOKMARK_TREE_VIEW);
49 
50     connect(tree, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), SLOT(sl_onTreeCurrentChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
51     connect(tree, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(sl_onContextMenuRequested(const QPoint &)));
52     connect(tree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), SLOT(sl_onItemActivated(QTreeWidgetItem *, int)));
53     connect(tree, SIGNAL(itemChanged(QTreeWidgetItem *, int)), SLOT(sl_onItemChanged(QTreeWidgetItem *, int)));
54 
55     activateViewAction = new QAction(tr("Activate view"), this);
56     activateViewAction->setObjectName(ACTION_ACTIVATE_VIEW);
57     activateViewAction->setShortcut(QKeySequence(Qt::Key_Space));
58     activateViewAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
59     connect(activateViewAction, SIGNAL(triggered()), SLOT(sl_activateView()));
60 
61     addStateAction = new QAction(tr("Add bookmark"), this);
62     addStateAction->setObjectName(ACTION_ADD_BOOKMARK);
63     addStateAction->setIcon(QIcon(":core/images/bookmark_add.png"));
64     connect(addStateAction, SIGNAL(triggered()), SLOT(sl_addState()));
65 
66     removeStateAction = new QAction(tr("Remove bookmark"), this);
67     removeStateAction->setObjectName(ACTION_REMOVE_BOOKMARK);
68     removeStateAction->setIcon(QIcon(":core/images/bookmark_remove.png"));
69     removeStateAction->setShortcut(QKeySequence(Qt::Key_Delete));
70     removeStateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
71     connect(removeStateAction, SIGNAL(triggered()), SLOT(sl_removeState()));
72 
73     renameStateAction = new QAction(tr("Rename bookmark"), this);
74     renameStateAction->setObjectName(ACTION_RENAME_BOOKMARK);
75     renameStateAction->setIcon(QIcon(":core/images/bookmark_edit.png"));
76     renameStateAction->setShortcut(QKeySequence(Qt::Key_F2));
77     renameStateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
78     connect(renameStateAction, SIGNAL(triggered()), SLOT(sl_renameState()));
79 
80     tree->addAction(activateViewAction);
81     tree->addAction(removeStateAction);
82     tree->addAction(renameStateAction);
83 
84     connectModel();
85 
86     buildTree();
87     updateActions();
88 }
89 
connectModel()90 void ObjectViewTreeController::connectModel() {
91     Project *p = AppContext::getProject();
92     connect(p, SIGNAL(si_objectViewStateAdded(GObjectViewState *)), SLOT(sl_onViewStateAdded(GObjectViewState *)));
93     connect(p, SIGNAL(si_objectViewStateRemoved(GObjectViewState *)), SLOT(sl_onViewStateRemoved(GObjectViewState *)));
94 
95     MWMDIManager *mdi = AppContext::getMainWindow()->getMDIManager();
96     connect(mdi, SIGNAL(si_windowAdded(MWMDIWindow *)), SLOT(sl_onMdiWindowAdded(MWMDIWindow *)));
97     connect(mdi, SIGNAL(si_windowClosing(MWMDIWindow *)), SLOT(sl_onMdiWindowClosing(MWMDIWindow *)));
98 
99     connect(mdi, SIGNAL(si_windowActivated(MWMDIWindow *)), SLOT(sl_onMdiWindowActivated(MWMDIWindow *)));
100 }
101 
buildTree()102 void ObjectViewTreeController::buildTree() {
103     tree->clear();
104 
105     const QList<GObjectViewState *> &states = AppContext::getProject()->getGObjectViewStates();
106     foreach (GObjectViewState *s, states) {
107         addState(s);
108     }
109 
110     QList<GObjectViewWindow *> views = GObjectViewUtils::getAllActiveViews();
111     foreach (GObjectViewWindow *v, views) {
112         addViewWindow(v);
113     }
114 }
115 
addViewWindow(GObjectViewWindow * viewWindow)116 void ObjectViewTreeController::addViewWindow(GObjectViewWindow *viewWindow) {
117     viewWindow->installEventFilter(this);
118     connect(viewWindow, SIGNAL(si_persistentStateChanged(GObjectViewWindow *)), SLOT(sl_onViewPersistentStateChanged(GObjectViewWindow *)));
119     connect(viewWindow->getObjectView(), SIGNAL(si_nameChanged(const QString &)), SLOT(sl_onViewNameChanged(const QString &)));
120     OVTViewItem *vi = findViewItem(viewWindow->getViewName());
121     if (vi == nullptr) {
122         vi = new OVTViewItem(viewWindow, this);
123         tree->addTopLevelItem(vi);
124         if (tree->currentItem() == nullptr) {
125             tree->setCurrentItem(vi);
126             vi->markAsActive(true);
127         }
128     } else {
129         assert(vi->viewWindow == nullptr);
130         vi->viewWindow = viewWindow;
131         vi->updateVisual();
132     }
133 }
134 
addState(GObjectViewState * s)135 OVTStateItem *ObjectViewTreeController::addState(GObjectViewState *s) {
136     OVTViewItem *vi = findViewItem(s->getViewName());
137     if (vi == nullptr) {
138         vi = new OVTViewItem(s->getViewName(), this);
139         tree->addTopLevelItem(vi);
140     }
141     OVTStateItem *si = findStateItem(s);
142     SAFE_POINT(si == nullptr, QString("State item is already exists: %1 -> %2").arg(s->getViewName()).arg(s->getStateName()), si);
143     si = new OVTStateItem(s, vi, this);
144     si->setIcon(0, bookmarkStateIcon);
145     vi->addChild(si);
146     return si;
147 }
148 
removeState(GObjectViewState * s)149 void ObjectViewTreeController::removeState(GObjectViewState *s) {
150     OVTStateItem *si = findStateItem(s);
151     SAFE_POINT(si != nullptr, QString("Failed to find state item to remove: %1 -> %2").arg(s->getViewName()).arg(s->getStateName()), );
152     OVTViewItem *vi = static_cast<OVTViewItem *>(si->parent());
153     delete si;
154     if (vi->childCount() == 0) {
155         if (vi->viewWindow == nullptr) {
156             delete vi;
157         } else {
158             makeViewTransient(vi->viewWindow);
159         }
160     }
161 }
162 
currentItem() const163 OVTItem *ObjectViewTreeController::currentItem() const {
164     QTreeWidgetItem *i = tree->currentItem();
165     return static_cast<OVTItem *>(i);
166 }
167 
currentViewItem(bool deriveFromState) const168 OVTViewItem *ObjectViewTreeController::currentViewItem(bool deriveFromState) const {
169     OVTItem *i = currentItem();
170     OVTViewItem *vi = (i != nullptr && i->isViewItem()) ? static_cast<OVTViewItem *>(i) : nullptr;
171     if (vi == nullptr && deriveFromState) {
172         OVTStateItem *si = currentStateItem();
173         if (si != nullptr) {
174             vi = static_cast<OVTViewItem *>(si->parent());
175         }
176     }
177     return vi;
178 }
179 
currentStateItem() const180 OVTStateItem *ObjectViewTreeController::currentStateItem() const {
181     OVTItem *i = currentItem();
182     return (i != nullptr && i->isStateItem()) ? static_cast<OVTStateItem *>(i) : nullptr;
183 }
activeViewItem() const184 OVTViewItem *ObjectViewTreeController::activeViewItem() const {
185     const GObjectViewWindow *w = GObjectViewUtils::getActiveObjectViewWindow();
186     if (w == nullptr) {
187         return nullptr;
188     }
189     for (int i = 0; i < tree->topLevelItemCount(); i++) {
190         OVTViewItem *vi = static_cast<OVTViewItem *>(tree->topLevelItem(i));
191         if (vi->viewWindow == w) {
192             return vi;
193         }
194     }
195     return nullptr;
196 }
197 
findStateToOpen() const198 GObjectViewState *ObjectViewTreeController::findStateToOpen() const {
199     OVTStateItem *si = currentStateItem();
200     GObjectViewState *state = nullptr;
201     if (si != nullptr) {
202         state = si->state;
203     } else {
204         OVTViewItem *vi = currentViewItem();
205         if (vi != nullptr && vi->viewWindow == nullptr) {
206             const QList<GObjectViewState *> &allStates = AppContext::getProject()->getGObjectViewStates();
207             state = GObjectViewUtils::findStateInList(vi->viewName, GObjectViewState::APP_CLOSING_STATE_NAME, allStates);
208         }
209     }
210     return state;
211 }
212 
updateActions()213 void ObjectViewTreeController::updateActions() {
214     OVTStateItem *si = currentStateItem();
215     OVTViewItem *vi = currentViewItem(true);
216 
217     bool hasActiveView = vi != nullptr && vi->viewWindow != nullptr;
218 
219     GObjectViewState *stateToOpen = findStateToOpen();
220 
221     bool canAddStates = hasActiveView && vi->viewWindow->getViewFactory()->supportsSavedStates() && vi->isActiveItem();
222 
223     activateViewAction->setEnabled(hasActiveView || stateToOpen != nullptr);
224     addStateAction->setEnabled(canAddStates);
225     removeStateAction->setEnabled(si != nullptr || (vi != nullptr && vi->childCount() > 0));
226     renameStateAction->setEnabled(si != nullptr);
227 }
228 
sl_onMdiWindowAdded(MWMDIWindow * w)229 void ObjectViewTreeController::sl_onMdiWindowAdded(MWMDIWindow *w) {
230     GObjectViewWindow *vw = qobject_cast<GObjectViewWindow *>(w);
231     if (vw == nullptr) {
232         return;
233     }
234     addViewWindow(vw);
235     updateActions();
236 }
237 
sl_onMdiWindowClosing(MWMDIWindow * w)238 void ObjectViewTreeController::sl_onMdiWindowClosing(MWMDIWindow *w) {
239     GObjectViewWindow *wv = qobject_cast<GObjectViewWindow *>(w);
240     if (wv == nullptr) {
241         return;
242     }
243     OVTViewItem *vi = findViewItem(wv->getViewName());
244     SAFE_POINT(vi != nullptr, QString("Can't find view item on window closing! View name: %1").arg(wv->getViewName()), );
245     if (wv->isPersistent()) {
246         vi->viewWindow = nullptr;
247         vi->updateVisual();
248     } else {
249         assert(vi->childCount() == 0);
250         delete vi;
251     }
252     updateActions();
253 }
254 
sl_onMdiWindowActivated(MWMDIWindow * w)255 void ObjectViewTreeController::sl_onMdiWindowActivated(MWMDIWindow *w) {
256     GObjectViewWindow *wv = qobject_cast<GObjectViewWindow *>(w);
257 
258     for (int i = 0; i < tree->topLevelItemCount(); i++) {
259         OVTViewItem *vi = static_cast<OVTViewItem *>(tree->topLevelItem(i));
260         bool isActiveItem = (vi->viewWindow == wv && wv != nullptr);
261         vi->markAsActive(isActiveItem);
262     }
263     updateActions();
264 }
265 
sl_onViewStateAdded(GObjectViewState * s)266 void ObjectViewTreeController::sl_onViewStateAdded(GObjectViewState *s) {
267     OVTStateItem *si = addState(s);
268     updateActions();
269     connect(s, SIGNAL(si_stateModified(GObjectViewState *)), SLOT(sl_onStateModified(GObjectViewState *)));
270 
271     if (s->getStateName() != GObjectViewState::APP_CLOSING_STATE_NAME) {
272         // Start renaming to allow user to enter the name for bookmark
273         tree->setCurrentItem(si);
274         sl_renameState();
275     }
276 }
277 
sl_onViewStateRemoved(GObjectViewState * s)278 void ObjectViewTreeController::sl_onViewStateRemoved(GObjectViewState *s) {
279     removeState(s);
280     updateActions();
281     s->disconnect(this);
282 }
283 
sl_onViewPersistentStateChanged(GObjectViewWindow * v)284 void ObjectViewTreeController::sl_onViewPersistentStateChanged(GObjectViewWindow *v) {
285     OVTViewItem *vi = findViewItem(v->getViewName());
286     vi->updateVisual();
287     updateActions();
288 }
289 
findViewItem(const QString & name)290 OVTViewItem *ObjectViewTreeController::findViewItem(const QString &name) {
291     for (int i = 0; i < tree->topLevelItemCount(); i++) {
292         OVTViewItem *vi = static_cast<OVTViewItem *>(tree->topLevelItem(i));
293         if (vi->viewName == name) {
294             return vi;
295         }
296     }
297     return nullptr;
298 }
299 
findStateItem(GObjectViewState * s)300 OVTStateItem *ObjectViewTreeController::findStateItem(GObjectViewState *s) {
301     OVTViewItem *vi = findViewItem(s->getViewName());
302     if (vi == nullptr) {
303         return nullptr;
304     }
305     for (int i = 0; i < vi->childCount(); i++) {
306         OVTStateItem *si = static_cast<OVTStateItem *>(vi->child(i));
307         if (si->state == s) {
308             return si;
309         }
310     }
311     return nullptr;
312 }
313 
sl_onContextMenuRequested(const QPoint & pos)314 void ObjectViewTreeController::sl_onContextMenuRequested(const QPoint &pos) {
315     Q_UNUSED(pos);
316     QMenu popup;
317     bool hasItemSelected = tree->currentItem() != nullptr;
318 
319     if (hasItemSelected) {
320         popup.addAction(activateViewAction);
321     }
322 
323     popup.addAction(addStateAction);
324     popup.addAction(renameStateAction);
325     popup.addAction(removeStateAction);
326 
327     // TODO: emit si_onPopupMenuRequested(*popup);
328     if (!popup.isEmpty()) {
329         popup.exec(QCursor::pos());
330     }
331 }
332 
sl_onTreeCurrentChanged(QTreeWidgetItem * current,QTreeWidgetItem * previous)333 void ObjectViewTreeController::sl_onTreeCurrentChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) {
334     Q_UNUSED(current);
335     Q_UNUSED(previous);
336     updateActions();
337 }
338 
sl_activateView()339 void ObjectViewTreeController::sl_activateView() {
340     GCOUNTER(cvar, "Bookmarks::Bookmark Activated");
341     OVTViewItem *vi = currentViewItem();
342     if (vi != nullptr && vi->viewWindow != nullptr) {  // raise existing view, no state change
343         AppContext::getMainWindow()->getMDIManager()->activateWindow(vi->viewWindow);
344         return;
345     }
346     // open closed view by state or update state of the active view
347     GObjectViewState *state = findStateToOpen();
348     if (state == nullptr) {
349         return;
350     }
351     GObjectViewWindow *view = GObjectViewUtils::findViewByName(state->getViewName());
352     if (view != nullptr) {
353         assert(view->isPersistent());
354         AppContext::getMainWindow()->getMDIManager()->activateWindow(view);
355         if (state != nullptr) {  // state was selected -> apply state
356             AppContext::getTaskScheduler()->registerTopLevelTask(view->getObjectView()->updateViewTask(state->getStateName(), state->getStateData()));
357         }
358     } else {
359         GObjectViewFactory *f = AppContext::getObjectViewFactoryRegistry()->getFactoryById(state->getViewFactoryId());
360         AppContext::getTaskScheduler()->registerTopLevelTask(f->createViewTask(state->getViewName(), state->getStateData()));
361     }
362 }
363 
sl_onItemActivated(QTreeWidgetItem * i,int col)364 void ObjectViewTreeController::sl_onItemActivated(QTreeWidgetItem *i, int col) {
365     Q_UNUSED(i);
366     Q_UNUSED(col);
367     sl_activateView();
368 }
369 
makeViewPersistent(GObjectViewWindow * w)370 void ObjectViewTreeController::makeViewPersistent(GObjectViewWindow *w) {
371     assert(!w->isPersistent());
372     assert(w->getViewFactory()->supportsSavedStates());
373     w->setPersistent(true);
374 }
375 
sl_addState()376 void ObjectViewTreeController::sl_addState() {
377     GCOUNTER(cvar, "Bookmarks::Add New Bookmark");
378     OVTViewItem *vi = activeViewItem();
379     SAFE_POINT(vi != nullptr, QString("Can't find view item to add state!"), );
380     SAFE_POINT(vi->viewWindow != nullptr, QString("View window is NULL: %1").arg(vi->viewName), );
381     if (!vi->viewWindow->isPersistent()) {
382         makeViewPersistent(vi->viewWindow);
383     }
384     assert(vi->viewWindow->isPersistent());
385 
386     QString stateName = GObjectViewUtils::genUniqueStateName(tr("New bookmark"));  // todo: avoid localization here?
387     QVariantMap state = vi->viewWindow->getObjectView()->saveState();
388     GObjectViewState *s = new GObjectViewState(vi->viewWindow->getViewFactoryId(), vi->viewWindow->getViewName(), stateName, state);
389     AppContext::getProject()->addGObjectViewState(s);
390 
391     vi->setExpanded(true);
392 }
393 
makeViewTransient(GObjectViewWindow * w)394 void ObjectViewTreeController::makeViewTransient(GObjectViewWindow *w) {
395     assert(w->isPersistent());
396     w->setPersistent(false);
397 }
398 
sl_removeState()399 void ObjectViewTreeController::sl_removeState() {
400     GCOUNTER(cvar, "Bookmarks::Remove Bookmark");
401     OVTStateItem *si = currentStateItem();
402     Project *p = AppContext::getProject();
403     if (si != nullptr) {
404         assert(si->state != nullptr);
405         p->removeGObjectViewState(si->state);
406     } else {
407         OVTViewItem *vi = currentViewItem();
408         SAFE_POINT(vi != nullptr, QString("Can't find view item to remove its state!"), );
409         int childs = vi->childCount();
410         assert(childs > 0);
411         for (int i = 0; i < childs; i++) {
412             OVTStateItem *si2 = static_cast<OVTStateItem *>(vi->child(0));
413             p->removeGObjectViewState(si2->state);
414         }
415     }
416 }
417 
sl_renameState()418 void ObjectViewTreeController::sl_renameState() {
419     GCOUNTER(cvar, "Bookmarks::Rename Bookmark");
420     OVTStateItem *si = currentStateItem();
421     SAFE_POINT(si != nullptr, QString("Can't find state item to rename!"), );
422 
423     si->setFlags(si->flags() | Qt::ItemIsEditable);
424     tree->editItem(si);
425     // tree->disconnect(this, SLOT(sl_onItemChanged(QTreeWidgetItem*, int)));
426     si->setFlags(si->flags() ^ Qt::ItemIsEditable);
427 }
428 
sl_onItemChanged(QTreeWidgetItem * i,int c)429 void ObjectViewTreeController::sl_onItemChanged(QTreeWidgetItem *i, int c) {
430     assert(c == 0);
431     Q_UNUSED(c);
432     OVTItem *oi = static_cast<OVTItem *>(i);
433     if (oi->isViewItem()) {
434         OVTViewItem *vi = static_cast<OVTViewItem *>(oi);
435         assert(vi->text(0).endsWith(vi->viewName));
436         Q_UNUSED(vi);
437         return;
438     }
439     assert(oi->isStateItem());
440     OVTStateItem *si = static_cast<OVTStateItem *>(oi);
441     QString newName = si->text(0);
442     GObjectViewState *state = GObjectViewUtils::findStateByName(si->state->getViewName(), newName);
443     if (state == si->state) {
444         return;
445     }
446     if (state != nullptr) {
447         // todo: show error!
448         return;
449     }
450     if (newName.isEmpty()) {
451         // todo: show error
452         return;
453     }
454     si->state->setStateName(newName);
455 }
456 
sl_onStateModified(GObjectViewState * s)457 void ObjectViewTreeController::sl_onStateModified(GObjectViewState *s) {
458     OVTStateItem *si = findStateItem(s);
459     SAFE_POINT(si != nullptr, QString("Can't find state item to update: %1 -> %2").arg(s->getViewName()).arg(s->getStateName()), );
460     si->updateVisual();
461 }
462 
sl_onViewNameChanged(const QString & oldName)463 void ObjectViewTreeController::sl_onViewNameChanged(const QString &oldName) {
464     OVTViewItem *vi = findViewItem(oldName);
465     SAFE_POINT(vi, QString("Can't find view item to rename: %1").arg(oldName), );
466     vi->updateVisual();
467 }
468 
469 //////////////////////////////////////////////////////////////////////////
470 /// tree items
471 
OVTViewItem(GObjectViewWindow * v,ObjectViewTreeController * c)472 OVTViewItem::OVTViewItem(GObjectViewWindow *v, ObjectViewTreeController *c)
473     : OVTItem(c), viewName(v->getViewName()), viewWindow(v), isActive(false) {
474     updateVisual();
475 }
476 
OVTViewItem(const QString & _viewName,ObjectViewTreeController * c)477 OVTViewItem::OVTViewItem(const QString &_viewName, ObjectViewTreeController *c)
478     : OVTItem(c), viewName(_viewName), viewWindow(nullptr), isActive(false) {
479     updateVisual();
480 }
481 
updateVisual()482 void OVTViewItem::updateVisual() {
483     setIcon(0, viewWindow == nullptr ? controller->getInactiveBookmarkIcon() : controller->getActiveBookmarkIcon());
484     viewName = viewWindow == nullptr ? viewName : viewWindow->getViewName();
485 
486     QString text = viewName;
487     setText(0, text);
488 }
markAsActive(bool _isActive)489 void OVTViewItem::markAsActive(bool _isActive) {
490     isActive = _isActive;
491 
492     QFont curFont = font(0);
493     curFont.setBold(isActive);
494     setFont(0, curFont);
495 }
496 
OVTStateItem(GObjectViewState * _state,OVTViewItem * parent,ObjectViewTreeController * c)497 OVTStateItem::OVTStateItem(GObjectViewState *_state, OVTViewItem *parent, ObjectViewTreeController *c)
498     : OVTItem(c), state(_state) {
499     updateVisual();
500     parent->addChild(this);
501 }
502 
updateVisual()503 void OVTStateItem::updateVisual() {
504     setText(0, state->getStateName());
505 }
506 
507 }  // namespace U2
508