subsurface/qt-ui/locationinformation.cpp
Tomaz Canabrava c4c7e7a7f4 Display the popup in the correct place
The popup should be shown beneath the QLineEdit. this code here
is shamelessy stolen from the QCompleter source code because I
really didn't want to rethink the correct way of doing this.

Signed-off-by: Tomaz Canabrava <tomaz.canabrava@intel.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2015-09-23 14:15:22 -07:00

515 lines
15 KiB
C++

#include "locationinformation.h"
#include "dive.h"
#include "mainwindow.h"
#include "divelistview.h"
#include "qthelper.h"
#include "globe.h"
#include "filtermodels.h"
#include "divelocationmodel.h"
#include "divesitehelpers.h"
#include "modeldelegates.h"
#include <QDebug>
#include <QShowEvent>
#include <QItemSelectionModel>
#include <qmessagebox.h>
#include <cstdlib>
#include <QDesktopWidget>
#include <QScrollBar>
LocationInformationWidget::LocationInformationWidget(QWidget *parent) : QGroupBox(parent), modified(false)
{
ui.setupUi(this);
ui.diveSiteMessage->setCloseButtonVisible(false);
acceptAction = new QAction(tr("Apply changes"), this);
connect(acceptAction, SIGNAL(triggered(bool)), this, SLOT(acceptChanges()));
rejectAction = new QAction(tr("Discard changes"), this);
connect(rejectAction, SIGNAL(triggered(bool)), this, SLOT(rejectChanges()));
ui.diveSiteMessage->setText(tr("Dive site management"));
ui.diveSiteMessage->addAction(acceptAction);
ui.diveSiteMessage->addAction(rejectAction);
connect(this, SIGNAL(startFilterDiveSite(uint32_t)), MultiFilterSortModel::instance(), SLOT(startFilterDiveSite(uint32_t)));
connect(this, SIGNAL(stopFilterDiveSite()), MultiFilterSortModel::instance(), SLOT(stopFilterDiveSite()));
connect(ui.geoCodeButton, SIGNAL(clicked()), this, SLOT(reverseGeocode()));
SsrfSortFilterProxyModel *filter_model = new SsrfSortFilterProxyModel(this);
filter_model->setSourceModel(LocationInformationModel::instance());
filter_model->setFilterRow(filter_same_gps_cb);
ui.diveSiteListView->setModel(filter_model);
ui.diveSiteListView->setModelColumn(LocationInformationModel::NAME);
ui.diveSiteListView->installEventFilter(this);
#ifndef NO_MARBLE
// Globe Management Code.
connect(this, &LocationInformationWidget::requestCoordinates,
GlobeGPS::instance(), &GlobeGPS::prepareForGetDiveCoordinates);
connect(this, &LocationInformationWidget::endRequestCoordinates,
GlobeGPS::instance(), &GlobeGPS::endGetDiveCoordinates);
connect(GlobeGPS::instance(), &GlobeGPS::coordinatesChanged,
this, &LocationInformationWidget::updateGpsCoordinates);
connect(this, &LocationInformationWidget::endEditDiveSite,
GlobeGPS::instance(), &GlobeGPS::repopulateLabels);
#endif
}
bool LocationInformationWidget::eventFilter(QObject*, QEvent *ev)
{
if( ev->type() == QEvent::ContextMenu ) {
QContextMenuEvent *ctx = (QContextMenuEvent*) ev;
QMenu contextMenu;
contextMenu.addAction(tr("Merge into current site"), this, SLOT(mergeSelectedDiveSites()));
contextMenu.exec(ctx->globalPos());
return true;
}
return false;
}
void LocationInformationWidget::mergeSelectedDiveSites() {
if (QMessageBox::warning(MainWindow::instance(), tr("Merging dive sites"),
tr("You are about to merge dive sites, you can't undo that action \n Are you sure you want to continue?"),
QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok)
return;
QModelIndexList selection = ui.diveSiteListView->selectionModel()->selectedIndexes();
uint32_t *selected_dive_sites = (uint32_t*) malloc(sizeof(uint32_t) * selection.count());
int i = 0;
Q_FOREACH(const QModelIndex& idx, selection) {
selected_dive_sites[i] = (uint32_t) idx.data(LocationInformationModel::UUID_ROLE).toInt();
i++;
}
merge_dive_sites(displayed_dive_site.uuid, selected_dive_sites, i);
LocationInformationModel::instance()->update();
QSortFilterProxyModel *m = (QSortFilterProxyModel *) ui.diveSiteListView->model();
m->invalidate();
free(selected_dive_sites);
}
void LocationInformationWidget::updateLabels()
{
if (displayed_dive_site.name)
ui.diveSiteName->setText(displayed_dive_site.name);
else
ui.diveSiteName->clear();
if (displayed_dive_site.description)
ui.diveSiteDescription->setText(displayed_dive_site.description);
else
ui.diveSiteDescription->clear();
if (displayed_dive_site.notes)
ui.diveSiteNotes->setPlainText(displayed_dive_site.notes);
else
ui.diveSiteNotes->clear();
if (displayed_dive_site.latitude.udeg || displayed_dive_site.longitude.udeg)
ui.diveSiteCoordinates->setText(printGPSCoords(displayed_dive_site.latitude.udeg, displayed_dive_site.longitude.udeg));
else
ui.diveSiteCoordinates->clear();
emit startFilterDiveSite(displayed_dive_site.uuid);
emit startEditDiveSite(displayed_dive_site.uuid);
}
void LocationInformationWidget::updateGpsCoordinates()
{
QString oldText = ui.diveSiteCoordinates->text();
ui.diveSiteCoordinates->setText(printGPSCoords(displayed_dive_site.latitude.udeg, displayed_dive_site.longitude.udeg));
if (oldText != ui.diveSiteCoordinates->text())
markChangedWidget(ui.diveSiteCoordinates);
}
void LocationInformationWidget::acceptChanges()
{
emit stopFilterDiveSite();
char *uiString;
struct dive_site *currentDs;
uiString = ui.diveSiteName->text().toUtf8().data();
if (get_dive_site_by_uuid(displayed_dive_site.uuid) != NULL)
currentDs = get_dive_site_by_uuid(displayed_dive_site.uuid);
else
currentDs = get_dive_site_by_uuid(create_dive_site_from_current_dive(uiString));
currentDs->latitude = displayed_dive_site.latitude;
currentDs->longitude = displayed_dive_site.longitude;
if (!same_string(uiString, currentDs->name)) {
free(currentDs->name);
currentDs->name = copy_string(uiString);
}
uiString = ui.diveSiteDescription->text().toUtf8().data();
if (!same_string(uiString, currentDs->description)) {
free(currentDs->description);
currentDs->description = copy_string(uiString);
}
uiString = ui.diveSiteNotes->document()->toPlainText().toUtf8().data();
if (!same_string(uiString, currentDs->notes)) {
free(currentDs->notes);
currentDs->notes = copy_string(uiString);
}
if (!ui.diveSiteCoordinates->text().isEmpty()) {
double lat, lon;
parseGpsText(ui.diveSiteCoordinates->text(), &lat, &lon);
currentDs->latitude.udeg = lat * 1000000.0;
currentDs->longitude.udeg = lon * 1000000.0;
}
if (dive_site_is_empty(currentDs)) {
LocationInformationModel::instance()->removeRow(get_divesite_idx(currentDs));
displayed_dive.dive_site_uuid = 0;
}
copy_dive_site(currentDs, &displayed_dive_site);
mark_divelist_changed(true);
resetState();
emit endRequestCoordinates();
emit endEditDiveSite();
emit coordinatesChanged();
}
void LocationInformationWidget::rejectChanges()
{
resetState();
emit endRequestCoordinates();
emit stopFilterDiveSite();
emit endEditDiveSite();
emit coordinatesChanged();
}
void LocationInformationWidget::showEvent(QShowEvent *ev)
{
if (displayed_dive_site.uuid) {
updateLabels();
QSortFilterProxyModel *m = qobject_cast<QSortFilterProxyModel*>(ui.diveSiteListView->model());
emit startFilterDiveSite(displayed_dive_site.uuid);
if (m)
m->invalidate();
}
emit requestCoordinates();
QGroupBox::showEvent(ev);
}
void LocationInformationWidget::markChangedWidget(QWidget *w)
{
QPalette p;
qreal h, s, l, a;
if (!modified)
enableEdition();
qApp->palette().color(QPalette::Text).getHslF(&h, &s, &l, &a);
p.setBrush(QPalette::Base, (l <= 0.3) ? QColor(Qt::yellow).lighter()
: (l <= 0.6) ? QColor(Qt::yellow).light()
: /* else */ QColor(Qt::yellow).darker(300));
w->setPalette(p);
modified = true;
}
void LocationInformationWidget::resetState()
{
modified = false;
resetPallete();
MainWindow::instance()->dive_list()->setEnabled(true);
MainWindow::instance()->setEnabledToolbar(true);
ui.diveSiteMessage->setText(tr("Dive site management"));
}
void LocationInformationWidget::enableEdition()
{
MainWindow::instance()->dive_list()->setEnabled(false);
MainWindow::instance()->setEnabledToolbar(false);
ui.diveSiteMessage->setText(tr("You are editing a dive site"));
}
void LocationInformationWidget::on_diveSiteCoordinates_textChanged(const QString& text)
{
uint lat = displayed_dive_site.latitude.udeg;
uint lon = displayed_dive_site.longitude.udeg;
if (!same_string(qPrintable(text), printGPSCoords(lat, lon))) {
double latitude, longitude;
if (parseGpsText(text, &latitude, &longitude)) {
displayed_dive_site.latitude.udeg = latitude * 1000000;
displayed_dive_site.longitude.udeg = longitude * 1000000;
markChangedWidget(ui.diveSiteCoordinates);
emit coordinatesChanged();
}
}
}
void LocationInformationWidget::on_diveSiteDescription_textChanged(const QString& text)
{
if (!same_string(qPrintable(text), displayed_dive_site.description))
markChangedWidget(ui.diveSiteDescription);
}
void LocationInformationWidget::on_diveSiteName_textChanged(const QString& text)
{
if (!same_string(qPrintable(text), displayed_dive_site.name))
markChangedWidget(ui.diveSiteName);
}
void LocationInformationWidget::on_diveSiteNotes_textChanged()
{
if (!same_string(qPrintable(ui.diveSiteNotes->toPlainText()), displayed_dive_site.notes))
markChangedWidget(ui.diveSiteNotes);
}
void LocationInformationWidget::resetPallete()
{
QPalette p;
ui.diveSiteCoordinates->setPalette(p);
ui.diveSiteDescription->setPalette(p);
ui.diveSiteName->setPalette(p);
ui.diveSiteNotes->setPalette(p);
}
bool LocationManagementEditHelper::eventFilter(QObject *obj, QEvent *ev)
{
QListView *view = qobject_cast<QListView*>(obj);
if(!view)
return false;
if(ev->type() == QEvent::Show) {
last_uuid = 0;
qDebug() << "EventFilter: " << last_uuid;
}
if(ev->type() == QEvent::KeyPress) {
QKeyEvent *keyEv = (QKeyEvent*) ev;
if(keyEv->key() == Qt::Key_Return) {
handleActivation(view->currentIndex());
view->hide();
return true;
}
}
return false;
}
void LocationManagementEditHelper::handleActivation(const QModelIndex& activated)
{
if (!activated.isValid())
return;
QModelIndex uuidIdx = activated.model()->index(
activated.row(), LocationInformationModel::UUID);
last_uuid = uuidIdx.data().toInt();
/* if we are in 'recently added divesite mode, create a new divesite,
* and go to dive site edit edit mode. */
if (last_uuid == RECENTLY_ADDED_DIVESITE) {
uint32_t ds_uuid = create_dive_site_from_current_dive(qPrintable(activated.data().toString()));
qDebug() << "ds_uuid" << ds_uuid;
struct dive_site *ds = get_dive_site_by_uuid(ds_uuid);
copy_dive_site(ds, &displayed_dive_site);
displayed_dive.dive_site_uuid = ds->uuid;
last_uuid = ds->uuid;
// Move this out of here later.
MainWindow::instance()->startDiveSiteEdit();
}
qDebug() << "Selected dive_site: " << last_uuid;
}
void LocationManagementEditHelper::resetDiveSiteUuid() {
last_uuid = 0;
qDebug() << "Reset: " << last_uuid;
}
uint32_t LocationManagementEditHelper::diveSiteUuid() const {
return last_uuid;
}
void LocationInformationWidget::reverseGeocode()
{
ReverseGeoLookupThread *geoLookup = ReverseGeoLookupThread::instance();
geoLookup->lookup(&displayed_dive_site);
updateLabels();
}
DiveLocationFilterProxyModel::DiveLocationFilterProxyModel(QObject *parent)
{
}
DiveLocationLineEdit *location_line_edit = 0;
bool DiveLocationFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
if(source_row == 0)
return true;
QString sourceString = sourceModel()->index(source_row, DiveLocationModel::NAME).data(Qt::DisplayRole).toString();
return sourceString.toLower().startsWith(location_line_edit->text().toLower());
}
bool DiveLocationFilterProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
{
return source_left.data().toString() <= source_right.data().toString();
}
DiveLocationModel::DiveLocationModel(QObject *o)
{
resetModel();
}
void DiveLocationModel::resetModel()
{
beginResetModel();
endResetModel();
}
QVariant DiveLocationModel::data(const QModelIndex& index, int role) const
{
if(index.row() <= 1) { // two special cases.
switch(role) {
case Qt::DisplayRole : return new_ds_value[index.row()];
case Qt::ToolTipRole : return "Create a new dive site";
case Qt::DecorationRole : return QIcon(":plus");
}
}
// The dive sites are -2 because of the first two items.
struct dive_site *ds = get_dive_site(index.row() - 2);
switch(role) {
case Qt::EditRole:
case Qt::DisplayRole :
switch(index.column()) {
case UUID: return ds->uuid;
case NAME: return ds->name;
case LATITUDE: return ds->latitude.udeg;
case LONGITUDE: return ds->longitude.udeg;
case DESCRIPTION: return ds->description;
case NOTES: return ds->name;
}
break;
case Qt::DecorationRole : {
if (dive_site_has_gps_location(ds))
return QIcon(":geocode");
}
}
return QVariant();
}
int DiveLocationModel::columnCount(const QModelIndex& parent) const
{
return COLUMNS;
}
int DiveLocationModel::rowCount(const QModelIndex& parent) const
{
return dive_site_table.nr + 2;
}
bool DiveLocationModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if(!index.isValid())
return false;
if (index.row() > 1)
return false;
new_ds_value[index.row()] = value.toString();
dataChanged(index, index);
return true;
}
DiveLocationLineEdit::DiveLocationLineEdit(QWidget *parent)
{
location_line_edit = this;
proxy = new DiveLocationFilterProxyModel();
model = new DiveLocationModel();
view = new DiveLocationListView();
proxy->setSourceModel(model);
proxy->setFilterKeyColumn(DiveLocationModel::NAME);
view->setModel(proxy);
view->setModelColumn(DiveLocationModel::NAME);
view->setItemDelegate(new LocationFilterDelegate());
connect(this, &QLineEdit::textEdited, this, &DiveLocationLineEdit::setTemporaryDiveSiteName);
}
void DiveLocationLineEdit::refreshDiveSiteCache()
{
model->resetModel();
}
static struct dive_site *get_dive_site_name_start_which_str(const QString& str) {
struct dive_site *ds;
int i;
for_each_dive_site(i,ds) {
QString dsName(ds->name);
if (dsName.toLower().startsWith(str.toLower())) {
return ds;
}
}
return NULL;
}
void DiveLocationLineEdit::setTemporaryDiveSiteName(const QString& s)
{
QModelIndex i0 = model->index(0, DiveLocationModel::NAME);
QModelIndex i1 = model->index(1, DiveLocationModel::NAME);
model->setData(i0, text());
QString i1_name = INVALID_DIVE_SITE_NAME;
if (struct dive_site *ds = get_dive_site_name_start_which_str(text())) {
const QString orig_name = QString(ds->name).toLower();
const QString new_name = text().toLower();
if (new_name != orig_name)
i1_name = QString(ds->name);
}
model->setData(i1, i1_name );
proxy->invalidate();
}
void DiveLocationLineEdit::keyPressEvent(QKeyEvent *ev)
{
QLineEdit::keyPressEvent(ev);
if(ev->key() != Qt::Key_Left && ev->key() != Qt::Key_Right && !view->isVisible()) {
qDebug() << "Showing popup";
showPopup();
} else {
qDebug() << "Not showing popup";
}
}
void DiveLocationLineEdit::showPopup()
{
const QRect screen = QApplication::desktop()->availableGeometry(this);
const int maxVisibleItems = 5;
Qt::LayoutDirection dir = layoutDirection();
QPoint pos;
int rh, w;
int h = (view->sizeHintForRow(0) * qMin(maxVisibleItems, view->model()->rowCount()) + 3) + 3;
QScrollBar *hsb = view->horizontalScrollBar();
if (hsb && hsb->isVisible())
h += view->horizontalScrollBar()->sizeHint().height();
rh = height();
pos = mapToGlobal(QPoint(0, height() - 2));
w = width();
if (w > screen.width())
w = screen.width();
if ((pos.x() + w) > (screen.x() + screen.width()))
pos.setX(screen.x() + screen.width() - w);
if (pos.x() < screen.x())
pos.setX(screen.x());
int top = pos.y() - rh - screen.top() + 2;
int bottom = screen.bottom() - pos.y();
h = qMax(h, view->minimumHeight());
if (h > bottom) {
h = qMin(qMax(top, bottom), h);
if (top > bottom)
pos.setY(pos.y() - h - rh + 2);
}
view->setGeometry(pos.x(), pos.y(), w, h);
if (!view->isVisible())
view->show();
}
DiveLocationListView::DiveLocationListView(QWidget *parent)
{
}