/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2021 Jon Beniston, M7RCE // // Copyright (C) 2020 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // // the Free Software Foundation as version 3 of the License, or // // (at your option) any later version. // // // // This program is distributed in the hope that it will be useful, // // but WITHOUT ANY WARRANTY; without even the implied warranty of // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // // GNU General Public License V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #ifndef INCLUDE_FEATURE_MAPGUI_H_ #define INCLUDE_FEATURE_MAPGUI_H_ #include #include #include #include "feature/featuregui.h" #include "util/messagequeue.h" #include "util/azel.h" #include "pipes/pipeendpoint.h" #include "mapsettings.h" #include "SWGMapItem.h" #include "mapbeacondialog.h" class PluginAPI; class FeatureUISet; class Map; namespace Ui { class MapGUI; } class MapGUI; class MapModel; struct Beacon; // Information required about each item displayed on the map class MapItem { public: MapItem(const PipeEndPoint *sourcePipe, quint32 sourceMask, SWGSDRangel::SWGMapItem *mapItem) { m_sourcePipe = sourcePipe; m_sourceMask = sourceMask; m_name = *mapItem->getName(); m_latitude = mapItem->getLatitude(); m_longitude = mapItem->getLongitude(); m_altitude = mapItem->getAltitude(); m_image = *mapItem->getImage(); m_imageRotation = mapItem->getImageRotation(); m_imageMinZoom = mapItem->getImageMinZoom(); QString *text = mapItem->getText(); if (text != nullptr) m_text = *text; findFrequency(); } void update(SWGSDRangel::SWGMapItem *mapItem) { m_latitude = mapItem->getLatitude(); m_longitude = mapItem->getLongitude(); m_altitude = mapItem->getAltitude(); m_image = *mapItem->getImage(); m_imageRotation = mapItem->getImageRotation(); m_imageMinZoom = mapItem->getImageMinZoom(); QString *text = mapItem->getText(); if (text != nullptr) m_text = *text; findFrequency(); } QGeoCoordinate getCoordinates() { QGeoCoordinate coords; coords.setLatitude(m_latitude); coords.setLongitude(m_longitude); return coords; } private: void findFrequency(); friend MapModel; const PipeEndPoint *m_sourcePipe; // Channel/feature that created the item quint32 m_sourceMask; // Source bitmask as per MapSettings::SOURCE_* constants QString m_name; float m_latitude; float m_longitude; float m_altitude; // In metres QString m_image; int m_imageRotation; int m_imageMinZoom; QString m_text; double m_frequency; // Frequency to set QString m_frequencyString; }; // Model used for each item on the map class MapModel : public QAbstractListModel { Q_OBJECT public: using QAbstractListModel::QAbstractListModel; enum MarkerRoles { positionRole = Qt::UserRole + 1, mapTextRole = Qt::UserRole + 2, mapTextVisibleRole = Qt::UserRole + 3, mapImageVisibleRole = Qt::UserRole + 4, mapImageRole = Qt::UserRole + 5, mapImageRotationRole = Qt::UserRole + 6, mapImageMinZoomRole = Qt::UserRole + 7, bubbleColourRole = Qt::UserRole + 8, selectedRole = Qt::UserRole + 9, targetRole = Qt::UserRole + 10, frequencyRole = Qt::UserRole + 11, frequencyStringRole = Qt::UserRole + 12 }; MapModel(MapGUI *gui) : m_gui(gui), m_target(-1), m_sources(-1) { } Q_INVOKABLE void add(MapItem *item) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_items.append(item); m_selected.append(false); endInsertRows(); } void update(const PipeEndPoint *source, SWGSDRangel::SWGMapItem *swgMapItem, quint32 sourceMask=0); void updateTarget(); void update(MapItem *item) { int row = m_items.indexOf(item); if (row >= 0) { QModelIndex idx = index(row); emit dataChanged(idx, idx); if (row == m_target) updateTarget(); } } void remove(MapItem *item) { int row = m_items.indexOf(item); if (row >= 0) { beginRemoveRows(QModelIndex(), row, row); m_items.removeAt(row); m_selected.removeAt(row); if (row == m_target) m_target = -1; endRemoveRows(); } } Q_INVOKABLE void moveToFront(int oldRow) { // Last item in list is drawn on top, so remove than add to end of list if (oldRow < m_items.size() - 1) { bool wasTarget = m_target == oldRow; MapItem *item = m_items[oldRow]; bool wasSelected = m_selected[oldRow]; remove(item); add(item); int newRow = m_items.size() - 1; if (wasTarget) m_target = newRow; m_selected[newRow] = wasSelected; QModelIndex idx = index(newRow); emit dataChanged(idx, idx); } } Q_INVOKABLE void moveToBack(int oldRow) { // First item in list is drawn first, so remove item then add to front of list if ((oldRow < m_items.size()) && (oldRow > 0)) { bool wasTarget = m_target == oldRow; int newRow = 0; // See: https://forum.qt.io/topic/122991/changing-the-order-mapquickitems-are-drawn-on-a-map //QModelIndex parent; //beginMoveRows(parent, oldRow, oldRow, parent, newRow); beginResetModel(); m_items.move(oldRow, newRow); m_selected.move(oldRow, newRow); if (wasTarget) m_target = newRow; //endMoveRows(); endResetModel(); //emit dataChanged(index(oldRow), index(newRow)); } } MapItem *findMapItem(const PipeEndPoint *source, const QString& name) { // FIXME: Should consider adding a QHash for this QListIterator i(m_items); while (i.hasNext()) { MapItem *item = i.next(); if ((item->m_name == name) && (item->m_sourcePipe == source)) return item; } return nullptr; } MapItem *findMapItem(const QString& name) { QListIterator i(m_items); while (i.hasNext()) { MapItem *item = i.next(); if (item->m_name == name) return item; } return nullptr; } int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) return m_items.count(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override { (void) index; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } void allUpdated() { for (int i = 0; i < m_items.count(); i++) { QModelIndex idx = index(i); emit dataChanged(idx, idx); } } void removeAll() { beginRemoveRows(QModelIndex(), 0, m_items.count()); m_items.clear(); m_selected.clear(); endRemoveRows(); } void setDisplayNames(bool displayNames) { m_displayNames = displayNames; allUpdated(); } Q_INVOKABLE void setFrequency(double frequency); QHash roleNames() const { QHash roles; roles[positionRole] = "position"; roles[mapTextRole] = "mapText"; roles[mapTextVisibleRole] = "mapTextVisible"; roles[mapImageVisibleRole] = "mapImageVisible"; roles[mapImageRole] = "mapImage"; roles[mapImageRotationRole] = "mapImageRotation"; roles[mapImageMinZoomRole] = "mapImageMinZoom"; roles[bubbleColourRole] = "bubbleColour"; roles[selectedRole] = "selected"; roles[targetRole] = "target"; roles[frequencyRole] = "frequency"; roles[frequencyStringRole] = "frequencyString"; return roles; } // Set the sources of data we should display void setSources(quint32 sources) { m_sources = sources; allUpdated(); } private: MapGUI *m_gui; QList m_items; QList m_selected; int m_target; // Row number of current target, or -1 for none bool m_displayNames; quint32 m_sources; }; class MapGUI : public FeatureGUI { Q_OBJECT public: static MapGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature); virtual void destroy(); void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } AzEl *getAzEl() { return &m_azEl; } Map *getMap() { return m_map; } quint32 getSourceMask(const PipeEndPoint *sourcePipe); static QString getBeaconFilename(); QList *getBeacons() { return m_beacons; } void setBeacons(QList *beacons); void find(const QString& target); private: Ui::MapGUI* ui; PluginAPI* m_pluginAPI; FeatureUISet* m_featureUISet; MapSettings m_settings; bool m_doApplySettings; QList m_availablePipes; Map* m_map; MessageQueue m_inputMessageQueue; MapModel m_mapModel; AzEl m_azEl; // Position of station QList *m_beacons; MapBeaconDialog m_beaconDialog; explicit MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); virtual ~MapGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); void applyMapSettings(); void displaySettings(); bool handleMessage(const Message& message); void geoReply(); void leaveEvent(QEvent*); void enterEvent(QEvent*); static QString getDataDir(); private slots: void onMenuDialogCalled(const QPoint &p); void onWidgetRolled(QWidget* widget, bool rollDown); void handleInputMessages(); void on_displayNames_clicked(bool checked=false); void on_find_returnPressed(); void on_maidenhead_clicked(bool checked=false); void on_deleteAll_clicked(); void on_displaySettings_clicked(); void on_mapTypes_currentIndexChanged(int index); void on_beacons_clicked(); }; #endif // INCLUDE_FEATURE_MAPGUI_H_