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 "WorkflowInvestigationWidgetsController.h"
23 
24 #include <QApplication>
25 #include <QClipboard>
26 #include <QHeaderView>
27 #include <QMenu>
28 #include <QTabWidget>
29 #include <QTableView>
30 #include <QtMath>
31 
32 #include "InvestigationDataModel.h"
33 
34 const qint16 HEADER_TEXT_MARGIN = 40;
35 const int DEFAULT_SELECTED_COLUMN = -1;
36 const char *CONVERT_TO_DOC_ACTION_NAME = "Convert to document";
37 const char *COPY_TO_CLIPBOARD_ACTION_NAME = "Copy to clipboard";
38 const char *HIDE_SELECTED_COLUMN_ACTION_NAME = "Hide this column";
39 const char *HIDE_ALL_COLUMNS_BUT_SELECTED_ACTION_NAME = "Hide all columns but this";
40 const char *SHOW_ALL_COLUMNS_ACTION_NAME = "Show all columns";
41 
42 namespace U2 {
43 
WorkflowInvestigationWidgetsController(QWidget * parent)44 WorkflowInvestigationWidgetsController::WorkflowInvestigationWidgetsController(QWidget *parent)
45     : QObject(qobject_cast<QObject *>(parent)),
46       investigationView(nullptr),
47       investigationModel(nullptr),
48       investigatedLink(nullptr),
49       investigatorName(),
50       wasDisplayed(false),
51       exportInvestigationAction(nullptr),
52       copyToClipboardAction(nullptr),
53       hideThisColumnAction(nullptr),
54       hideAllColumnsButThisAction(nullptr),
55       showAllColumnsAction(nullptr),
56       selectedColumn(DEFAULT_SELECTED_COLUMN),
57       columnWidths() {
58     QTabWidget *container = dynamic_cast<QTabWidget *>(parent);
59     Q_ASSERT(nullptr != container);
60     Q_UNUSED(container);
61 
62     exportInvestigationAction = new QAction(
63         QIcon(":workflow_designer/images/document_convert.png"),
64         tr(CONVERT_TO_DOC_ACTION_NAME),
65         this);
66     connect(exportInvestigationAction, SIGNAL(triggered()), SLOT(sl_exportInvestigation()));
67 
68     copyToClipboardAction = new QAction(QIcon(":workflow_designer/images/clipboard.png"),
69                                         tr(COPY_TO_CLIPBOARD_ACTION_NAME),
70                                         this);
71     connect(copyToClipboardAction, SIGNAL(triggered()), SLOT(sl_copyToClipboard()));
72 
73     hideThisColumnAction = new QAction(tr(HIDE_SELECTED_COLUMN_ACTION_NAME), this);
74     connect(hideThisColumnAction, SIGNAL(triggered()), SLOT(sl_hideSelectedColumn()));
75 
76     hideAllColumnsButThisAction = new QAction(tr(HIDE_ALL_COLUMNS_BUT_SELECTED_ACTION_NAME), this);
77     connect(hideAllColumnsButThisAction, SIGNAL(triggered()), SLOT(sl_hideAllColumnsButSelected()));
78 
79     showAllColumnsAction = new QAction(tr(SHOW_ALL_COLUMNS_ACTION_NAME), this);
80     connect(showAllColumnsAction, SIGNAL(triggered()), SLOT(sl_showAllColumns()));
81 }
82 
~WorkflowInvestigationWidgetsController()83 WorkflowInvestigationWidgetsController::~WorkflowInvestigationWidgetsController() {
84     deleteBusInvestigations();
85 }
86 
eventFilter(QObject * watched,QEvent * event)87 bool WorkflowInvestigationWidgetsController::eventFilter(QObject *watched, QEvent *event) {
88     if (QEvent::Paint == event->type() && nullptr != investigationView && watched == dynamic_cast<QObject *>(investigationView->viewport())) {
89         if (nullptr == investigationView->model() && nullptr != investigatedLink) {
90             createInvestigationModel();
91             investigationView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
92             adjustInvestigationColumnWidth(investigationView);
93         }
94     }
95     return QObject::eventFilter(watched, event);
96 }
97 
setCurrentInvestigation(const Workflow::Link * bus)98 void WorkflowInvestigationWidgetsController::setCurrentInvestigation(const Workflow::Link *bus) {
99     QTabWidget *container = dynamic_cast<QTabWidget *>(parent());
100     Q_ASSERT(nullptr != container);
101     const int tabNumberForInvestigation = container->indexOf(investigationView);
102     if (-1 != tabNumberForInvestigation) {
103         deleteBusInvestigations();
104         container->removeTab(tabNumberForInvestigation);
105     }
106     investigatedLink = bus;
107     createNewInvestigation();
108     investigationView->setParent(container);
109     investigatorName = tr("Messages from '") + bus->source()->owner()->getLabel() + tr("' to '") + bus->destination()->owner()->getLabel() + tr("'");
110     container->addTab(investigationView, investigatorName);
111     container->setCurrentWidget(investigationView);
112     if (!container->isVisible()) {
113         container->show();
114     }
115 }
116 
deleteBusInvestigations()117 void WorkflowInvestigationWidgetsController::deleteBusInvestigations() {
118     if (nullptr != investigationView && nullptr != investigationModel) {
119         const QBitArray hiddenColumns = investigationModel->getColumnsVisibility();
120         for (int column = 0; investigationModel->columnCount() > column; ++column) {
121             const int absoluteColumnNumber = investigationModel->getAbsoluteNumberOfVisibleColumn(column);
122             columnWidths[investigatedLink][absoluteColumnNumber] = investigationView->columnWidth(column) * static_cast<int>(qPow(-1, hiddenColumns.testBit(absoluteColumnNumber)));
123         }
124         delete investigationModel;
125         investigationModel = nullptr;
126         investigationView->viewport()->removeEventFilter(this);
127         delete investigationView;
128         investigationView = nullptr;
129     }
130 }
131 
resetInvestigations()132 void WorkflowInvestigationWidgetsController::resetInvestigations() {
133     investigatedLink = nullptr;
134     investigatorName.clear();
135     columnWidths.clear();
136 }
137 
createNewInvestigation()138 void WorkflowInvestigationWidgetsController::createNewInvestigation() {
139     investigationView = new QTableView();
140     investigationView->viewport()->installEventFilter(this);
141     investigationView->setContextMenuPolicy(Qt::CustomContextMenu);
142     connect(investigationView, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(sl_contextMenuRequested(const QPoint &)));
143     investigationView->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
144     connect(investigationView->horizontalHeader(),
145             SIGNAL(customContextMenuRequested(const QPoint &)),
146             SLOT(sl_hotizontalHeaderContextMenuRequested(const QPoint &)));
147 }
148 
createInvestigationModel()149 void WorkflowInvestigationWidgetsController::createInvestigationModel() {
150     Q_ASSERT(nullptr != investigatedLink && nullptr != investigationView);
151     investigationModel = new InvestigationDataModel(investigatedLink, this);
152 
153     connect(investigationModel, SIGNAL(si_investigationRequested(const Workflow::Link *, int)), SIGNAL(si_updateCurrentInvestigation(const Workflow::Link *, int)));
154     connect(investigationModel, SIGNAL(si_countOfMessagesRequested(const Workflow::Link *)), SIGNAL(si_countOfMessagesRequested(const Workflow::Link *)));
155     connect(investigationModel, SIGNAL(si_columnsVisibilityRequested()), SLOT(sl_columnsVisibilityResponse()));
156 
157     investigationView->setModel(investigationModel);
158 }
159 
adjustInvestigationColumnWidth(WorkflowInvestigationWidget * investigator)160 void WorkflowInvestigationWidgetsController::adjustInvestigationColumnWidth(
161     WorkflowInvestigationWidget *investigator) {
162     for (int currentColumn = 0; investigationModel->columnCount() > currentColumn; ++currentColumn) {
163         const int absoluteColumnNumber = investigationModel->getAbsoluteNumberOfVisibleColumn(currentColumn);
164         const int width = (columnWidths[investigatedLink].size() <= absoluteColumnNumber || 0 == columnWidths[investigatedLink][absoluteColumnNumber]) ? investigator->fontMetrics().width(investigationModel->headerData(
165                                                                                                                                                                                                                  currentColumn, Qt::Horizontal)
166                                                                                                                                                                                                .toString()) +
167                                                                                                                                                              HEADER_TEXT_MARGIN :
168                                                                                                                                                          columnWidths[investigatedLink][absoluteColumnNumber];
169         Q_ASSERT(0 < width);
170         investigator->setColumnWidth(currentColumn, width);
171     }
172 }
173 
setInvestigationWidgetsVisible(bool visible)174 void WorkflowInvestigationWidgetsController::setInvestigationWidgetsVisible(bool visible) {
175     QTabWidget *container = dynamic_cast<QTabWidget *>(parent());
176     Q_ASSERT(nullptr != container);
177     if (!visible && nullptr != investigationView) {
178         WorkflowInvestigationWidget *currentWidget = dynamic_cast<WorkflowInvestigationWidget *>(container->currentWidget());
179         wasDisplayed = (investigationView == currentWidget);
180         container->removeTab(container->indexOf(static_cast<QWidget *>(investigationView)));
181         deleteBusInvestigations();
182         if (wasDisplayed) {
183             container->hide();
184         }
185     } else if (visible && nullptr != investigatedLink) {
186         createNewInvestigation();
187         investigationView->setParent(container);
188         container->addTab(investigationView, investigatorName);
189         if (wasDisplayed) {
190             container->show();
191             container->setCurrentWidget(investigationView);
192         }
193     }
194 }
195 
sl_currentInvestigationUpdateResponse(const WorkflowInvestigationData & investigationInfo,const Workflow::Link * bus)196 void WorkflowInvestigationWidgetsController::sl_currentInvestigationUpdateResponse(
197     const WorkflowInvestigationData &investigationInfo,
198     const Workflow::Link *bus) {
199     Q_ASSERT(bus == investigatedLink);
200     Q_UNUSED(bus);
201     if (!investigationInfo.isEmpty()) {
202         const int rowInsertionStartPosition = investigationModel->loadedRowCount();
203         if (!investigationModel->headerData(0, Qt::Horizontal, Qt::DisplayRole).isValid()) {
204             const QList<QString> headerTitles = investigationInfo.keys();
205             for (int i = 0; i < headerTitles.size(); ++i) {
206                 investigationModel->setHeaderData(i, Qt::Horizontal, headerTitles[i]);
207             }
208             if (columnWidths[investigatedLink].isEmpty()) {
209                 columnWidths[investigatedLink].resize(headerTitles.size());
210                 columnWidths[investigatedLink].fill(0);
211             }
212         }
213         for (int columnNumber = 0; columnNumber < investigationInfo.keys().size(); ++columnNumber) {
214             const QString key = investigationInfo.keys()[columnNumber];
215             for (int rowNumber = rowInsertionStartPosition;
216                  rowNumber < investigationInfo[key].size() + rowInsertionStartPosition;
217                  ++rowNumber) {
218                 investigationModel->setData(investigationModel->index(rowNumber, columnNumber),
219                                             investigationInfo[key][rowNumber - rowInsertionStartPosition]);
220             }
221         }
222     } else if (investigationModel->getColumnsVisibility().isNull()) {
223         investigationModel->setColumnsVisibility(QBitArray(0));
224     }
225 }
226 
sl_countOfMessagesResponse(const Workflow::Link *,int countOfMessages)227 void WorkflowInvestigationWidgetsController::sl_countOfMessagesResponse(const Workflow::Link * /*bus*/,
228                                                                         int countOfMessages) {
229     investigationModel->insertRows(0, countOfMessages, QModelIndex());
230 }
231 
sl_contextMenuRequested(const QPoint & cursorPosition)232 void WorkflowInvestigationWidgetsController::sl_contextMenuRequested(const QPoint &cursorPosition) {
233     const QItemSelectionModel *selectionModel = investigationView->selectionModel();
234     const QModelIndexList selection = selectionModel->selectedIndexes();
235     if (1 == selection.size()) {
236         QMenu contextMenu;
237         contextMenu.addAction(exportInvestigationAction);
238         contextMenu.addSeparator();
239         contextMenu.addAction(copyToClipboardAction);
240         contextMenu.exec(investigationView->viewport()->mapToGlobal(cursorPosition));
241     }
242 }
243 
sl_hotizontalHeaderContextMenuRequested(const QPoint & cursorPosition)244 void WorkflowInvestigationWidgetsController::sl_hotizontalHeaderContextMenuRequested(
245     const QPoint &cursorPosition) {
246     QMenu contextMenu;
247     selectedColumn = investigationView->columnAt(cursorPosition.x());
248     if (DEFAULT_SELECTED_COLUMN != selectedColumn) {
249         if (1 < investigationModel->columnCount()) {
250             contextMenu.addAction(hideThisColumnAction);
251             contextMenu.addAction(hideAllColumnsButThisAction);
252         }
253         if (investigationModel->isAnyColumnHidden()) {
254             contextMenu.addAction(showAllColumnsAction);
255         }
256         contextMenu.exec(investigationView->viewport()->mapToGlobal(QPoint(cursorPosition.x(),
257                                                                            cursorPosition.y() - investigationView->horizontalHeader()->height())));
258         selectedColumn = DEFAULT_SELECTED_COLUMN;
259     }
260 }
261 
sl_exportInvestigation()262 void WorkflowInvestigationWidgetsController::sl_exportInvestigation() {
263     const QItemSelectionModel *selectionModel = investigationView->selectionModel();
264     const QModelIndexList selected = selectionModel->selectedIndexes();
265     Q_ASSERT(1 == selected.size());
266     const QModelIndex item = selected.first();
267     const QString messageType = investigationModel->headerData(item.column(), Qt::Horizontal)
268                                     .toString();
269     emit si_convertionMessages2DocumentsIsRequested(investigatedLink,
270                                                     messageType,
271                                                     item.row());
272 }
273 
sl_copyToClipboard() const274 void WorkflowInvestigationWidgetsController::sl_copyToClipboard() const {
275     const QModelIndexList selected = investigationView->selectionModel()->selectedIndexes();
276     Q_ASSERT(1 == selected.size());
277     QApplication::clipboard()->setText(selected.first().data().toString());
278 }
279 
sl_hideSelectedColumn()280 void WorkflowInvestigationWidgetsController::sl_hideSelectedColumn() {
281     const int absoluteColumnNumber = investigationModel->getAbsoluteNumberOfVisibleColumn(selectedColumn);
282     columnWidths[investigatedLink][absoluteColumnNumber] = -investigationView->columnWidth(selectedColumn);
283     investigationModel->removeColumn(selectedColumn);
284 }
285 
sl_hideAllColumnsButSelected()286 void WorkflowInvestigationWidgetsController::sl_hideAllColumnsButSelected() {
287     for (int column = 0; investigationModel->columnCount() > column; ++column) {
288         if (selectedColumn != column) {
289             columnWidths[investigatedLink][investigationModel->getAbsoluteNumberOfVisibleColumn(column)] = -investigationView->columnWidth(column);
290         }
291     }
292     investigationModel->removeColumns(0, selectedColumn);
293     investigationModel->removeColumns(1, investigationModel->columnCount() - 1);
294 }
295 
sl_showAllColumns()296 void WorkflowInvestigationWidgetsController::sl_showAllColumns() {
297     const int absoluteSelectedColumnNumber = investigationModel->getAbsoluteNumberOfVisibleColumn(selectedColumn);
298     investigationModel->showAllHiddenColumns();
299     for (int column = 0; investigationModel->columnCount() > column; ++column) {
300         const int width = columnWidths[investigatedLink][column];
301         if (absoluteSelectedColumnNumber != column && 0 > width) {
302             columnWidths[investigatedLink][column] = -width;
303             investigationView->setColumnWidth(column, -width);
304         }
305     }
306 }
307 
sl_columnsVisibilityResponse()308 void WorkflowInvestigationWidgetsController::sl_columnsVisibilityResponse() {
309     QBitArray hiddenColumns(0);
310     const QVector<int> widths = columnWidths[investigatedLink];
311     if (!widths.isEmpty()) {
312         const int columnCount = widths.size();
313         hiddenColumns.resize(columnCount);
314         for (int column = 0; columnCount > column; ++column) {
315             if (0 > widths[column]) {
316                 hiddenColumns.setBit(column, true);
317             }
318         }
319     }
320     investigationModel->setColumnsVisibility(hiddenColumns);
321 }
322 
323 }    // namespace U2
324