mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	This is even harder because setActiveTrip is called from an action slot from QML. If the C++ code called from that slot causes the object to which this slot belongs to be destroyed, we get very strange crashes. The only workaround I could come up with was to update the filter asynchronously. This all seems very ugly and fragile. Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
		
			
				
	
	
		
			493 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			493 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| #include "qt-models/divelistmodel.h"
 | |
| #include "core/divesite.h"
 | |
| #include "core/qthelper.h"
 | |
| #include "core/trip.h"
 | |
| #include "core/settings/qPrefGeneral.h"
 | |
| #include "core/ssrf.h" // for LOG_STP
 | |
| #include "core/errorhelper.h" // for verbose
 | |
| #include <QDateTime>
 | |
| #include <QtConcurrent>
 | |
| #include <QDebug>
 | |
| 
 | |
| // the DiveListSortModel creates the sorted, filtered list of dives that the user
 | |
| // can flip through horizontally
 | |
| // the CollapsedDiveListSortModel creates the vertical dive list which is a second
 | |
| // filter on top of the one applied to the DiveListSortModel
 | |
| 
 | |
| CollapsedDiveListSortModel::CollapsedDiveListSortModel()
 | |
| {
 | |
| 	setSourceModel(DiveListSortModel::instance());
 | |
| 	// make sure that we after changes to the underlying model (and therefore the dive list
 | |
| 	// we update the filter state
 | |
| 	connect(DiveListModel::instance(), &DiveListModel::rowsInserted, this, &CollapsedDiveListSortModel::updateFilterState);
 | |
| 	connect(DiveListModel::instance(), &DiveListModel::rowsMoved, this, &CollapsedDiveListSortModel::updateFilterState);
 | |
| 	connect(DiveListModel::instance(), &DiveListModel::rowsRemoved, this, &CollapsedDiveListSortModel::updateFilterState);
 | |
| }
 | |
| 
 | |
| CollapsedDiveListSortModel *CollapsedDiveListSortModel::instance()
 | |
| {
 | |
| 	static CollapsedDiveListSortModel self;
 | |
| 	return &self;
 | |
| }
 | |
| 
 | |
| void CollapsedDiveListSortModel::setSourceModel(QAbstractItemModel *sourceModel)
 | |
| {
 | |
| 	QSortFilterProxyModel::setSourceModel(sourceModel);
 | |
| 	// make sure we sort descending and have the filters correct
 | |
| 	setDynamicSortFilter(true);
 | |
| 	setSortRole(DiveListModel::DiveDateRole);
 | |
| 	sort(0, Qt::DescendingOrder);
 | |
| 	updateFilterState();
 | |
| }
 | |
| 
 | |
| // In QtQuick ListView, section headings can only be strings. To identify dives
 | |
| // that belong to the same trip, a string containing the trip-id is passed in.
 | |
| // To format the trip heading, the string is then converted back with this function.
 | |
| static dive_trip *tripIdToObject(const QString &s)
 | |
| {
 | |
| 	if (s.isEmpty())
 | |
| 		return nullptr;
 | |
| 	int id = s.toInt();
 | |
| 	dive_trip **trip = std::find_if(&trip_table.trips[0], &trip_table.trips[trip_table.nr],
 | |
| 					[id] (const dive_trip *t) { return t->id == id; });
 | |
| 	if (trip == &trip_table.trips[trip_table.nr]) {
 | |
| 		fprintf(stderr, "Warning: unknown trip id passed through QML: %d\n", id);
 | |
| 		return nullptr;
 | |
| 	}
 | |
| 	return *trip;
 | |
| }
 | |
| 
 | |
| // the trip title is designed to be location (# dives)
 | |
| // or, if there is no location name date range (# dives)
 | |
| // where the date range is given as "month year" or "month-month year" or "month year - month year"
 | |
| QString CollapsedDiveListSortModel::tripTitle(const QString §ion)
 | |
| {
 | |
| 	const dive_trip *dt = tripIdToObject(section);
 | |
| 	if (!dt)
 | |
| 		return QString();
 | |
| 	QString numDives = tr("(%n dive(s))", "", dt->dives.nr);
 | |
| 	int shown = trip_shown_dives(dt);
 | |
| 	QString shownDives = shown != dt->dives.nr ? QStringLiteral(" ") + tr("(%L1 shown)").arg(shown) : QString();
 | |
| 	QString title(dt->location);
 | |
| 
 | |
| 	if (title.isEmpty()) {
 | |
| 		// so use the date range
 | |
| 		QDateTime firstTime = QDateTime::fromMSecsSinceEpoch(1000*trip_date(dt), Qt::UTC);
 | |
| 		QString firstMonth = firstTime.toString("MMM");
 | |
| 		QString firstYear = firstTime.toString("yyyy");
 | |
| 		QDateTime lastTime = QDateTime::fromMSecsSinceEpoch(1000*dt->dives.dives[0]->when, Qt::UTC);
 | |
| 		QString lastMonth = lastTime.toString("MMM");
 | |
| 		QString lastYear = lastTime.toString("yyyy");
 | |
| 		if (lastMonth == firstMonth && lastYear == firstYear)
 | |
| 			title = firstMonth + " " + firstYear;
 | |
| 		else if (lastMonth != firstMonth && lastYear == firstYear)
 | |
| 			title = firstMonth + "-" + lastMonth + " " + firstYear;
 | |
| 		else
 | |
| 			title = firstMonth + " " + firstYear + " - " + lastMonth + " " + lastYear;
 | |
| 	}
 | |
| 	return QStringLiteral("%1 %2%3").arg(title, numDives, shownDives);
 | |
| }
 | |
| 
 | |
| QString CollapsedDiveListSortModel::tripShortDate(const QString §ion)
 | |
| {
 | |
| 	const dive_trip *dt = tripIdToObject(section);
 | |
| 	if (!dt)
 | |
| 		return QString();
 | |
| 	QDateTime firstTime = QDateTime::fromMSecsSinceEpoch(1000*trip_date(dt), Qt::UTC);
 | |
| 	QString firstMonth = firstTime.toString("MMM");
 | |
| 	return QStringLiteral("%1\n'%2").arg(firstMonth,firstTime.toString("yy"));
 | |
| }
 | |
| 
 | |
| void CollapsedDiveListSortModel::setActiveTrip(const QString &trip)
 | |
| {
 | |
| 	m_activeTrip = trip;
 | |
| 	// we can't update the filter state from the this function as that is called from
 | |
| 	// a slot in the QML code which could cause the object that is executing the slot
 | |
| 	// to be destroyed before this function returns.
 | |
| 	// Instead do this asynchronously
 | |
| 	QtConcurrent::run(QThreadPool::globalInstance(),
 | |
| 			  [=]{
 | |
| 				CollapsedDiveListSortModel::instance()->updateFilterState();
 | |
| 			  });
 | |
| }
 | |
| 
 | |
| QString CollapsedDiveListSortModel::activeTrip() const
 | |
| {
 | |
| 	return m_activeTrip;
 | |
| }
 | |
| 
 | |
| // tell us if this dive is the first dive in a trip that has at least one
 | |
| // dive that isn't hidden (even if this dive is hidden)
 | |
| static bool isFirstInNotCompletelyHiddenTrip(struct dive *d)
 | |
| {
 | |
| 	struct dive_trip *dt = d->divetrip;
 | |
| 	if (dt->dives.nr > 0 && dt->dives.dives[0] == d) {
 | |
| 		// ok, this is the first dive in its trip
 | |
| 		int i = -1;
 | |
| 		while (++i < dt->dives.nr)
 | |
| 			if (!dt->dives.dives[i]->hidden_by_filter)
 | |
| 				return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| // the mobile app allows only one selected dive
 | |
| // that means there are either zero or exactly one expanded trip -
 | |
| bool CollapsedDiveListSortModel::isExpanded(struct dive_trip *dt) const
 | |
| {
 | |
| 	return !m_activeTrip.isEmpty() && dt == tripIdToObject(m_activeTrip);
 | |
| }
 | |
| 
 | |
| void CollapsedDiveListSortModel::updateFilterState()
 | |
| {
 | |
| 	// now do something clever to show the right dives
 | |
| 	// first make sure that the underlying filtering is taken care of
 | |
| 	DiveListSortModel::instance()->updateFilterState();
 | |
| 	int i;
 | |
| 	struct dive *d;
 | |
| 	for_each_dive(i, d) {
 | |
| 		CollapsedState state = DontShow;
 | |
| 		struct dive_trip *dt = d->divetrip;
 | |
| 
 | |
| 		// we show the dives that are outside of a trip or inside of the one expanded trip
 | |
| 		if (!d->hidden_by_filter && (dt == nullptr || isExpanded(dt)))
 | |
| 			state = ShowDive;
 | |
| 		// we mark the first dive of a trip that contains any unfiltered dives as ShowTrip  or ShowDiveAndTrip (if this is the one expanded trip)
 | |
| 		if (dt != nullptr && isFirstInNotCompletelyHiddenTrip(d))
 | |
| 			state = (state == ShowDive) ? ShowDiveAndTrip : ShowTrip;
 | |
| 		d->collapsed = state;
 | |
| 	}
 | |
| 	// everything up to here can be done even if we don't have a source model
 | |
| 	if (sourceModel() != nullptr) {
 | |
| 		DiveListModel *dlm = DiveListModel::instance();
 | |
| 		dlm->dataChanged(dlm->index(0,0), dlm->index(dlm->rowCount() - 1, 0));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CollapsedDiveListSortModel::updateSelectionState()
 | |
| {
 | |
| 	QVector<int> changedRoles = { DiveListModel::SelectedRole };
 | |
| 	dataChanged(index(0,0), index(rowCount() - 1, 0), changedRoles);
 | |
| }
 | |
| 
 | |
| bool CollapsedDiveListSortModel::filterAcceptsRow(int source_row, const QModelIndex &) const
 | |
| {
 | |
| 	// get the corresponding dive from the DiveListModel and check if we should show it
 | |
| 	const dive *d = DiveListModel::instance()->getDive(source_row);
 | |
| 	if (verbose > 1)
 | |
| 		qDebug() << "FAR source row" << source_row << "dive" << (d ? QString::number(d->number) : "NULL") << "is" << (d != nullptr && d->collapsed != DontShow) <<
 | |
| 			    (d != nullptr ? QString::number(d->collapsed) : "");
 | |
| 	return d != nullptr && d->collapsed != DontShow;
 | |
| }
 | |
| 
 | |
| DiveListSortModel::DiveListSortModel()
 | |
| {
 | |
| 	setSourceModel(DiveListModel::instance());
 | |
| 	LOG_STP("run_ui diveListModel sorted");
 | |
| }
 | |
| 
 | |
| DiveListSortModel *DiveListSortModel::instance()
 | |
| {
 | |
| 	static DiveListSortModel self;
 | |
| 	return &self;
 | |
| }
 | |
| 
 | |
| QString DiveListSortModel::getFilterString() const
 | |
| {
 | |
| 	return filterString;
 | |
| }
 | |
| 
 | |
| void DiveListSortModel::updateFilterState()
 | |
| {
 | |
| 	if (filterString.isEmpty()) {
 | |
| 		resetFilter();
 | |
| 		return;
 | |
| 	}
 | |
| 	// store this in local variables to avoid having to call these methods over and over
 | |
| 	bool includeNotes = qPrefGeneral::filterFullTextNotes();
 | |
| 	Qt::CaseSensitivity cs = qPrefGeneral::filterCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
 | |
| 
 | |
| 	int i;
 | |
| 	struct dive *d;
 | |
| 	for_each_dive(i, d)
 | |
| 		d->hidden_by_filter = !diveContainsText(d, filterString, cs, includeNotes);
 | |
| }
 | |
| 
 | |
| void DiveListSortModel::setSourceModel(QAbstractItemModel *sourceModel)
 | |
| {
 | |
| 	QSortFilterProxyModel::setSourceModel(sourceModel);
 | |
| 	// make sure we sort descending and have the filters correct
 | |
| 	setDynamicSortFilter(true);
 | |
| 	setSortRole(DiveListModel::DiveDateRole);
 | |
| 	sort(0, Qt::DescendingOrder);
 | |
| 	updateFilterState();
 | |
| }
 | |
| 
 | |
| void DiveListSortModel::setFilter(QString f)
 | |
| {
 | |
| 	filterString = f;
 | |
| 	CollapsedDiveListSortModel::instance()->updateFilterState();
 | |
| 	invalidateFilter();
 | |
| }
 | |
| 
 | |
| void DiveListSortModel::resetFilter()
 | |
| {
 | |
| 	int i;
 | |
| 	struct dive *d;
 | |
| 	for_each_dive(i, d)
 | |
| 		d->hidden_by_filter = false;
 | |
| 	invalidateFilter();
 | |
| }
 | |
| 
 | |
| // filtering is way too slow on mobile. Maybe we should roll our own?
 | |
| bool DiveListSortModel::filterAcceptsRow(int source_row, const QModelIndex &) const
 | |
| {
 | |
| 	DiveListModel *mySourceModel = qobject_cast<DiveListModel *>(sourceModel());
 | |
| 	const dive *d = mySourceModel->getDive(source_row);
 | |
| 	return d && !d->hidden_by_filter;
 | |
| }
 | |
| 
 | |
| int DiveListSortModel::shown()
 | |
| {
 | |
| 	return rowCount();
 | |
| }
 | |
| 
 | |
| int DiveListSortModel::getIdxForId(int id)
 | |
| {
 | |
| 	DiveListModel *mySourceModel = qobject_cast<DiveListModel *>(sourceModel());
 | |
| 	QModelIndex sourceIdx = mySourceModel->getDiveQIdx(id);
 | |
| 	if (!sourceIdx.isValid())
 | |
| 		return -1;
 | |
| 	QModelIndex localIdx = mapFromSource(sourceIdx);
 | |
| 	return localIdx.row();
 | |
| }
 | |
| 
 | |
| void DiveListSortModel::reload()
 | |
| {
 | |
| 	DiveListModel *mySourceModel = qobject_cast<DiveListModel *>(sourceModel());
 | |
| 	mySourceModel->resetInternalData();
 | |
| }
 | |
| 
 | |
| DiveListModel::DiveListModel()
 | |
| {
 | |
| 	LOG_STP("run_ui diveListModel started");
 | |
| }
 | |
| 
 | |
| void DiveListModel::insertDive(int i)
 | |
| {
 | |
| 	beginInsertRows(QModelIndex(), i, i);
 | |
| 	endInsertRows();
 | |
| }
 | |
| 
 | |
| void DiveListModel::removeDive(int i)
 | |
| {
 | |
| 	beginRemoveRows(QModelIndex(), i, i);
 | |
| 	endRemoveRows();
 | |
| }
 | |
| 
 | |
| void DiveListModel::removeDiveById(int id)
 | |
| {
 | |
| 	for (int i = 0; i < dive_table.nr; i++) {
 | |
| 		if (dive_table.dives[i]->id == id) {
 | |
| 			removeDive(i);
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void DiveListModel::updateDive(int i, dive *d)
 | |
| {
 | |
| 	// we need to make sure that QML knows that this dive has changed -
 | |
| 	// the only reliable way I've found is to remove and re-insert it
 | |
| 	removeDive(i);
 | |
| 	insertDive(i);
 | |
| }
 | |
| 
 | |
| void DiveListModel::clear()
 | |
| {
 | |
| 	beginResetModel();
 | |
| 	clear_dive_file_data();
 | |
| 	endResetModel();
 | |
| }
 | |
| 
 | |
| void DiveListModel::reload()
 | |
| {
 | |
| 	// Note: instead of doing a (logical) beginResetModel()/endResetModel(),
 | |
| 	// we add the rows (if any). The reason is that a beginResetModel()/endResetModel()
 | |
| 	// pair resulted in the DiveDetailsPage being renedered for *every* dive in
 | |
| 	// the list. It is unclear whether this is a Qt-bug or intended insanity.
 | |
| 	// Therefore, this function must only be called after having called clear().
 | |
| 	// Otherwise the model will become inconsistent!
 | |
| 	if (dive_table.nr > 0) {
 | |
| 		beginInsertRows(QModelIndex(), 0, dive_table.nr - 1);
 | |
| 		endInsertRows();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void DiveListModel::resetInternalData()
 | |
| {
 | |
| 	// this is a hack. There is a long standing issue, that seems related to a
 | |
| 	// sync problem between QML engine and underlying model data. It causes delete
 | |
| 	// from divelist (on mobile) to crash. But not always. This function is part of
 | |
| 	// an attempt to fix this. See commit.
 | |
| 	beginResetModel();
 | |
| 	endResetModel();
 | |
| }
 | |
| 
 | |
| int DiveListModel::rowCount(const QModelIndex &) const
 | |
| {
 | |
| 	return dive_table.nr;
 | |
| }
 | |
| 
 | |
| // Get the index of a dive in the global dive list by the dive's unique id. Returns an integer [0..nrdives).
 | |
| int DiveListModel::getDiveIdx(int id) const
 | |
| {
 | |
| 	return get_idx_by_uniq_id(id);
 | |
| }
 | |
| 
 | |
| // Get an index of a dive. In contrast to getDiveIdx, this returns a Qt model-index,
 | |
| // which can be used to access data of a Qt model.
 | |
| QModelIndex DiveListModel::getDiveQIdx(int id)
 | |
| {
 | |
| 	int idx = getDiveIdx(id);
 | |
| 	return idx >= 0 ? createIndex(idx, 0) : QModelIndex();
 | |
| }
 | |
| 
 | |
| QVariant DiveListModel::data(const QModelIndex &index, int role) const
 | |
| {
 | |
| 	if(index.row() < 0 || index.row() >= dive_table.nr)
 | |
| 		return QVariant();
 | |
| 
 | |
| 	dive *d = dive_table.dives[index.row()];
 | |
| 	if (!d)
 | |
| 		return QVariant();
 | |
| 	switch(role) {
 | |
| 	case DiveDateRole: return (qlonglong)d->when;
 | |
| 	// We have to return a QString as trip-id, because that will be used as section
 | |
| 	// variable in the QtQuick list view. That has to be a string because it will try
 | |
| 	// to do locale-aware sorting. And amazingly this can't be changed.
 | |
| 	case TripIdRole: return d->divetrip ? QString::number(d->divetrip->id) : QString();
 | |
| 	case TripNrDivesRole: return d->divetrip ? d->divetrip->dives.nr : 0;
 | |
| 	case DateTimeRole: {
 | |
| 		QDateTime localTime = QDateTime::fromMSecsSinceEpoch(1000 * d->when, Qt::UTC);
 | |
| 		localTime.setTimeSpec(Qt::UTC);
 | |
| 		return QStringLiteral("%1 %2").arg(localTime.date().toString(prefs.date_format_short),
 | |
| 						   localTime.time().toString(prefs.time_format));
 | |
| 		}
 | |
| 	case IdRole: return d->id;
 | |
| 	case NumberRole: return d->number;
 | |
| 	case LocationRole: return get_dive_location(d);
 | |
| 	case DepthRole: return get_depth_string(d->dc.maxdepth.mm, true, true);
 | |
| 	case DurationRole: return get_dive_duration_string(d->duration.seconds, gettextFromC::tr("h"), gettextFromC::tr("min"));
 | |
| 	case DepthDurationRole: return QStringLiteral("%1 / %2").arg(get_depth_string(d->dc.maxdepth.mm, true, true),
 | |
| 								     get_dive_duration_string(d->duration.seconds, gettextFromC::tr("h"), gettextFromC::tr("min")));
 | |
| 	case RatingRole: return d->rating;
 | |
| 	case VizRole: return d->visibility;
 | |
| 	case SuitRole: return d->suit;
 | |
| 	case AirTempRole: return get_temperature_string(d->airtemp, true);
 | |
| 	case WaterTempRole: return get_temperature_string(d->watertemp, true);
 | |
| 	case SacRole: return formatSac(d);
 | |
| 	case SumWeightRole: return get_weight_string(weight_t { total_weight(d) }, true);
 | |
| 	case DiveMasterRole: return d->divemaster ? d->divemaster : QString();
 | |
| 	case BuddyRole: return d->buddy ? d->buddy : QString();
 | |
| 	case NotesRole: return formatNotes(d);
 | |
| 	case GpsRole: return d->dive_site ? printGPSCoords(&d->dive_site->location) : QString();
 | |
| 	case GpsDecimalRole: return format_gps_decimal(d);
 | |
| 	case NoDiveRole: return d->duration.seconds == 0 && d->dc.duration.seconds == 0;
 | |
| 	case DiveSiteRole: return QVariant::fromValue(d->dive_site);
 | |
| 	case CylinderRole: return formatGetCylinder(d).join(", ");
 | |
| 	case GetCylinderRole: return formatGetCylinder(d);
 | |
| 	case CylinderListRole: return getFullCylinderList();
 | |
| 	case SingleWeightRole: return d->weightsystems.nr <= 1;
 | |
| 	case StartPressureRole: return getStartPressure(d);
 | |
| 	case EndPressureRole: return getEndPressure(d);
 | |
| 	case FirstGasRole: return getFirstGas(d);
 | |
| 	case CollapsedRole: return d->collapsed;
 | |
| 	case SelectedRole: return d->selected;
 | |
| 	}
 | |
| 	return QVariant();
 | |
| }
 | |
| 
 | |
| QHash<int, QByteArray> DiveListModel::roleNames() const
 | |
| {
 | |
| 	QHash<int, QByteArray> roles;
 | |
| 	roles[DiveDateRole] = "date";
 | |
| 	roles[TripIdRole] = "tripId";
 | |
| 	roles[TripNrDivesRole] = "tripNrDives";
 | |
| 	roles[DateTimeRole] = "dateTime";
 | |
| 	roles[IdRole] = "id";
 | |
| 	roles[NumberRole] = "number";
 | |
| 	roles[LocationRole] = "location";
 | |
| 	roles[DepthRole] = "depth";
 | |
| 	roles[DurationRole] = "duration";
 | |
| 	roles[DepthDurationRole] = "depthDuration";
 | |
| 	roles[RatingRole] = "rating";
 | |
| 	roles[VizRole] = "viz";
 | |
| 	roles[SuitRole] = "suit";
 | |
| 	roles[AirTempRole] = "airTemp";
 | |
| 	roles[WaterTempRole] = "waterTemp";
 | |
| 	roles[SacRole] = "sac";
 | |
| 	roles[SumWeightRole] = "sumWeight";
 | |
| 	roles[DiveMasterRole] = "diveMaster";
 | |
| 	roles[BuddyRole] = "buddy";
 | |
| 	roles[NotesRole]= "notes";
 | |
| 	roles[GpsRole] = "gps";
 | |
| 	roles[GpsDecimalRole] = "gpsDecimal";
 | |
| 	roles[NoDiveRole] = "noDive";
 | |
| 	roles[DiveSiteRole] = "diveSite";
 | |
| 	roles[CylinderRole] = "cylinder";
 | |
| 	roles[GetCylinderRole] = "getCylinder";
 | |
| 	roles[CylinderListRole] = "cylinderList";
 | |
| 	roles[SingleWeightRole] = "singleWeight";
 | |
| 	roles[StartPressureRole] = "startPressure";
 | |
| 	roles[EndPressureRole] = "endPressure";
 | |
| 	roles[FirstGasRole] = "firstGas";
 | |
| 	roles[CollapsedRole] = "collapsed";
 | |
| 	roles[SelectedRole] = "selected";
 | |
| 	return roles;
 | |
| }
 | |
| 
 | |
| // create a new dive. set the current time and add it to the end of the dive list
 | |
| QString DiveListModel::startAddDive()
 | |
| {
 | |
| 	struct dive *d;
 | |
| 	d = alloc_dive();
 | |
| 	d->when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset();
 | |
| 
 | |
| 	// find the highest dive nr we have and pick the next one
 | |
| 	struct dive *pd;
 | |
| 	int i, nr = 0;
 | |
| 	for_each_dive(i, pd) {
 | |
| 		if (pd->number > nr)
 | |
| 			nr = pd->number;
 | |
| 	}
 | |
| 	nr++;
 | |
| 	d->number = nr;
 | |
| 	d->dc.model = strdup("manually added dive");
 | |
| 	append_dive(d);
 | |
| 	insertDive(get_idx_by_uniq_id(d->id));
 | |
| 	return QString::number(d->id);
 | |
| }
 | |
| 
 | |
| DiveListModel *DiveListModel::instance()
 | |
| {
 | |
| 	static DiveListModel self;
 | |
| 	return &self;
 | |
| }
 | |
| 
 | |
| struct dive *DiveListModel::getDive(int i)
 | |
| {
 | |
| 	if (i < 0 || i >= dive_table.nr) {
 | |
| 		qWarning("DiveListModel::getDive(): accessing invalid dive with id %d", i);
 | |
| 		return nullptr;
 | |
| 	}
 | |
| 	return dive_table.dives[i];
 | |
| }
 | |
| 
 | |
| DiveObjectHelper DiveListModel::at(int i)
 | |
| {
 | |
| 	// For an invalid id, returns an invalid DiveObjectHelper that will crash on access.
 | |
| 	dive *d = getDive(i);
 | |
| 	return d ? DiveObjectHelper(d) : DiveObjectHelper();
 | |
| }
 |