2017-04-27 18:26:05 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2013-04-14 03:44:02 +00:00
|
|
|
/*
|
|
|
|
* divelistview.cpp
|
|
|
|
*
|
|
|
|
* classes for the divelist of Subsurface
|
|
|
|
*
|
|
|
|
*/
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "qt-models/filtermodels.h"
|
|
|
|
#include "desktop-widgets/modeldelegates.h"
|
|
|
|
#include "desktop-widgets/mainwindow.h"
|
|
|
|
#include "desktop-widgets/divepicturewidget.h"
|
2019-11-24 14:02:34 +00:00
|
|
|
#include "core/selection.h"
|
2019-11-17 17:13:55 +00:00
|
|
|
#include "core/divefilter.h"
|
2020-04-19 14:48:53 +00:00
|
|
|
#include "core/divesite.h" // for dive_site_table. TODO: remove once adding pictures is undoified
|
2015-04-24 15:10:55 +00:00
|
|
|
#include <unistd.h>
|
2013-05-21 19:51:49 +00:00
|
|
|
#include <QSettings>
|
2015-02-09 20:58:40 +00:00
|
|
|
#include <QKeyEvent>
|
2013-10-18 00:50:08 +00:00
|
|
|
#include <QFileDialog>
|
2015-04-24 15:10:55 +00:00
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QStandardPaths>
|
2015-05-18 08:52:03 +00:00
|
|
|
#include <QMessageBox>
|
2018-05-23 07:55:49 +00:00
|
|
|
#include <QHeaderView>
|
2019-11-13 14:08:40 +00:00
|
|
|
#include "commands/command.h"
|
2019-08-05 17:41:15 +00:00
|
|
|
#include "core/errorhelper.h"
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "core/qthelper.h"
|
2019-05-31 14:09:14 +00:00
|
|
|
#include "core/trip.h"
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "desktop-widgets/divelistview.h"
|
|
|
|
#include "qt-models/divepicturemodel.h"
|
|
|
|
#include "core/metrics.h"
|
2018-10-21 16:00:02 +00:00
|
|
|
#include "desktop-widgets/simplewidgets.h"
|
2019-08-30 15:38:54 +00:00
|
|
|
#include "desktop-widgets/mapwidget.h"
|
2013-04-12 07:24:07 +00:00
|
|
|
|
2019-11-28 20:41:23 +00:00
|
|
|
DiveListView::DiveListView(QWidget *parent) : QTreeView(parent),
|
2019-11-28 14:42:34 +00:00
|
|
|
currentLayout(DiveTripModelBase::TREE),
|
2020-04-26 16:44:59 +00:00
|
|
|
initialColumnWidths(DiveTripModelBase::COLUMNS, 50), // Set up with default length 50
|
|
|
|
programmaticalSelectionChange(false)
|
2013-04-12 07:24:07 +00:00
|
|
|
{
|
2014-01-15 17:52:42 +00:00
|
|
|
setItemDelegate(new DiveListDelegate(this));
|
2013-04-24 15:57:30 +00:00
|
|
|
setUniformRowHeights(true);
|
2018-12-27 09:06:11 +00:00
|
|
|
setItemDelegateForColumn(DiveTripModelBase::RATING, new StarWidgetsDelegate(this));
|
2019-11-27 20:55:37 +00:00
|
|
|
MultiFilterSortModel *m = MultiFilterSortModel::instance();
|
|
|
|
setModel(m);
|
|
|
|
connect(m, &MultiFilterSortModel::selectionChanged, this, &DiveListView::diveSelectionChanged);
|
|
|
|
connect(m, &MultiFilterSortModel::currentDiveChanged, this, &DiveListView::currentDiveChanged);
|
2013-06-12 17:14:25 +00:00
|
|
|
|
2018-11-06 20:48:18 +00:00
|
|
|
setSortingEnabled(true);
|
2013-06-06 00:06:42 +00:00
|
|
|
setContextMenuPolicy(Qt::DefaultContextMenu);
|
2015-02-10 20:08:01 +00:00
|
|
|
setSelectionMode(ExtendedSelection);
|
2013-05-21 19:51:49 +00:00
|
|
|
header()->setContextMenuPolicy(Qt::ActionsContextMenu);
|
2018-12-27 09:06:11 +00:00
|
|
|
|
|
|
|
resetModel();
|
2014-10-15 13:30:49 +00:00
|
|
|
|
2019-02-24 21:26:08 +00:00
|
|
|
connect(&diveListNotifier, &DiveListNotifier::tripChanged, this, &DiveListView::tripChanged);
|
|
|
|
|
2013-11-13 18:59:56 +00:00
|
|
|
header()->setStretchLastSection(true);
|
2018-10-27 15:44:06 +00:00
|
|
|
header()->setSortIndicatorShown(true);
|
2018-10-29 14:06:23 +00:00
|
|
|
header()->setSectionsClickable(true);
|
2018-11-04 16:54:38 +00:00
|
|
|
connect(header(), &QHeaderView::sortIndicatorChanged, this, &DiveListView::sortIndicatorChanged);
|
2015-02-06 11:19:04 +00:00
|
|
|
|
|
|
|
installEventFilter(this);
|
2018-07-25 18:40:47 +00:00
|
|
|
|
2018-12-27 09:06:11 +00:00
|
|
|
for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS; i++)
|
2018-07-25 19:23:19 +00:00
|
|
|
calculateInitialColumnWidth(i);
|
2018-07-26 16:03:07 +00:00
|
|
|
setColumnWidths();
|
2013-11-08 22:30:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DiveListView::~DiveListView()
|
|
|
|
{
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup("ListWidget");
|
2014-08-27 00:23:41 +00:00
|
|
|
// don't set a width for the last column - location is supposed to be "the rest"
|
2018-12-27 09:06:11 +00:00
|
|
|
for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS - 1; i++) {
|
2013-11-08 22:30:17 +00:00
|
|
|
if (isColumnHidden(i))
|
|
|
|
continue;
|
2014-08-27 00:23:41 +00:00
|
|
|
// we used to hardcode them all to 100 - so that might still be in the settings
|
2018-07-25 18:40:47 +00:00
|
|
|
if (columnWidth(i) == 100 || columnWidth(i) == initialColumnWidths[i])
|
2014-08-27 00:23:41 +00:00
|
|
|
settings.remove(QString("colwidth%1").arg(i));
|
|
|
|
else
|
|
|
|
settings.setValue(QString("colwidth%1").arg(i), columnWidth(i));
|
2013-11-08 22:30:17 +00:00
|
|
|
}
|
2018-12-27 09:06:11 +00:00
|
|
|
settings.remove(QString("colwidth%1").arg(DiveTripModelBase::COLUMNS - 1));
|
2013-11-08 22:30:17 +00:00
|
|
|
settings.endGroup();
|
|
|
|
}
|
|
|
|
|
2018-12-27 09:06:11 +00:00
|
|
|
void DiveListView::resetModel()
|
|
|
|
{
|
2019-11-27 20:55:37 +00:00
|
|
|
MultiFilterSortModel *m = MultiFilterSortModel::instance();
|
|
|
|
m->resetModel(currentLayout);
|
2018-12-27 09:06:11 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 19:23:19 +00:00
|
|
|
void DiveListView::calculateInitialColumnWidth(int col)
|
2018-07-25 18:40:47 +00:00
|
|
|
{
|
|
|
|
const QFontMetrics metrics(defaultModelFont());
|
2020-01-02 01:14:15 +00:00
|
|
|
QString header_txt = MultiFilterSortModel::instance()->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString();
|
|
|
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
2018-07-25 18:40:47 +00:00
|
|
|
int em = metrics.width('m');
|
|
|
|
int zw = metrics.width('0');
|
|
|
|
int width = metrics.width(header_txt);
|
2020-01-02 01:14:15 +00:00
|
|
|
#else // QT 5.11 or newer
|
|
|
|
int em = metrics.horizontalAdvance('m');
|
|
|
|
int zw = metrics.horizontalAdvance('0');
|
|
|
|
int width = metrics.horizontalAdvance(header_txt);
|
|
|
|
#endif
|
2018-07-25 18:40:47 +00:00
|
|
|
int sw = 0;
|
|
|
|
switch (col) {
|
2018-12-27 09:06:11 +00:00
|
|
|
case DiveTripModelBase::NR:
|
|
|
|
case DiveTripModelBase::DURATION:
|
2018-07-25 18:40:47 +00:00
|
|
|
sw = 8*zw;
|
|
|
|
break;
|
2018-12-27 09:06:11 +00:00
|
|
|
case DiveTripModelBase::DATE:
|
2018-07-25 18:40:47 +00:00
|
|
|
sw = 14*em;
|
|
|
|
break;
|
2018-12-27 09:06:11 +00:00
|
|
|
case DiveTripModelBase::RATING:
|
2018-07-25 18:40:47 +00:00
|
|
|
sw = static_cast<StarWidgetsDelegate*>(itemDelegateForColumn(col))->starSize().width();
|
|
|
|
break;
|
2018-12-27 09:06:11 +00:00
|
|
|
case DiveTripModelBase::SUIT:
|
|
|
|
case DiveTripModelBase::SAC:
|
2018-07-25 18:40:47 +00:00
|
|
|
sw = 7*em;
|
|
|
|
break;
|
2018-12-27 09:06:11 +00:00
|
|
|
case DiveTripModelBase::PHOTOS:
|
2018-07-25 18:40:47 +00:00
|
|
|
sw = 5*em;
|
|
|
|
break;
|
2018-12-27 09:06:11 +00:00
|
|
|
case DiveTripModelBase::BUDDIES:
|
2018-09-04 15:10:11 +00:00
|
|
|
sw = 50*em;
|
|
|
|
break;
|
2018-12-27 09:06:11 +00:00
|
|
|
case DiveTripModelBase::LOCATION:
|
2018-07-25 18:40:47 +00:00
|
|
|
sw = 50*em;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
sw = 5*em;
|
|
|
|
}
|
|
|
|
if (sw > width)
|
|
|
|
width = sw;
|
|
|
|
width += zw; // small padding
|
|
|
|
initialColumnWidths[col] = std::max(initialColumnWidths[col], width);
|
|
|
|
}
|
|
|
|
|
2018-07-26 16:03:07 +00:00
|
|
|
void DiveListView::setColumnWidths()
|
2014-01-16 04:50:56 +00:00
|
|
|
{
|
2013-11-08 22:30:17 +00:00
|
|
|
QSettings settings;
|
2019-11-28 20:55:26 +00:00
|
|
|
std::vector<int> expandedRows = backupExpandedRows();
|
2013-11-08 22:30:17 +00:00
|
|
|
settings.beginGroup("ListWidget");
|
|
|
|
/* if no width are set, use the calculated width for each column;
|
|
|
|
* for that to work we need to temporarily expand all rows */
|
|
|
|
expandAll();
|
2018-12-27 09:06:11 +00:00
|
|
|
for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS; i++) {
|
2014-01-16 04:50:56 +00:00
|
|
|
if (isColumnHidden(i))
|
2013-11-08 22:30:17 +00:00
|
|
|
continue;
|
|
|
|
QVariant width = settings.value(QString("colwidth%1").arg(i));
|
|
|
|
if (width.isValid())
|
|
|
|
setColumnWidth(i, width.toInt());
|
|
|
|
else
|
2018-07-25 18:40:47 +00:00
|
|
|
setColumnWidth(i, initialColumnWidths[i]);
|
2013-11-08 22:30:17 +00:00
|
|
|
}
|
|
|
|
settings.endGroup();
|
2019-11-28 20:55:26 +00:00
|
|
|
restoreExpandedRows(expandedRows);
|
2013-11-13 18:59:56 +00:00
|
|
|
setColumnWidth(lastVisibleColumn(), 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
int DiveListView::lastVisibleColumn()
|
|
|
|
{
|
|
|
|
int lastColumn = -1;
|
2018-12-27 09:06:11 +00:00
|
|
|
for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS; i++) {
|
2014-01-16 04:50:56 +00:00
|
|
|
if (isColumnHidden(i))
|
2013-11-13 18:59:56 +00:00
|
|
|
continue;
|
|
|
|
lastColumn = i;
|
|
|
|
}
|
|
|
|
return lastColumn;
|
2013-11-08 23:08:14 +00:00
|
|
|
}
|
2013-11-08 22:46:25 +00:00
|
|
|
|
2019-11-28 20:55:26 +00:00
|
|
|
std::vector<int> DiveListView::backupExpandedRows()
|
2014-01-16 04:50:56 +00:00
|
|
|
{
|
2019-11-28 20:55:26 +00:00
|
|
|
std::vector<int> expandedRows;
|
2014-02-28 04:09:57 +00:00
|
|
|
for (int i = 0; i < model()->rowCount(); i++)
|
|
|
|
if (isExpanded(model()->index(i, 0)))
|
2013-11-08 23:08:14 +00:00
|
|
|
expandedRows.push_back(i);
|
2019-11-28 20:55:26 +00:00
|
|
|
return expandedRows;
|
2013-05-30 04:04:42 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 20:55:26 +00:00
|
|
|
void DiveListView::restoreExpandedRows(const std::vector<int> &expandedRows)
|
2014-01-16 04:50:56 +00:00
|
|
|
{
|
2013-11-16 20:41:47 +00:00
|
|
|
setAnimated(false);
|
2019-11-28 20:55:26 +00:00
|
|
|
for (int i: expandedRows)
|
2014-03-03 21:25:55 +00:00
|
|
|
setExpanded(model()->index(i, 0), true);
|
2013-11-16 20:41:47 +00:00
|
|
|
setAnimated(true);
|
2013-11-08 23:08:14 +00:00
|
|
|
}
|
2018-07-29 15:40:53 +00:00
|
|
|
|
|
|
|
// If the model is reset, check which items are trip-items and expand the first column
|
|
|
|
void DiveListView::reset()
|
2013-06-12 17:14:25 +00:00
|
|
|
{
|
2018-07-29 15:40:53 +00:00
|
|
|
// First, let the QTreeView do its thing.
|
|
|
|
QTreeView::reset();
|
|
|
|
|
2013-06-12 17:14:25 +00:00
|
|
|
QAbstractItemModel *m = model();
|
2018-07-29 15:40:53 +00:00
|
|
|
for (int i = 0; i < m->rowCount(); ++i) {
|
2014-02-28 04:09:57 +00:00
|
|
|
if (m->rowCount(m->index(i, 0)) != 0)
|
2013-06-12 17:14:25 +00:00
|
|
|
setFirstColumnSpanned(i, QModelIndex(), true);
|
2018-07-29 15:40:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Undo: select dives after add, remove, merge, split dive commands
Select the proper dives after the add, remove, split and merge
dives commands on undo *and* redo. Generally, select the added
dives. For undo of add, remember the pre-addition selection.
For redo of remove, select the closest dive to the first removed
dive.
The biggest part of the commit is the signal-interface between
the dive commands and the dive-list model and dive-list view.
This is done in two steps:
1) To the DiveTripModel in batches of trips. The dive trip model
transforms the dives into indices.
2) To the DiveListView. The DiveListView has to translate the
DiveTripModel indexes to actual indexes via its QSortFilterProxy-
model.
For code-reuse, derive all divelist-changing commands from a new base-class,
which has a flag that describes whether the divelist changed. The helper
functions which add and remove dives are made members of the base class and
set the flag is a selected dive is added or removed.
To properly detect when the current dive was deleted it
became necessary to turn the current dive from an index
to a pointer, because indices are not stable.
Unfortunately, in some cases an index was expected and these
places now have to transform the dive into an index. These
should be converted in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-03 09:35:43 +00:00
|
|
|
// If items were selected, inform the selection model
|
2020-03-11 10:30:51 +00:00
|
|
|
void DiveListView::diveSelectionChanged(const QVector<QModelIndex> &indices)
|
Undo: select dives after add, remove, merge, split dive commands
Select the proper dives after the add, remove, split and merge
dives commands on undo *and* redo. Generally, select the added
dives. For undo of add, remember the pre-addition selection.
For redo of remove, select the closest dive to the first removed
dive.
The biggest part of the commit is the signal-interface between
the dive commands and the dive-list model and dive-list view.
This is done in two steps:
1) To the DiveTripModel in batches of trips. The dive trip model
transforms the dives into indices.
2) To the DiveListView. The DiveListView has to translate the
DiveTripModel indexes to actual indexes via its QSortFilterProxy-
model.
For code-reuse, derive all divelist-changing commands from a new base-class,
which has a flag that describes whether the divelist changed. The helper
functions which add and remove dives are made members of the base class and
set the flag is a selected dive is added or removed.
To properly detect when the current dive was deleted it
became necessary to turn the current dive from an index
to a pointer, because indices are not stable.
Unfortunately, in some cases an index was expected and these
places now have to transform the dive into an index. These
should be converted in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-03 09:35:43 +00:00
|
|
|
{
|
2020-04-26 16:44:59 +00:00
|
|
|
// This is the entry point for programmatical selection changes.
|
|
|
|
// Set a flag so that selection changes are not further processed,
|
|
|
|
// since the core structures were already set.
|
|
|
|
programmaticalSelectionChange = true;
|
|
|
|
|
2019-06-23 09:13:41 +00:00
|
|
|
clearSelection();
|
2020-04-26 19:21:44 +00:00
|
|
|
QItemSelection selection;
|
|
|
|
for (const QModelIndex &index: indices)
|
|
|
|
selection.select(index, index); // Is there a faster way to do this?
|
|
|
|
selectionModel()->select(selection, QItemSelectionModel::Rows | QItemSelectionModel::Select);
|
|
|
|
|
|
|
|
// Expand all unexpanded trips
|
|
|
|
std::vector<int> affectedTrips;
|
2020-03-11 10:30:51 +00:00
|
|
|
for (const QModelIndex &index: indices) {
|
2020-04-26 19:21:44 +00:00
|
|
|
if (!index.parent().isValid())
|
|
|
|
continue;
|
|
|
|
int row = index.parent().row();
|
|
|
|
if (std::find(affectedTrips.begin(), affectedTrips.end(), row) == affectedTrips.end())
|
|
|
|
affectedTrips.push_back(row);
|
|
|
|
}
|
|
|
|
MultiFilterSortModel *m = MultiFilterSortModel::instance();
|
|
|
|
for (int row: affectedTrips) {
|
|
|
|
QModelIndex idx = m->index(row, 0);
|
|
|
|
expand(idx);
|
Undo: select dives after add, remove, merge, split dive commands
Select the proper dives after the add, remove, split and merge
dives commands on undo *and* redo. Generally, select the added
dives. For undo of add, remember the pre-addition selection.
For redo of remove, select the closest dive to the first removed
dive.
The biggest part of the commit is the signal-interface between
the dive commands and the dive-list model and dive-list view.
This is done in two steps:
1) To the DiveTripModel in batches of trips. The dive trip model
transforms the dives into indices.
2) To the DiveListView. The DiveListView has to translate the
DiveTripModel indexes to actual indexes via its QSortFilterProxy-
model.
For code-reuse, derive all divelist-changing commands from a new base-class,
which has a flag that describes whether the divelist changed. The helper
functions which add and remove dives are made members of the base class and
set the flag is a selected dive is added or removed.
To properly detect when the current dive was deleted it
became necessary to turn the current dive from an index
to a pointer, because indices are not stable.
Unfortunately, in some cases an index was expected and these
places now have to transform the dive into an index. These
should be converted in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-03 09:35:43 +00:00
|
|
|
}
|
2019-07-03 18:51:24 +00:00
|
|
|
|
2019-11-27 23:14:14 +00:00
|
|
|
selectionChangeDone();
|
2020-04-26 16:44:59 +00:00
|
|
|
programmaticalSelectionChange = false;
|
Undo: select dives after add, remove, merge, split dive commands
Select the proper dives after the add, remove, split and merge
dives commands on undo *and* redo. Generally, select the added
dives. For undo of add, remember the pre-addition selection.
For redo of remove, select the closest dive to the first removed
dive.
The biggest part of the commit is the signal-interface between
the dive commands and the dive-list model and dive-list view.
This is done in two steps:
1) To the DiveTripModel in batches of trips. The dive trip model
transforms the dives into indices.
2) To the DiveListView. The DiveListView has to translate the
DiveTripModel indexes to actual indexes via its QSortFilterProxy-
model.
For code-reuse, derive all divelist-changing commands from a new base-class,
which has a flag that describes whether the divelist changed. The helper
functions which add and remove dives are made members of the base class and
set the flag is a selected dive is added or removed.
To properly detect when the current dive was deleted it
became necessary to turn the current dive from an index
to a pointer, because indices are not stable.
Unfortunately, in some cases an index was expected and these
places now have to transform the dive into an index. These
should be converted in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-03 09:35:43 +00:00
|
|
|
}
|
|
|
|
|
2018-08-14 13:12:21 +00:00
|
|
|
void DiveListView::currentDiveChanged(QModelIndex index)
|
|
|
|
{
|
2019-11-27 20:55:37 +00:00
|
|
|
// Set the currently activated row.
|
2018-08-14 13:12:21 +00:00
|
|
|
// Note, we have to use the QItemSelectionModel::Current mode to avoid
|
|
|
|
// changing our selection (in contrast to Qt's documentation, which
|
|
|
|
// instructs to use QItemSelectionModel::NoUpdate, which results in
|
|
|
|
// funny side-effects).
|
2019-11-27 20:55:37 +00:00
|
|
|
selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current);
|
2018-08-14 13:12:21 +00:00
|
|
|
}
|
|
|
|
|
2018-07-29 15:40:53 +00:00
|
|
|
// If rows are added, check which of these rows is a trip and expand the first column
|
|
|
|
void DiveListView::rowsInserted(const QModelIndex &parent, int start, int end)
|
|
|
|
{
|
|
|
|
// First, let the QTreeView do its thing.
|
|
|
|
QTreeView::rowsInserted(parent, start, end);
|
|
|
|
|
Undo: select dives after add, remove, merge, split dive commands
Select the proper dives after the add, remove, split and merge
dives commands on undo *and* redo. Generally, select the added
dives. For undo of add, remember the pre-addition selection.
For redo of remove, select the closest dive to the first removed
dive.
The biggest part of the commit is the signal-interface between
the dive commands and the dive-list model and dive-list view.
This is done in two steps:
1) To the DiveTripModel in batches of trips. The dive trip model
transforms the dives into indices.
2) To the DiveListView. The DiveListView has to translate the
DiveTripModel indexes to actual indexes via its QSortFilterProxy-
model.
For code-reuse, derive all divelist-changing commands from a new base-class,
which has a flag that describes whether the divelist changed. The helper
functions which add and remove dives are made members of the base class and
set the flag is a selected dive is added or removed.
To properly detect when the current dive was deleted it
became necessary to turn the current dive from an index
to a pointer, because indices are not stable.
Unfortunately, in some cases an index was expected and these
places now have to transform the dive into an index. These
should be converted in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-03 09:35:43 +00:00
|
|
|
// Check for each inserted row whether this is a trip and expand the first column
|
2018-08-01 08:47:09 +00:00
|
|
|
QAbstractItemModel *m = model();
|
2018-07-29 15:40:53 +00:00
|
|
|
if (parent.isValid()) // Trips don't have a parent
|
|
|
|
return;
|
|
|
|
for (int i = start; i <= end; ++i) {
|
|
|
|
if (m->rowCount(m->index(i, 0)) != 0)
|
|
|
|
setFirstColumnSpanned(i, QModelIndex(), true);
|
|
|
|
}
|
2013-06-12 17:14:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-24 21:26:08 +00:00
|
|
|
// This is a bit ugly: we hook directly into the tripChanged signal to
|
|
|
|
// select the trip if it was edited. This feels like a layering violation:
|
|
|
|
// Shouldn't the core-layer call us?
|
|
|
|
void DiveListView::tripChanged(dive_trip *trip, TripField)
|
|
|
|
{
|
2020-05-02 16:14:44 +00:00
|
|
|
// First check if the trip is already selected (and only this trip, as only then is it displayed).
|
|
|
|
if (single_selected_trip() == trip)
|
2019-02-24 21:26:08 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
unselectDives();
|
|
|
|
selectTrip(trip);
|
2019-11-27 23:14:14 +00:00
|
|
|
selectionChangeDone();
|
2019-02-24 21:26:08 +00:00
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
void DiveListView::selectTrip(dive_trip_t *trip)
|
2013-11-26 17:44:18 +00:00
|
|
|
{
|
|
|
|
if (!trip)
|
|
|
|
return;
|
|
|
|
|
2018-10-20 10:08:22 +00:00
|
|
|
QAbstractItemModel *m = model();
|
2018-12-27 09:06:11 +00:00
|
|
|
QModelIndexList match = m->match(m->index(0, 0), DiveTripModelBase::TRIP_ROLE, QVariant::fromValue(trip), 2, Qt::MatchRecursive);
|
2013-11-26 17:44:18 +00:00
|
|
|
QItemSelectionModel::SelectionFlags flags;
|
|
|
|
if (!match.count())
|
|
|
|
return;
|
|
|
|
QModelIndex idx = match.first();
|
2014-02-28 04:09:57 +00:00
|
|
|
flags = QItemSelectionModel::Select;
|
2013-11-26 17:44:18 +00:00
|
|
|
flags |= QItemSelectionModel::Rows;
|
|
|
|
selectionModel()->select(idx, flags);
|
|
|
|
expand(idx);
|
|
|
|
}
|
|
|
|
|
2013-06-05 06:41:52 +00:00
|
|
|
void DiveListView::unselectDives()
|
|
|
|
{
|
2020-05-02 12:41:52 +00:00
|
|
|
clear_selection();
|
2014-08-22 00:58:15 +00:00
|
|
|
// clear the Qt selection
|
2013-06-05 06:41:52 +00:00
|
|
|
selectionModel()->clearSelection();
|
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
bool DiveListView::eventFilter(QObject *, QEvent *event)
|
2013-05-30 04:04:42 +00:00
|
|
|
{
|
2014-01-16 04:50:56 +00:00
|
|
|
if (event->type() != QEvent::KeyPress)
|
2013-05-30 04:04:42 +00:00
|
|
|
return false;
|
2014-02-28 04:09:57 +00:00
|
|
|
QKeyEvent *keyEv = static_cast<QKeyEvent *>(event);
|
2015-02-06 11:19:04 +00:00
|
|
|
if (keyEv->key() == Qt::Key_Delete) {
|
|
|
|
contextMenuIndex = currentIndex();
|
|
|
|
deleteDive();
|
|
|
|
}
|
2013-10-11 13:21:04 +00:00
|
|
|
if (keyEv->key() != Qt::Key_Escape)
|
2013-05-30 04:04:42 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
2013-05-16 19:00:33 +00:00
|
|
|
}
|
|
|
|
|
2018-11-04 16:54:38 +00:00
|
|
|
void DiveListView::sortIndicatorChanged(int i, Qt::SortOrder order)
|
2013-05-28 20:07:43 +00:00
|
|
|
{
|
2018-12-27 09:06:11 +00:00
|
|
|
DiveTripModelBase::Layout newLayout = i == (int)DiveTripModelBase::NR ? DiveTripModelBase::TREE : DiveTripModelBase::LIST;
|
2013-05-31 04:53:04 +00:00
|
|
|
/* No layout change? Just re-sort, and scroll to first selection, making sure all selections are expanded */
|
|
|
|
if (currentLayout == newLayout) {
|
2018-11-04 16:54:38 +00:00
|
|
|
sortByColumn(i, order);
|
2013-05-31 04:53:04 +00:00
|
|
|
} else {
|
2020-03-11 10:30:51 +00:00
|
|
|
// clear the model, repopulate with new indices.
|
2019-11-28 20:55:26 +00:00
|
|
|
std::vector<int> expandedRows;
|
|
|
|
if(currentLayout == DiveTripModelBase::TREE)
|
|
|
|
expandedRows = backupExpandedRows();
|
2018-10-29 14:14:29 +00:00
|
|
|
currentLayout = newLayout;
|
2018-12-27 09:06:11 +00:00
|
|
|
resetModel();
|
2018-11-04 16:54:38 +00:00
|
|
|
sortByColumn(i, order);
|
2018-12-27 09:06:11 +00:00
|
|
|
if (newLayout == DiveTripModelBase::TREE)
|
2019-11-28 20:55:26 +00:00
|
|
|
restoreExpandedRows(expandedRows);
|
2013-05-31 04:53:04 +00:00
|
|
|
}
|
2013-05-28 20:07:43 +00:00
|
|
|
}
|
|
|
|
|
2018-11-04 16:54:38 +00:00
|
|
|
void DiveListView::setSortOrder(int i, Qt::SortOrder order)
|
2013-05-16 19:00:33 +00:00
|
|
|
{
|
2018-11-04 16:54:38 +00:00
|
|
|
// The QHeaderView will call our signal if the sort order changed
|
|
|
|
header()->setSortIndicator(i, order);
|
|
|
|
}
|
2015-05-24 15:47:07 +00:00
|
|
|
|
2018-11-04 16:54:38 +00:00
|
|
|
void DiveListView::reload()
|
|
|
|
{
|
2018-12-27 09:06:11 +00:00
|
|
|
resetModel();
|
2013-05-29 17:03:36 +00:00
|
|
|
|
2014-01-16 04:50:56 +00:00
|
|
|
if (selectedIndexes().count()) {
|
2013-11-15 14:01:56 +00:00
|
|
|
QModelIndex curr = selectedIndexes().first();
|
|
|
|
curr = curr.parent().isValid() ? curr.parent() : curr;
|
2014-01-16 04:50:56 +00:00
|
|
|
if (!isExpanded(curr)) {
|
2013-11-15 14:01:56 +00:00
|
|
|
setAnimated(false);
|
|
|
|
expand(curr);
|
When scrolling to dive, scroll to trip first
The behavior at startup is actually very annoying: we select the latest
dive, and expand the trip it is in, but since we use "scrollTo()" on
just the dive, and it's not initially visible, the startup will make the
first dive be at the top of the list view.
Which means that the actual _trip_ detail is not visible at all, since
it will have been scrolled off the list view entirely.
Fix this by first scrolling to the trip, and only then scrolling to the
actual dive (using the default "EnsureVisible" policy). Obviously, if
it's a trip with lots of dives, scrolling to the dive may end up
scrolling away from the trip header again, but at least that never
happens at startup, and at that point you have to scroll away from the
trip just to show the dive.
Do this same dance when changing the dive selection (mainly noticeable
when picking dives on the globe view).
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-04-28 17:39:06 +00:00
|
|
|
scrollTo(curr);
|
2013-11-15 14:01:56 +00:00
|
|
|
setAnimated(true);
|
|
|
|
}
|
|
|
|
}
|
2013-05-26 17:12:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::reloadHeaderActions()
|
|
|
|
{
|
2013-05-21 19:51:49 +00:00
|
|
|
// Populate the context menu of the headers that will show
|
|
|
|
// the menu to show / hide columns.
|
2013-05-23 04:25:05 +00:00
|
|
|
if (!header()->actions().size()) {
|
2013-05-21 19:51:49 +00:00
|
|
|
QSettings s;
|
|
|
|
s.beginGroup("DiveListColumnState");
|
2014-02-28 04:09:57 +00:00
|
|
|
for (int i = 0; i < model()->columnCount(); i++) {
|
2013-05-29 21:18:10 +00:00
|
|
|
QString title = QString("%1").arg(model()->headerData(i, Qt::Horizontal).toString());
|
2013-05-26 17:12:45 +00:00
|
|
|
QString settingName = QString("showColumn%1").arg(i);
|
2013-05-21 19:51:49 +00:00
|
|
|
QAction *a = new QAction(title, header());
|
2018-12-27 09:06:11 +00:00
|
|
|
bool showHeaderFirstRun = !(i == DiveTripModelBase::MAXCNS ||
|
|
|
|
i == DiveTripModelBase::GAS ||
|
|
|
|
i == DiveTripModelBase::OTU ||
|
|
|
|
i == DiveTripModelBase::TEMPERATURE ||
|
|
|
|
i == DiveTripModelBase::TOTALWEIGHT ||
|
|
|
|
i == DiveTripModelBase::SUIT ||
|
|
|
|
i == DiveTripModelBase::CYLINDER ||
|
|
|
|
i == DiveTripModelBase::SAC ||
|
|
|
|
i == DiveTripModelBase::TAGS);
|
2013-10-04 17:28:49 +00:00
|
|
|
bool shown = s.value(settingName, showHeaderFirstRun).toBool();
|
2013-05-21 19:51:49 +00:00
|
|
|
a->setCheckable(true);
|
2013-05-26 17:12:45 +00:00
|
|
|
a->setChecked(shown);
|
2013-05-21 19:51:49 +00:00
|
|
|
a->setProperty("index", i);
|
2013-05-26 17:12:45 +00:00
|
|
|
a->setProperty("settingName", settingName);
|
|
|
|
connect(a, SIGNAL(triggered(bool)), this, SLOT(toggleColumnVisibilityByIndex()));
|
2013-05-21 19:51:49 +00:00
|
|
|
header()->addAction(a);
|
2013-05-26 17:12:45 +00:00
|
|
|
setColumnHidden(i, !shown);
|
2013-05-21 19:51:49 +00:00
|
|
|
}
|
|
|
|
s.endGroup();
|
2013-06-09 23:09:39 +00:00
|
|
|
} else {
|
2014-02-28 04:09:57 +00:00
|
|
|
for (int i = 0; i < model()->columnCount(); i++) {
|
2013-06-09 23:09:39 +00:00
|
|
|
QString title = QString("%1").arg(model()->headerData(i, Qt::Horizontal).toString());
|
2013-12-04 16:20:33 +00:00
|
|
|
header()->actions()[i]->setText(title);
|
2013-06-09 23:09:39 +00:00
|
|
|
}
|
2013-05-21 19:51:49 +00:00
|
|
|
}
|
2013-05-14 01:14:59 +00:00
|
|
|
}
|
|
|
|
|
2013-05-26 17:12:45 +00:00
|
|
|
void DiveListView::toggleColumnVisibilityByIndex()
|
2013-05-14 01:14:59 +00:00
|
|
|
{
|
2014-02-28 04:09:57 +00:00
|
|
|
QAction *action = qobject_cast<QAction *>(sender());
|
2013-05-21 19:51:49 +00:00
|
|
|
if (!action)
|
|
|
|
return;
|
2018-08-26 20:46:46 +00:00
|
|
|
const int idx = action->property("index").toInt();
|
|
|
|
|
|
|
|
// Count the number of visible columns.
|
|
|
|
int totalVisible = 0, lastVisibleIdx = -1;
|
|
|
|
for (int i = 0; i < model()->columnCount(); i++) {
|
|
|
|
if (isColumnHidden(i))
|
|
|
|
continue;
|
|
|
|
totalVisible++;
|
|
|
|
lastVisibleIdx = i;
|
|
|
|
}
|
|
|
|
// If there is only one visible column and we are performing an action on it,
|
|
|
|
// don't hide the column and revert the action back to checked == true.
|
|
|
|
// This keeps one column visible at all times.
|
|
|
|
if (totalVisible == 1 && idx == lastVisibleIdx) {
|
|
|
|
action->setChecked(true);
|
|
|
|
return;
|
|
|
|
}
|
2013-05-21 19:51:49 +00:00
|
|
|
|
|
|
|
QSettings s;
|
|
|
|
s.beginGroup("DiveListColumnState");
|
2013-05-26 17:12:45 +00:00
|
|
|
s.setValue(action->property("settingName").toString(), action->isChecked());
|
2013-05-21 19:51:49 +00:00
|
|
|
s.endGroup();
|
2013-05-22 02:07:19 +00:00
|
|
|
s.sync();
|
2018-08-26 20:46:46 +00:00
|
|
|
setColumnHidden(idx, !action->isChecked());
|
2013-11-13 18:59:56 +00:00
|
|
|
setColumnWidth(lastVisibleColumn(), 10);
|
2013-05-14 01:14:59 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 16:09:09 +00:00
|
|
|
void DiveListView::currentChanged(const QModelIndex ¤t, const QModelIndex&)
|
2013-05-14 11:18:26 +00:00
|
|
|
{
|
2015-05-10 23:01:58 +00:00
|
|
|
if (!isVisible())
|
|
|
|
return;
|
2013-05-14 11:18:26 +00:00
|
|
|
if (!current.isValid())
|
|
|
|
return;
|
2013-06-05 07:46:23 +00:00
|
|
|
scrollTo(current);
|
2013-05-14 11:18:26 +00:00
|
|
|
}
|
|
|
|
|
2020-02-08 21:05:37 +00:00
|
|
|
void DiveListView::mouseReleaseEvent(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
// Oy vey. We hook into QTreeView's setSelection() to update the UI
|
|
|
|
// after selection changes. However, there is a case when this doesn't
|
|
|
|
// work: when narrowing the selection. If multiple dives are selected,
|
|
|
|
// and the user clicks on one of them, the selection is unchanged.
|
|
|
|
// Only on mouse-release the selection is changed, but then
|
|
|
|
// setSelection() is not called.
|
|
|
|
// Notably, this happens when the user selects a trip and the clicks
|
|
|
|
// on a dive in the same trip.
|
|
|
|
// To solve this, we hook into the mouseReleseEvent here and detect
|
|
|
|
// selection changes changes by comparing the selection before and after
|
|
|
|
// processing of the event.
|
|
|
|
// We really should find another way to solve this.
|
|
|
|
QModelIndexList selectionBefore = selectionModel()->selectedRows();
|
|
|
|
QTreeView::mouseReleaseEvent(event);
|
|
|
|
QModelIndexList selectionAfter = selectionModel()->selectedRows();
|
2020-02-28 21:11:12 +00:00
|
|
|
if (selectionBefore != selectionAfter)
|
|
|
|
selectionChangeDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::keyPressEvent(QKeyEvent *event)
|
|
|
|
{
|
|
|
|
// Hook into cursor-up and cursor-down events and update selection if necessary.
|
|
|
|
// See comment in mouseReleaseEvent()
|
|
|
|
if (event->key() != Qt::Key_Down && event->key() != Qt::Key_Up)
|
|
|
|
return QTreeView::keyPressEvent(event);
|
|
|
|
QModelIndexList selectionBefore = selectionModel()->selectedRows();
|
|
|
|
QTreeView::keyPressEvent(event);
|
|
|
|
QModelIndexList selectionAfter = selectionModel()->selectedRows();
|
2020-02-08 21:05:37 +00:00
|
|
|
if (selectionBefore != selectionAfter)
|
|
|
|
selectionChangeDone();
|
|
|
|
}
|
|
|
|
|
2019-11-27 23:14:14 +00:00
|
|
|
void DiveListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags)
|
|
|
|
{
|
2020-02-08 21:05:37 +00:00
|
|
|
// We hook into QTreeView's setSelection() to update the UI
|
|
|
|
// after selection changes. However, we must be careful:
|
|
|
|
// when the user clicks on an already selected dive, this is called
|
|
|
|
// with the QItemSelectionModel::NoUpdate flags. In this case, just
|
|
|
|
// route through. Moreover, we get setSelection() calls on every
|
|
|
|
// mouseMove event. To avoid excessive reloading of the UI check
|
|
|
|
// if the selection changed by comparing before and after processing
|
|
|
|
// the selection.
|
|
|
|
if (flags == QItemSelectionModel::NoUpdate)
|
|
|
|
return QTreeView::setSelection(rect, flags);
|
|
|
|
QModelIndexList selectionBefore = selectionModel()->selectedRows();
|
2019-11-27 23:14:14 +00:00
|
|
|
QTreeView::setSelection(rect, flags);
|
2020-02-08 21:05:37 +00:00
|
|
|
QModelIndexList selectionAfter = selectionModel()->selectedRows();
|
|
|
|
if (selectionBefore != selectionAfter)
|
|
|
|
selectionChangeDone();
|
2019-11-27 23:14:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::selectAll()
|
|
|
|
{
|
|
|
|
QTreeView::selectAll();
|
|
|
|
selectionChangeDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::selectionChangeDone()
|
|
|
|
{
|
|
|
|
// When receiving the divesSelected signal the main window will
|
|
|
|
// instruct the map to update the flags. Thus, make sure that
|
|
|
|
// the selected maps are registered correctly.
|
|
|
|
// But don't do this if we are in divesite mode, because then
|
|
|
|
// the dive-site selection is controlled by the filter not
|
|
|
|
// by the selected dives.
|
|
|
|
if (!DiveFilter::instance()->diveSiteMode()) {
|
2020-04-26 17:34:08 +00:00
|
|
|
// This is truly sad, but taking the list of selected indices and turning them
|
|
|
|
// into dive sites turned out to be unreasonably slow. Therefore, let's access
|
|
|
|
// the core list directly. In my tests, this went down from 700 to 0 ms!
|
2019-11-27 23:14:14 +00:00
|
|
|
QVector<dive_site *> selectedSites;
|
2020-04-26 17:34:08 +00:00
|
|
|
selectedSites.reserve(amount_selected);
|
|
|
|
int i;
|
|
|
|
dive *d;
|
|
|
|
for_each_dive(i, d) {
|
|
|
|
if (d->selected && !d->hidden_by_filter && d->dive_site && !selectedSites.contains(d->dive_site))
|
|
|
|
selectedSites.push_back(d->dive_site);
|
2019-11-27 23:14:14 +00:00
|
|
|
}
|
|
|
|
MapWidget::instance()->setSelected(selectedSites);
|
|
|
|
}
|
|
|
|
emit divesSelected();
|
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
void DiveListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
|
2013-05-14 01:14:59 +00:00
|
|
|
{
|
2020-04-26 16:44:59 +00:00
|
|
|
if (programmaticalSelectionChange) {
|
Dive list view: replace signal-magic by flag
In DiveListView, we have a very fundamental problem: When
On the one hand, we get informed of user-selection in the
DiveListView::selectionChanged() slot. This has to set the
correct flags in the C-backend.
On the other hand, sometimes we have to set the selection
programatically, e.g. when selecting a trip. This is done
by calling QItemSelectionModel::select().
But: this will *also* call into the above slot, in which
we can't tell whether it was a user interaction or an
internal call. This can lead to either infinite loops or
very inefficient behavior, because the current dive
is set numerous times.
The current code is aware of that and disconnects the
corresponding signal. This is scary, as these signals are
set internally by the model and view. Replace this
by a global "command executing" flag in DiveListNotifier.
The flag is set using a "marker" class, which resets the flag
once it goes out of scope (cf. RAII pattern).
In DiveListView, only process a selection if the flag is not
set. Otherwise simply call the QTreeView base class, to reflect
the new selection in the UI.
To have a common point for notifications of selection changes,
add such a signal to DiveListNotifier. This signal will be
used by the DiveListView as well as the Command-objects.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-01 17:23:43 +00:00
|
|
|
// This is a programmatical change of the selection.
|
|
|
|
// Call the QTreeView base function to reflect the selection in the display,
|
|
|
|
// but don't process it any further.
|
|
|
|
QTreeView::selectionChanged(selected, deselected);
|
|
|
|
return;
|
|
|
|
}
|
2013-05-29 17:03:36 +00:00
|
|
|
|
Dive list view: replace signal-magic by flag
In DiveListView, we have a very fundamental problem: When
On the one hand, we get informed of user-selection in the
DiveListView::selectionChanged() slot. This has to set the
correct flags in the C-backend.
On the other hand, sometimes we have to set the selection
programatically, e.g. when selecting a trip. This is done
by calling QItemSelectionModel::select().
But: this will *also* call into the above slot, in which
we can't tell whether it was a user interaction or an
internal call. This can lead to either infinite loops or
very inefficient behavior, because the current dive
is set numerous times.
The current code is aware of that and disconnects the
corresponding signal. This is scary, as these signals are
set internally by the model and view. Replace this
by a global "command executing" flag in DiveListNotifier.
The flag is set using a "marker" class, which resets the flag
once it goes out of scope (cf. RAII pattern).
In DiveListView, only process a selection if the flag is not
set. Otherwise simply call the QTreeView base class, to reflect
the new selection in the UI.
To have a common point for notifications of selection changes,
add such a signal to DiveListNotifier. This signal will be
used by the DiveListView as well as the Command-objects.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-01 17:23:43 +00:00
|
|
|
// This is a manual selection change. This means that the core does not yet know
|
|
|
|
// of the new selection. Update the core-structures accordingly and select/deselect
|
|
|
|
// all dives of a trip if a trip is selected/deselected.
|
|
|
|
const QItemSelection &newDeselected = deselected;
|
|
|
|
|
|
|
|
QItemSelection newSelected = selected.size() ? selected : selectionModel()->selection();
|
2013-05-29 17:03:36 +00:00
|
|
|
|
2014-05-22 18:40:22 +00:00
|
|
|
Q_FOREACH (const QModelIndex &index, newDeselected.indexes()) {
|
2013-05-31 12:05:33 +00:00
|
|
|
if (index.column() != 0)
|
2013-05-29 17:03:36 +00:00
|
|
|
continue;
|
2013-05-14 01:14:59 +00:00
|
|
|
const QAbstractItemModel *model = index.model();
|
2018-12-27 09:06:11 +00:00
|
|
|
struct dive *dive = model->data(index, DiveTripModelBase::DIVE_ROLE).value<struct dive *>();
|
2020-05-02 15:57:31 +00:00
|
|
|
if (!dive) { // it's a trip!
|
|
|
|
dive_trip *trip = model->data(index, DiveTripModelBase::TRIP_ROLE).value<dive_trip *>();
|
|
|
|
deselect_trip(trip);
|
|
|
|
deselect_dives_in_trip(trip);
|
|
|
|
} else {
|
Undo: select dives after add, remove, merge, split dive commands
Select the proper dives after the add, remove, split and merge
dives commands on undo *and* redo. Generally, select the added
dives. For undo of add, remember the pre-addition selection.
For redo of remove, select the closest dive to the first removed
dive.
The biggest part of the commit is the signal-interface between
the dive commands and the dive-list model and dive-list view.
This is done in two steps:
1) To the DiveTripModel in batches of trips. The dive trip model
transforms the dives into indices.
2) To the DiveListView. The DiveListView has to translate the
DiveTripModel indexes to actual indexes via its QSortFilterProxy-
model.
For code-reuse, derive all divelist-changing commands from a new base-class,
which has a flag that describes whether the divelist changed. The helper
functions which add and remove dives are made members of the base class and
set the flag is a selected dive is added or removed.
To properly detect when the current dive was deleted it
became necessary to turn the current dive from an index
to a pointer, because indices are not stable.
Unfortunately, in some cases an index was expected and these
places now have to transform the dive into an index. These
should be converted in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-03 09:35:43 +00:00
|
|
|
deselect_dive(dive);
|
2020-05-02 15:57:31 +00:00
|
|
|
}
|
2013-05-31 12:05:33 +00:00
|
|
|
}
|
2014-05-22 18:40:22 +00:00
|
|
|
Q_FOREACH (const QModelIndex &index, newSelected.indexes()) {
|
2013-05-31 12:05:33 +00:00
|
|
|
if (index.column() != 0)
|
|
|
|
continue;
|
2013-06-14 13:17:38 +00:00
|
|
|
|
2013-05-31 12:05:33 +00:00
|
|
|
const QAbstractItemModel *model = index.model();
|
2018-12-27 09:06:11 +00:00
|
|
|
struct dive *dive = model->data(index, DiveTripModelBase::DIVE_ROLE).value<struct dive *>();
|
2013-05-31 12:05:33 +00:00
|
|
|
if (!dive) { // it's a trip!
|
2020-05-02 15:57:31 +00:00
|
|
|
dive_trip *trip = model->data(index, DiveTripModelBase::TRIP_ROLE).value<dive_trip *>();
|
|
|
|
select_trip(trip);
|
|
|
|
select_dives_in_trip(trip);
|
2013-05-31 12:05:33 +00:00
|
|
|
if (model->rowCount(index)) {
|
2013-06-14 13:17:38 +00:00
|
|
|
QItemSelection selection;
|
2014-02-28 04:09:57 +00:00
|
|
|
selection.select(index.child(0, 0), index.child(model->rowCount(index) - 1, 0));
|
2013-06-14 13:17:38 +00:00
|
|
|
selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
|
|
|
selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::NoUpdate);
|
|
|
|
if (!isExpanded(index))
|
|
|
|
expand(index);
|
2013-05-31 12:05:33 +00:00
|
|
|
}
|
|
|
|
} else {
|
Undo: select dives after add, remove, merge, split dive commands
Select the proper dives after the add, remove, split and merge
dives commands on undo *and* redo. Generally, select the added
dives. For undo of add, remember the pre-addition selection.
For redo of remove, select the closest dive to the first removed
dive.
The biggest part of the commit is the signal-interface between
the dive commands and the dive-list model and dive-list view.
This is done in two steps:
1) To the DiveTripModel in batches of trips. The dive trip model
transforms the dives into indices.
2) To the DiveListView. The DiveListView has to translate the
DiveTripModel indexes to actual indexes via its QSortFilterProxy-
model.
For code-reuse, derive all divelist-changing commands from a new base-class,
which has a flag that describes whether the divelist changed. The helper
functions which add and remove dives are made members of the base class and
set the flag is a selected dive is added or removed.
To properly detect when the current dive was deleted it
became necessary to turn the current dive from an index
to a pointer, because indices are not stable.
Unfortunately, in some cases an index was expected and these
places now have to transform the dive into an index. These
should be converted in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-03 09:35:43 +00:00
|
|
|
select_dive(dive);
|
2013-05-14 01:14:59 +00:00
|
|
|
}
|
|
|
|
}
|
Dive list view: replace signal-magic by flag
In DiveListView, we have a very fundamental problem: When
On the one hand, we get informed of user-selection in the
DiveListView::selectionChanged() slot. This has to set the
correct flags in the C-backend.
On the other hand, sometimes we have to set the selection
programatically, e.g. when selecting a trip. This is done
by calling QItemSelectionModel::select().
But: this will *also* call into the above slot, in which
we can't tell whether it was a user interaction or an
internal call. This can lead to either infinite loops or
very inefficient behavior, because the current dive
is set numerous times.
The current code is aware of that and disconnects the
corresponding signal. This is scary, as these signals are
set internally by the model and view. Replace this
by a global "command executing" flag in DiveListNotifier.
The flag is set using a "marker" class, which resets the flag
once it goes out of scope (cf. RAII pattern).
In DiveListView, only process a selection if the flag is not
set. Otherwise simply call the QTreeView base class, to reflect
the new selection in the UI.
To have a common point for notifications of selection changes,
add such a signal to DiveListNotifier. This signal will be
used by the DiveListView as well as the Command-objects.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-08-01 17:23:43 +00:00
|
|
|
|
|
|
|
// Display the new, processed, selection
|
|
|
|
QTreeView::selectionChanged(selectionModel()->selection(), newDeselected);
|
2013-04-12 07:24:07 +00:00
|
|
|
}
|
2013-06-05 18:29:11 +00:00
|
|
|
|
2015-05-18 08:52:03 +00:00
|
|
|
enum asked_user {NOTYET, MERGE, DONTMERGE};
|
|
|
|
|
|
|
|
static bool can_merge(const struct dive *a, const struct dive *b, enum asked_user *have_asked)
|
2013-11-16 22:46:08 +00:00
|
|
|
{
|
|
|
|
if (!a || !b)
|
|
|
|
return false;
|
|
|
|
if (a->when > b->when)
|
|
|
|
return false;
|
|
|
|
/* Don't merge dives if there's more than half an hour between them */
|
2015-09-22 19:32:27 +00:00
|
|
|
if (dive_endtime(a) + 30 * 60 < b->when) {
|
2015-05-18 08:52:03 +00:00
|
|
|
if (*have_asked == NOTYET) {
|
|
|
|
if (QMessageBox::warning(MainWindow::instance(),
|
2017-04-29 20:54:13 +00:00
|
|
|
MainWindow::tr("Warning"),
|
|
|
|
MainWindow::tr("Trying to merge dives with %1min interval in between").arg(
|
2015-09-22 19:32:27 +00:00
|
|
|
(b->when - dive_endtime(a)) / 60),
|
2015-05-18 08:52:03 +00:00
|
|
|
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
|
|
|
|
*have_asked = DONTMERGE;
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
*have_asked = MERGE;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return *have_asked == MERGE ? true : false;
|
|
|
|
}
|
|
|
|
}
|
2013-11-16 22:46:08 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-09-24 04:57:28 +00:00
|
|
|
void DiveListView::mergeDives()
|
|
|
|
{
|
|
|
|
int i;
|
2018-07-21 16:28:33 +00:00
|
|
|
struct dive *d;
|
2015-05-18 08:52:03 +00:00
|
|
|
enum asked_user have_asked = NOTYET;
|
2013-09-24 04:57:28 +00:00
|
|
|
|
2018-07-21 16:28:33 +00:00
|
|
|
// Collect a vector of batches of dives to merge (i.e. a vector of vector of dives)
|
|
|
|
QVector<QVector<dive *>> merge_batches;
|
|
|
|
QVector<dive *> current_batch;
|
|
|
|
for_each_dive (i, d) {
|
|
|
|
if (!d->selected)
|
|
|
|
continue;
|
|
|
|
if (current_batch.empty()) {
|
|
|
|
current_batch.append(d);
|
|
|
|
} else if (can_merge(current_batch.back(), d, &have_asked)) {
|
|
|
|
current_batch.append(d);
|
|
|
|
} else {
|
|
|
|
if (current_batch.count() > 1)
|
|
|
|
merge_batches.append(current_batch);
|
|
|
|
current_batch.clear();
|
2013-10-15 11:37:31 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-21 16:28:33 +00:00
|
|
|
if (current_batch.count() > 1)
|
|
|
|
merge_batches.append(current_batch);
|
|
|
|
|
2018-07-23 21:41:23 +00:00
|
|
|
for (const QVector<dive *> &batch: merge_batches)
|
|
|
|
Command::mergeDives(batch);
|
2013-09-24 04:57:28 +00:00
|
|
|
}
|
|
|
|
|
2015-10-02 01:17:37 +00:00
|
|
|
void DiveListView::splitDives()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct dive *dive;
|
|
|
|
|
2018-07-20 18:26:06 +00:00
|
|
|
// Let's collect the dives to be split first, so that we don't catch newly inserted dives!
|
|
|
|
QVector<struct dive *> dives;
|
2015-10-02 01:17:37 +00:00
|
|
|
for_each_dive (i, dive) {
|
|
|
|
if (dive->selected)
|
2018-07-20 18:26:06 +00:00
|
|
|
dives.append(dive);
|
|
|
|
}
|
2018-07-23 21:41:23 +00:00
|
|
|
for (struct dive *d: dives)
|
|
|
|
Command::splitDives(d, duration_t{-1});
|
2015-10-02 01:17:37 +00:00
|
|
|
}
|
|
|
|
|
2014-05-19 21:11:32 +00:00
|
|
|
void DiveListView::renumberDives()
|
|
|
|
{
|
2020-05-27 12:00:47 +00:00
|
|
|
RenumberDialog dialog(true, MainWindow::instance());
|
|
|
|
dialog.exec();
|
2014-05-19 21:11:32 +00:00
|
|
|
}
|
|
|
|
|
2013-06-27 05:16:40 +00:00
|
|
|
void DiveListView::merge_trip(const QModelIndex &a, int offset)
|
|
|
|
{
|
|
|
|
int i = a.row() + offset;
|
2014-02-28 04:09:57 +00:00
|
|
|
QModelIndex b = a.sibling(i, 0);
|
2013-06-27 05:16:40 +00:00
|
|
|
|
2018-12-27 09:06:11 +00:00
|
|
|
dive_trip_t *trip_a = a.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>();
|
|
|
|
dive_trip_t *trip_b = b.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>();
|
2013-06-27 05:16:40 +00:00
|
|
|
if (trip_a == trip_b || !trip_a || !trip_b)
|
|
|
|
return;
|
2018-07-23 21:41:23 +00:00
|
|
|
Command::mergeTrips(trip_a, trip_b);
|
2013-06-27 05:16:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::mergeTripAbove()
|
|
|
|
{
|
|
|
|
merge_trip(contextMenuIndex, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::mergeTripBelow()
|
|
|
|
{
|
|
|
|
merge_trip(contextMenuIndex, +1);
|
|
|
|
}
|
|
|
|
|
2013-06-06 00:06:42 +00:00
|
|
|
void DiveListView::removeFromTrip()
|
2013-06-05 18:29:11 +00:00
|
|
|
{
|
2014-04-17 02:56:42 +00:00
|
|
|
//TODO: move this to C-code.
|
2013-11-15 05:11:55 +00:00
|
|
|
int i;
|
2013-12-08 05:05:54 +00:00
|
|
|
struct dive *d;
|
2018-09-23 19:22:48 +00:00
|
|
|
QVector<dive *> divesToRemove;
|
2014-05-22 18:40:22 +00:00
|
|
|
for_each_dive (i, d) {
|
2018-08-29 18:30:01 +00:00
|
|
|
if (d->selected && d->divetrip)
|
Undo: fix multi-level undo of delete-dive and remove-dive-from-trip
The original undo-code was fundamentally broken. Not only did it leak
resources (copied trips were never freed), it also kept references
to trips or dives that could be changed by other commands. Thus,
anything more than a single undo could lead to crashes.
Two ways of fixing this were considered
1) Don't store pointers, but unique dive-ids and trip-ids.
Whereas such unique ids exist for dives, they would have to be
implemented for trips.
2) Don't free objects in the backend.
Instead, take ownership of deleted objects in the undo-object.
Thus, all references in previous undo-objects are guaranteed to
still exist (unless the objects are deleted elsewhere).
After some contemplation, the second method was chosen, because
it is significantly less intrusive. While touching the undo-objects,
clearly separate backend from ui-code, such that they can ultimately
be reused for mobile.
Note that if other parts of the code delete dives, crashes can still
be provoked. Notable examples are split/merge dives. These will have
to be fixed later. Nevertheless, the new code is a significant
improvement over the old state.
While touching the code, implement proper translation string based
on Qt's plural-feature (using %n).
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-07-19 12:44:27 +00:00
|
|
|
divesToRemove.append(d);
|
2013-11-15 05:11:55 +00:00
|
|
|
}
|
2018-07-23 21:41:23 +00:00
|
|
|
Command::removeDivesFromTrip(divesToRemove);
|
2013-06-06 01:37:18 +00:00
|
|
|
}
|
|
|
|
|
2013-11-15 02:32:19 +00:00
|
|
|
void DiveListView::newTripAbove()
|
|
|
|
{
|
2018-12-27 09:06:11 +00:00
|
|
|
struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>();
|
2013-11-15 02:32:19 +00:00
|
|
|
if (!d) // shouldn't happen as we only are setting up this action if this is a dive
|
|
|
|
return;
|
2014-04-17 02:56:42 +00:00
|
|
|
//TODO: port to c-code.
|
|
|
|
int idx;
|
2018-09-23 19:22:48 +00:00
|
|
|
QVector<dive *> dives;
|
2014-05-22 18:40:22 +00:00
|
|
|
for_each_dive (idx, d) {
|
2013-11-15 05:11:55 +00:00
|
|
|
if (d->selected)
|
2018-09-23 19:22:48 +00:00
|
|
|
dives.append(d);
|
2013-11-15 02:32:19 +00:00
|
|
|
}
|
2018-07-23 21:41:23 +00:00
|
|
|
Command::createTrip(dives);
|
2013-11-17 23:36:57 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 06:42:20 +00:00
|
|
|
void DiveListView::addToTripBelow()
|
|
|
|
{
|
2014-08-21 19:20:03 +00:00
|
|
|
addToTrip(1);
|
2014-03-20 06:42:20 +00:00
|
|
|
}
|
|
|
|
|
2013-11-17 23:36:57 +00:00
|
|
|
void DiveListView::addToTripAbove()
|
2014-03-20 06:42:20 +00:00
|
|
|
{
|
2014-08-21 19:20:03 +00:00
|
|
|
addToTrip(-1);
|
2014-03-20 06:42:20 +00:00
|
|
|
}
|
|
|
|
|
2014-08-21 19:20:03 +00:00
|
|
|
void DiveListView::addToTrip(int delta)
|
2013-11-17 23:36:57 +00:00
|
|
|
{
|
2017-10-22 12:43:55 +00:00
|
|
|
// d points to the row that has (mouse-)pointer focus, and there are nr rows selected
|
2018-12-27 09:06:11 +00:00
|
|
|
struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>();
|
2017-10-22 12:43:55 +00:00
|
|
|
int nr = selectionModel()->selectedRows().count();
|
|
|
|
QModelIndex t;
|
|
|
|
dive_trip_t *trip = NULL;
|
|
|
|
|
|
|
|
// now look for the trip to add to, for this, loop over the selected dives and
|
|
|
|
// check if its sibling is a trip.
|
|
|
|
for (int i = 1; i <= nr; i++) {
|
|
|
|
t = contextMenuIndex.sibling(contextMenuIndex.row() + (delta > 0 ? i: i * -1), 0);
|
2018-12-27 09:06:11 +00:00
|
|
|
trip = t.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>();
|
2017-10-22 12:43:55 +00:00
|
|
|
if (trip)
|
|
|
|
break;
|
|
|
|
}
|
2014-04-17 02:56:42 +00:00
|
|
|
|
2014-08-21 19:20:03 +00:00
|
|
|
if (!trip || !d)
|
|
|
|
// no dive, no trip? get me out of here
|
2013-11-17 23:36:57 +00:00
|
|
|
return;
|
2014-08-21 19:20:03 +00:00
|
|
|
|
2018-09-23 19:22:48 +00:00
|
|
|
QVector<dive *> dives;
|
2013-11-17 23:36:57 +00:00
|
|
|
if (d->selected) { // there are possibly other selected dives that we should add
|
2014-08-21 19:20:03 +00:00
|
|
|
int idx;
|
2014-05-22 18:40:22 +00:00
|
|
|
for_each_dive (idx, d) {
|
2013-11-17 23:36:57 +00:00
|
|
|
if (d->selected)
|
2018-09-23 19:22:48 +00:00
|
|
|
dives.append(d);
|
2013-11-17 23:36:57 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-23 21:41:23 +00:00
|
|
|
Command::addDivesToTrip(dives, trip);
|
2013-11-15 02:32:19 +00:00
|
|
|
}
|
|
|
|
|
2013-12-07 01:40:59 +00:00
|
|
|
void DiveListView::markDiveInvalid()
|
|
|
|
{
|
2019-12-12 22:07:17 +00:00
|
|
|
Command::editInvalid(true, false);
|
2013-12-07 01:40:59 +00:00
|
|
|
}
|
|
|
|
|
2019-12-13 05:33:12 +00:00
|
|
|
void DiveListView::markDiveValid()
|
|
|
|
{
|
|
|
|
Command::editInvalid(false, false);
|
|
|
|
}
|
|
|
|
|
2013-06-06 01:37:18 +00:00
|
|
|
void DiveListView::deleteDive()
|
|
|
|
{
|
2018-12-27 09:06:11 +00:00
|
|
|
struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>();
|
2013-11-15 05:11:55 +00:00
|
|
|
if (!d)
|
|
|
|
return;
|
2014-04-17 02:56:42 +00:00
|
|
|
|
|
|
|
int i;
|
2018-07-23 21:41:23 +00:00
|
|
|
QVector<struct dive*> deletedDives;
|
2014-05-19 21:37:19 +00:00
|
|
|
for_each_dive (i, d) {
|
Dive list: implement proper Qt-model semantics for DiveTripModel
Previously, each dive-list modifying function would lead to a
full model reset. Instead, implement proper Qt-model semantics
using beginInsertRows()/endInsertRows(), beginRemoveRows()/
endRemoveRows(), dataChange().
To do so, a DiveListNotifer singleton is generatated, which
broadcasts all changes to the dive-list. Signals are sent by
the commands and received by the DiveTripModel. Signals are
batched by dive-trip. This seems to be an adequate compromise
for the two kinds of list-views (tree and list). In the common
usecase mostly dives of a single trip are affected.
Thus, batching of dives is performed in two positions:
- At command-level to batch by trip
- In DiveTripModel to feed batches of contiguous elements
to Qt's begin*/end*-functions.
This is conceptually simple, but rather complex code. To avoid
repetition of complex loops, the batching is implemented in
templated-functions, which are passed lambda-functions, which
are called for each batch.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-07-30 07:20:25 +00:00
|
|
|
if (d->selected)
|
|
|
|
deletedDives.append(d);
|
2013-11-16 20:41:47 +00:00
|
|
|
}
|
2018-07-23 21:41:23 +00:00
|
|
|
Command::deleteDive(deletedDives);
|
2013-06-06 00:06:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::contextMenuEvent(QContextMenuEvent *event)
|
|
|
|
{
|
|
|
|
QAction *collapseAction = NULL;
|
|
|
|
// let's remember where we are
|
|
|
|
contextMenuIndex = indexAt(event->pos());
|
2018-12-27 09:06:11 +00:00
|
|
|
struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>();
|
|
|
|
dive_trip_t *trip = contextMenuIndex.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>();
|
2013-06-05 18:29:11 +00:00
|
|
|
QMenu popup(this);
|
2018-12-27 09:06:11 +00:00
|
|
|
if (currentLayout == DiveTripModelBase::TREE) {
|
2015-05-10 20:08:24 +00:00
|
|
|
// verify if there is a node that`s not expanded.
|
|
|
|
bool needs_expand = false;
|
|
|
|
bool needs_collapse = false;
|
2015-05-10 20:19:34 +00:00
|
|
|
uint expanded_nodes = 0;
|
2015-05-10 20:08:24 +00:00
|
|
|
for(int i = 0, end = model()->rowCount(); i < end; i++) {
|
|
|
|
QModelIndex idx = model()->index(i, 0);
|
2018-12-27 09:06:11 +00:00
|
|
|
if (idx.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>())
|
2015-05-10 20:08:24 +00:00
|
|
|
continue;
|
|
|
|
|
2015-05-10 20:19:34 +00:00
|
|
|
if (!isExpanded(idx)) {
|
2015-05-10 20:08:24 +00:00
|
|
|
needs_expand = true;
|
2015-05-10 20:19:34 +00:00
|
|
|
} else {
|
2015-05-10 20:08:24 +00:00
|
|
|
needs_collapse = true;
|
2015-05-10 20:19:34 +00:00
|
|
|
expanded_nodes ++;
|
|
|
|
}
|
2015-05-10 20:08:24 +00:00
|
|
|
}
|
|
|
|
if (needs_expand)
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Expand all"), this, &QTreeView::expandAll);
|
2015-05-10 20:08:24 +00:00
|
|
|
if (needs_collapse)
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Collapse all"), this, &QTreeView::collapseAll);
|
2015-05-10 20:08:24 +00:00
|
|
|
|
2015-05-10 20:19:34 +00:00
|
|
|
// verify if there`s a need for collapse others
|
|
|
|
if (expanded_nodes > 1)
|
2019-10-14 18:57:13 +00:00
|
|
|
collapseAction = popup.addAction(tr("Collapse others"), this, &QTreeView::collapseAll);
|
2015-05-10 20:19:34 +00:00
|
|
|
|
|
|
|
|
2013-06-06 00:06:42 +00:00
|
|
|
if (d) {
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Remove dive(s) from trip"), this, &DiveListView::removeFromTrip);
|
|
|
|
popup.addAction(tr("Create new trip above"), this, &DiveListView::newTripAbove);
|
2014-05-24 15:27:42 +00:00
|
|
|
if (!d->divetrip) {
|
2014-05-24 17:28:11 +00:00
|
|
|
struct dive *top = d;
|
|
|
|
struct dive *bottom = d;
|
2018-11-04 17:11:30 +00:00
|
|
|
Qt::SortOrder currentOrder = header()->sortIndicatorOrder();
|
2014-05-24 17:28:11 +00:00
|
|
|
if (d->selected) {
|
|
|
|
if (currentOrder == Qt::AscendingOrder) {
|
|
|
|
top = first_selected_dive();
|
|
|
|
bottom = last_selected_dive();
|
|
|
|
} else {
|
|
|
|
top = last_selected_dive();
|
|
|
|
bottom = first_selected_dive();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (is_trip_before_after(top, (currentOrder == Qt::AscendingOrder)))
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Add dive(s) to trip immediately above"), this, &DiveListView::addToTripAbove);
|
2014-05-24 17:28:11 +00:00
|
|
|
if (is_trip_before_after(bottom, (currentOrder == Qt::DescendingOrder)))
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Add dive(s) to trip immediately below"), this, &DiveListView::addToTripBelow);
|
2014-05-24 15:27:42 +00:00
|
|
|
}
|
2013-06-06 00:06:42 +00:00
|
|
|
}
|
2013-06-27 05:16:40 +00:00
|
|
|
if (trip) {
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Merge trip with trip above"), this, &DiveListView::mergeTripAbove);
|
|
|
|
popup.addAction(tr("Merge trip with trip below"), this, &DiveListView::mergeTripBelow);
|
2013-06-27 05:16:40 +00:00
|
|
|
}
|
2013-06-05 21:51:15 +00:00
|
|
|
}
|
2013-12-07 01:40:59 +00:00
|
|
|
if (d) {
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Delete dive(s)"), this, &DiveListView::deleteDive);
|
2019-12-13 05:33:12 +00:00
|
|
|
if (d->invalid)
|
|
|
|
popup.addAction(tr("Mark dive(s) valid"), this, &DiveListView::markDiveValid);
|
|
|
|
else
|
|
|
|
popup.addAction(tr("Mark dive(s) invalid"), this, &DiveListView::markDiveInvalid);
|
2013-12-07 01:40:59 +00:00
|
|
|
}
|
2013-09-24 10:42:40 +00:00
|
|
|
if (amount_selected > 1 && consecutive_selected())
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Merge selected dives"), this, &DiveListView::mergeDives);
|
2013-10-19 17:35:36 +00:00
|
|
|
if (amount_selected >= 1) {
|
2019-10-14 18:57:13 +00:00
|
|
|
popup.addAction(tr("Renumber dive(s)"), this, &DiveListView::renumberDives);
|
|
|
|
popup.addAction(tr("Shift dive times"), this, &DiveListView::shiftTimes);
|
|
|
|
popup.addAction(tr("Split selected dives"), this, &DiveListView::splitDives);
|
|
|
|
popup.addAction(tr("Load media from file(s)"), this, &DiveListView::loadImages);
|
|
|
|
popup.addAction(tr("Load media from web"), this, &DiveListView::loadWebImages);
|
2013-10-19 17:35:36 +00:00
|
|
|
}
|
2014-12-24 15:23:31 +00:00
|
|
|
|
2013-06-05 21:39:54 +00:00
|
|
|
// "collapse all" really closes all trips,
|
2013-06-05 21:51:15 +00:00
|
|
|
// "collapse" keeps the trip with the selected dive open
|
2014-02-28 04:09:57 +00:00
|
|
|
QAction *actionTaken = popup.exec(event->globalPos());
|
2013-06-06 00:06:42 +00:00
|
|
|
if (actionTaken == collapseAction && collapseAction) {
|
|
|
|
this->setAnimated(false);
|
2013-11-15 12:51:39 +00:00
|
|
|
scrollTo(selectedIndexes().first());
|
2013-06-06 00:06:42 +00:00
|
|
|
this->setAnimated(true);
|
|
|
|
}
|
|
|
|
event->accept();
|
2013-06-05 18:29:11 +00:00
|
|
|
}
|
2013-10-18 00:50:08 +00:00
|
|
|
|
2013-11-18 13:53:05 +00:00
|
|
|
void DiveListView::shiftTimes()
|
|
|
|
{
|
|
|
|
ShiftTimesDialog::instance()->show();
|
|
|
|
}
|
2013-12-05 16:28:28 +00:00
|
|
|
|
2014-01-27 13:44:26 +00:00
|
|
|
void DiveListView::loadImages()
|
|
|
|
{
|
2018-07-13 19:06:28 +00:00
|
|
|
QStringList m_filters = mediaExtensionFilters();
|
|
|
|
QStringList i_filters = imageExtensionFilters();
|
|
|
|
QStringList v_filters = videoExtensionFilters();
|
2017-05-05 19:13:46 +00:00
|
|
|
QStringList fileNames = QFileDialog::getOpenFileNames(this,
|
2018-07-13 18:46:45 +00:00
|
|
|
tr("Open media files"),
|
2017-05-05 19:13:46 +00:00
|
|
|
lastUsedImageDir(),
|
2018-07-13 19:06:28 +00:00
|
|
|
QString("%1 (%2);;%3 (%4);;%5 (%6);;%7 (*.*)")
|
|
|
|
.arg(tr("Media files"), m_filters.join(" ")
|
|
|
|
, tr("Image files"), i_filters.join(" ")
|
|
|
|
, tr("Video files"), v_filters.join(" ")
|
|
|
|
, tr("All files")));
|
2017-05-05 19:13:46 +00:00
|
|
|
|
2014-01-27 13:44:26 +00:00
|
|
|
if (fileNames.isEmpty())
|
|
|
|
return;
|
|
|
|
updateLastUsedImageDir(QFileInfo(fileNames[0]).dir().path());
|
2015-04-24 15:10:55 +00:00
|
|
|
matchImagesToDives(fileNames);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::matchImagesToDives(QStringList fileNames)
|
|
|
|
{
|
2015-03-14 16:44:24 +00:00
|
|
|
ShiftImageTimesDialog shiftDialog(this, fileNames);
|
2014-02-12 15:46:17 +00:00
|
|
|
shiftDialog.setOffset(lastImageTimeOffset());
|
2015-03-14 16:44:24 +00:00
|
|
|
if (!shiftDialog.exec())
|
|
|
|
return;
|
2014-02-12 15:46:17 +00:00
|
|
|
updateLastImageTimeOffset(shiftDialog.amount());
|
2014-01-27 13:44:26 +00:00
|
|
|
|
2020-04-19 16:48:23 +00:00
|
|
|
// Create the data structure of pictures to be added: a list of pictures per dive.
|
|
|
|
std::vector<Command::PictureListForAddition> pics;
|
2020-04-19 14:48:53 +00:00
|
|
|
for (const QString &fileName: fileNames) {
|
|
|
|
struct dive *d;
|
|
|
|
picture *pic = create_picture(qPrintable(fileName), shiftDialog.amount(), shiftDialog.matchAll(), &d);
|
|
|
|
if (!pic)
|
|
|
|
continue;
|
2020-04-19 16:48:23 +00:00
|
|
|
PictureObj pObj(*pic);
|
2020-04-19 14:48:53 +00:00
|
|
|
free(pic);
|
2020-04-19 16:48:23 +00:00
|
|
|
|
|
|
|
auto it = std::find_if(pics.begin(), pics.end(), [d](const Command::PictureListForAddition &l) { return l.d == d; });
|
|
|
|
if (it == pics.end())
|
|
|
|
pics.push_back(Command::PictureListForAddition { d, { pObj } });
|
|
|
|
else
|
|
|
|
it->pics.push_back(pObj);
|
2020-04-19 14:48:53 +00:00
|
|
|
}
|
2014-06-02 21:28:02 +00:00
|
|
|
|
2020-04-19 16:48:23 +00:00
|
|
|
if (pics.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Command::addPictures(pics);
|
2014-01-27 13:44:26 +00:00
|
|
|
}
|
|
|
|
|
2015-04-24 15:10:55 +00:00
|
|
|
void DiveListView::loadWebImages()
|
|
|
|
{
|
|
|
|
URLDialog urlDialog(this);
|
|
|
|
if (!urlDialog.exec())
|
|
|
|
return;
|
|
|
|
loadImageFromURL(QUrl::fromUserInput(urlDialog.url()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::loadImageFromURL(QUrl url)
|
|
|
|
{
|
|
|
|
if (url.isValid()) {
|
|
|
|
QEventLoop loop;
|
|
|
|
QNetworkRequest request(url);
|
|
|
|
QNetworkReply *reply = manager.get(request);
|
|
|
|
while (reply->isRunning()) {
|
|
|
|
loop.processEvents();
|
|
|
|
sleep(1);
|
|
|
|
}
|
|
|
|
QByteArray imageData = reply->readAll();
|
|
|
|
|
|
|
|
QImage image = QImage();
|
|
|
|
image.loadFromData(imageData);
|
2017-01-11 09:50:52 +00:00
|
|
|
if (image.isNull()) {
|
2015-04-24 15:10:55 +00:00
|
|
|
// If this is not an image, maybe it's an html file and Miika can provide some xslr magic to extract images.
|
|
|
|
// In this case we would call the function recursively on the list of image source urls;
|
2017-10-26 13:53:47 +00:00
|
|
|
report_error(qPrintable(tr("%1 does not appear to be an image").arg(url.toString())));
|
2015-04-24 15:10:55 +00:00
|
|
|
return;
|
2017-01-11 09:50:52 +00:00
|
|
|
}
|
2015-04-24 15:10:55 +00:00
|
|
|
|
|
|
|
QCryptographicHash hash(QCryptographicHash::Sha1);
|
2018-06-10 14:39:47 +00:00
|
|
|
hash.addData(url.toString().toUtf8());
|
2015-04-24 15:10:55 +00:00
|
|
|
QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first();
|
|
|
|
QDir dir(path);
|
|
|
|
if (!dir.exists())
|
|
|
|
dir.mkpath(path);
|
|
|
|
QFile imageFile(path.append("/").append(hash.result().toHex()));
|
|
|
|
if (imageFile.open(QIODevice::WriteOnly)) {
|
|
|
|
QDataStream stream(&imageFile);
|
|
|
|
stream.writeRawData(imageData.data(), imageData.length());
|
|
|
|
imageFile.waitForBytesWritten(-1);
|
|
|
|
imageFile.close();
|
2018-06-02 16:03:03 +00:00
|
|
|
learnPictureFilename(url.toString(), imageFile.fileName());
|
2015-04-24 15:10:55 +00:00
|
|
|
matchImagesToDives(QStringList(url.toString()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-27 13:44:26 +00:00
|
|
|
QString DiveListView::lastUsedImageDir()
|
|
|
|
{
|
|
|
|
QSettings settings;
|
|
|
|
QString lastImageDir = QDir::homePath();
|
|
|
|
|
|
|
|
settings.beginGroup("FileDialog");
|
|
|
|
if (settings.contains("LastImageDir"))
|
2017-12-04 07:49:28 +00:00
|
|
|
if (QDir(settings.value("LastImageDir").toString()).exists())
|
2017-12-04 07:44:07 +00:00
|
|
|
lastImageDir = settings.value("LastImageDir").toString();
|
2014-01-27 13:44:26 +00:00
|
|
|
return lastImageDir;
|
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
void DiveListView::updateLastUsedImageDir(const QString &dir)
|
2014-01-27 13:44:26 +00:00
|
|
|
{
|
|
|
|
QSettings s;
|
|
|
|
s.beginGroup("FileDialog");
|
|
|
|
s.setValue("LastImageDir", dir);
|
|
|
|
}
|
2014-02-12 15:46:17 +00:00
|
|
|
|
|
|
|
int DiveListView::lastImageTimeOffset()
|
|
|
|
{
|
|
|
|
QSettings settings;
|
|
|
|
int offset = 0;
|
|
|
|
|
|
|
|
settings.beginGroup("MainWindow");
|
|
|
|
if (settings.contains("LastImageTimeOffset"))
|
|
|
|
offset = settings.value("LastImageTimeOffset").toInt();
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiveListView::updateLastImageTimeOffset(const int offset)
|
|
|
|
{
|
|
|
|
QSettings s;
|
|
|
|
s.beginGroup("MainWindow");
|
|
|
|
s.setValue("LastImageTimeOffset", offset);
|
|
|
|
}
|
2017-01-28 11:16:45 +00:00
|
|
|
|
2018-05-22 06:04:28 +00:00
|
|
|
void DiveListView::mouseDoubleClickEvent(QMouseEvent*)
|
2017-01-28 11:16:45 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|