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