[gnome-maps/wip/cdavis/post-port-cleanups: 4/13] general: Rework context menu




commit ed9ff3695b3b133ba993b20afa62231c60117540
Author: Christopher Davis <christopherdavis gnome org>
Date:   Sun Aug 21 12:53:28 2022 -0400

    general: Rework context menu
    
    The context menu had a few papercuts after the port, notably
    placement. The code was also a bit hard to follow - the actions
    were declared in mainWindow.js, but defined in contextMenu.js.
    
    This commit removes the ContextMenu class, and instead builds
    and displays the popover in mapView.js. This also allows
    us to move our actions to mapView.js using a custom action group.
    The placement is also fixed.

 data/ui/context-menu.ui                 |  16 +--
 po/POTFILES.in                          |   1 -
 src/contextMenu.js                      | 218 --------------------------------
 src/mainWindow.js                       |  10 --
 src/mapView.js                          | 189 +++++++++++++++++++++++++++
 src/org.gnome.Maps.src.gresource.xml.in |   1 -
 6 files changed, 195 insertions(+), 240 deletions(-)
---
diff --git a/data/ui/context-menu.ui b/data/ui/context-menu.ui
index 07100c3e..2904df1b 100644
--- a/data/ui/context-menu.ui
+++ b/data/ui/context-menu.ui
@@ -4,34 +4,30 @@
     <section>
       <item>
         <attribute name="label" translatable="yes">Route from here</attribute>
-        <attribute name="action">win.route-from-here</attribute>
+        <attribute name="action">view.route-from-here</attribute>
       </item>
       <item>
         <attribute name="label" translatable="yes">Add intermediate destination</attribute>
-        <attribute name="action">win.add-intermediate-destination</attribute>
+        <attribute name="action">view.add-intermediate-destination</attribute>
       </item>
       <item>
         <attribute name="label" translatable="yes">Route to here</attribute>
-        <attribute name="action">win.route-to-here</attribute>
+        <attribute name="action">view.route-to-here</attribute>
       </item>
     </section>
     <section>
       <item>
         <attribute name="label" translatable="yes">What's here?</attribute>
-        <attribute name="action">win.whats-here</attribute>
+        <attribute name="action">view.whats-here</attribute>
       </item>
       <item>
         <attribute name="label" translatable="yes">Copy location</attribute>
-        <attribute name="action">win.copy-location</attribute>
+        <attribute name="action">view.copy-location</attribute>
       </item>
       <item>
         <attribute name="label" translatable="yes">Add to OpenStreetMap</attribute>
-        <attribute name="action">win.add-osm-location</attribute>
+        <attribute name="action">view.add-osm-location</attribute>
       </item>
     </section>
   </menu>
-  <template class="Gjs_ContextMenu" parent="GtkPopoverMenu">
-    <property name="menu-model">context-menu</property>
-    <property name="has-arrow">False</property>
-  </template>
 </interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 29eecaca..f6826366 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -29,7 +29,6 @@ data/ui/zoom-in-dialog.ui
 lib/maps-file-tile-source.c
 lib/maps-osm.c
 src/application.js
-src/contextMenu.js
 src/exportViewDialog.js
 src/geoclue.js
 src/geoJSONSource.js
diff --git a/src/mainWindow.js b/src/mainWindow.js
index 5c4b806d..019c6d09 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -31,7 +31,6 @@ import Adw from 'gi://Adw';
 import Shumate from 'gi://Shumate';
 
 import {Application} from './application.js';
-import {ContextMenu} from './contextMenu.js';
 import {ExportViewDialog} from './exportViewDialog.js';
 import {FavoritesPopover} from './favoritesPopover.js';
 import * as Geoclue from './geoclue.js';
@@ -123,9 +122,6 @@ export class MainWindow extends Gtk.ApplicationWindow {
         this._initDND();
         this._initPlaceBar();
 
-        this._contextMenu = new ContextMenu({ mapView: this._mapView,
-                                              mainWindow: this });
-
         this._grid.attach(this._sidebar, 1, 0, 1, 2);
 
         /* for some reason, setting the title of the window through the .ui
@@ -239,12 +235,6 @@ export class MainWindow extends Gtk.ApplicationWindow {
             'export-as-image': {
                 onActivate: () => this._onExportActivated()
             },
-            'route-from-here': {},
-            'add-intermediate-destination': {},
-            'route-to-here': {},
-            'whats-here': {},
-            'copy-location': {},
-            'add-osm-location': {}
         };
 
         // when aerial tiles are available, add shortcuts to switch
diff --git a/src/mapView.js b/src/mapView.js
index 81993de3..e077a5dd 100644
--- a/src/mapView.js
+++ b/src/mapView.js
@@ -33,14 +33,19 @@ import {Application} from './application.js';
 import {BoundingBox} from './boundingBox.js';
 import * as Color from './color.js';
 import * as Geoclue from './geoclue.js';
+import * as GeocodeFactory from './geocode.js';
 import {GeoJSONShapeLayer} from './geoJSONShapeLayer.js';
 import {KmlShapeLayer} from './kmlShapeLayer.js';
 import {GpxShapeLayer} from './gpxShapeLayer.js';
 import {Location} from './location.js';
 import * as MapSource from './mapSource.js';
 import {MapWalker} from './mapWalker.js';
+import {OSMAccountDialog} from './osmAccountDialog.js';
+import {OSMEdit} from './osmEdit.js';
+import {OSMEditDialog} from './osmEditDialog.js';
 import {Place} from './place.js';
 import {PlaceMarker} from './placeMarker.js';
+import {RouteQuery} from './routeQuery.js';
 import * as Service from './service.js';
 import {ShapeLayer} from './shapeLayer.js';
 import {StoredRoute} from './storedRoute.js';
@@ -50,6 +55,7 @@ import {TransitWalkMarker} from './transitWalkMarker.js';
 import {TurnPointMarker} from './turnPointMarker.js';
 import {UserLocationMarker} from './userLocationMarker.js';
 import * as Utils from './utils.js';
+import {ZoomInDialog} from './zoomInDialog.js';
 
 const _LOCATION_STORE_TIMEOUT = 500;
 const MapMinZoom = 2;
@@ -153,6 +159,49 @@ export class MapView extends Gtk.Overlay {
         Application.geoclue.connect('notify::state',
                                     this._updateUserLocation.bind(this));
         this._connectRouteSignals();
+
+        let actions = {
+            'route-from-here': {
+                onActivate: () => this._onRouteFromHereActivated()
+            },
+            'add-intermediate-destination': {
+                onActivate: () => this._onAddIntermediateDestinationActivated()
+            },
+            'route-to-here': {
+                onActivate: () => this._onRouteToHereActivated()
+            },
+            'whats-here': {
+                onActivate: () => this._onWhatsHereActivated()
+            },
+            'copy-location': {
+                onActivate: () => this._onCopyLocationActivated()
+            },
+            'add-osm-location': {
+                onActivate: () => this._onAddOSMLocationActivated()
+            }
+        };
+
+        let actionGroup = new Gio.SimpleActionGroup();
+        Utils.addActions(actionGroup, actions, null);
+        this.insert_action_group('view', actionGroup);
+
+        this._routeFromHereAction = actionGroup.lookup('route-from-here');
+        this._routeToHereAction = actionGroup.lookup('route-to-here');
+        this._addIntermediateDestinationAction = actionGroup.lookup('add-intermediate-destination');
+
+        let builder = Gtk.Builder.new_from_resource('/org/gnome/Maps/ui/context-menu.ui');
+        let menuModel = builder.get_object('context-menu');
+        this._contextMenu = new Gtk.PopoverMenu({ menu_model: menuModel, has_arrow: false });
+        this._contextMenu.set_parent(this);
+
+        this._clickGesture = new Gtk.GestureClick({ button: Gdk.BUTTON_SECONDARY });
+        this._clickGesture.connect('pressed', this._onClickGesturePressed.bind(this));
+        this.add_controller(this._clickGesture);
+    }
+
+    vfunc_size_allocate(width, height, baseline) {
+        super.vfunc_size_allocate(width, height, baseline);
+        this._contextMenu.present();
     }
 
     zoomIn() {
@@ -336,6 +385,16 @@ export class MapView extends Gtk.Overlay {
             this._instructionMarkerLayer.remove_all();
             this.routeShowing = false;
         });
+
+        query.connect('notify::points', () => {
+            let query = Application.routeQuery;
+            let numPoints = query.points.length;
+
+            this._routeFromHereAction.enabled = numPoints < RouteQuery.MAX_QUERY_POINTS;
+            this._routeToHereAction.enabled = numPoints < RouteQuery.MAX_QUERY_POINTS;
+            this._addIntermediateDestinationAction.enabled =
+                query.filledPoints.length >= 2 && numPoints < RouteQuery.MAX_QUERY_POINTS;
+        });
     }
 
     _getStoredMapType() {
@@ -899,6 +958,136 @@ export class MapView extends Gtk.Overlay {
     onSetMarkerSelected(selectedMarker) {
         this.emit('marker-selected', selectedMarker);
     }
+
+    _onClickGesturePressed(gesture, n_presses, x, y) {
+        if (n_presses > 1) {
+            gesture.set_state(Gtk.EventSequenceState.DENIED);
+            return;
+        }
+
+        let event = gesture.get_current_event();
+        if (event.triggers_context_menu()) {
+            let viewport = this.map.viewport;
+            let rect = new Gdk.Rectangle({ x: x, y: y, width: 0, height: 0 });
+
+            [this._latitude, this._longitude] = viewport.widget_coords_to_location(this, x, y);
+
+            if (this.direction === Gtk.TextDirection.RTL) {
+                this._contextMenu.halign = Gtk.Align.END;
+            } else {
+                this._contextMenu.halign = Gtk.Align.START;
+            }
+
+            this._contextMenu.pointing_to = rect;
+            this._contextMenu.popup();
+            gesture.set_state(Gtk.EventSequenceState.CLAIMED);
+        }
+
+        gesture.set_state(Gtk.EventSequenceState.DENIED);
+    }
+
+    _onRouteFromHereActivated() {
+        let query = Application.routeQuery;
+        let location = new Location({ latitude: this._latitude,
+                                      longitude: this._longitude,
+                                      accuracy: 0 });
+        let place = new Place({ location: location, store: false });
+
+        query.points[0].place = place;
+    }
+
+    _onAddIntermediateDestinationActivated() {
+        let query = Application.routeQuery;
+        let location = new Location({ latitude: this._latitude,
+                                      longitude: this._longitude,
+                                      accuracy: 0 });
+        let place = new Place({ location: location, store: false });
+
+        query.addPoint(-1).place = place;
+    }
+
+    _onRouteToHereActivated() {
+        let query = Application.routeQuery;
+        let location = new Location({ latitude: this._latitude,
+                                      longitude: this._longitude,
+                                      accuracy: 0 });
+        let place = new Place({ location: location, store: false });
+
+        query.points.last().place = place;
+    }
+
+    _onWhatsHereActivated() {
+        GeocodeFactory.getGeocoder().reverse(this._latitude, this._longitude,
+                                      (place) => {
+            if (place) {
+                this.showPlace(place, true);
+            } else {
+                let msg = _("Nothing found here!");
+
+                Utils.showDialog(msg, Gtk.MessageType.INFO, this._mainWindow);
+            }
+        });
+    }
+
+    _onCopyLocationActivated() {
+        let location = new Location({ latitude: this._latitude,
+                                      longitude: this._longitude,
+                                      accuracy: 0 });
+        let clipboard = this.get_clipboard();
+        let uri = location.to_uri(GeocodeGlib.LocationURIScheme.GEO);
+
+        clipboard.set(uri);
+    }
+
+    _onAddOSMLocationActivated() {
+        let osmEdit = Application.osmEdit;
+        /* if the user is not already signed in, show the account dialog */
+        if (!osmEdit.isSignedIn) {
+            let dialog = osmEdit.createAccountDialog(this._mainWindow, true);
+
+            dialog.show();
+            dialog.connect('response', (dialog, response) => {
+                dialog.destroy();
+                if (response === OSMAccountDialog.Response.SIGNED_IN)
+                    this._addOSMLocation();
+            });
+
+            return;
+        }
+
+        this._addOSMLocation();
+    }
+
+    _addOSMLocation() {
+        let osmEdit = Application.osmEdit;
+        let viewport = this.map.viewport;
+
+        if (viewport.zoom_level < OSMEdit.MIN_ADD_LOCATION_ZOOM_LEVEL) {
+            let zoomInDialog =
+                new ZoomInDialog({ longitude: this._longitude,
+                                   latitude: this._latitude,
+                                   map: this.map,
+                                   transient_for: this._mainWindow,
+                                   modal: true });
+
+            zoomInDialog.connect('response', () => zoomInDialog.destroy());
+            zoomInDialog.show();
+            return;
+        }
+
+        let dialog =
+            osmEdit.createEditNewDialog(this._mainWindow,
+                                        this._latitude, this._longitude);
+
+        dialog.show();
+        dialog.connect('response', (dialog, response) => {
+            dialog.destroy();
+            if (response === OSMEditDialog.Response.UPLOADED) {
+                Utils.showDialog(_("Location was added to the map, note that it may take a while before it 
shows on the map and in search results."),
+                                 Gtk.MessageType.INFO, this._mainWindow);
+            }
+        });
+    }
 }
 
 GObject.registerClass({
diff --git a/src/org.gnome.Maps.src.gresource.xml.in b/src/org.gnome.Maps.src.gresource.xml.in
index 84abec3f..5ae187da 100644
--- a/src/org.gnome.Maps.src.gresource.xml.in
+++ b/src/org.gnome.Maps.src.gresource.xml.in
@@ -5,7 +5,6 @@
     <file>application.js</file>
     <file>boundingBox.js</file>
     <file>color.js</file>
-    <file>contextMenu.js</file>
     <file>constants.js</file>
     <file>epaf.js</file>
     <file>exportViewDialog.js</file>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]