[gnome-maps] Add export to PNG function to context menu
- From: Jonas Danielsson <jonasdn src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-maps] Add export to PNG function to context menu
- Date: Tue, 1 Dec 2015 18:59:26 +0000 (UTC)
commit 08ad2b76dc83d145fc05d7bd94ba4fcc303fdcfb
Author: Jonas Danielsson <jonas threetimestwo org>
Date: Tue Nov 17 15:46:52 2015 +0100
Add export to PNG function to context menu
https://bugzilla.gnome.org/show_bug.cgi?id=753653
data/org.gnome.Maps.data.gresource.xml | 1 +
data/ui/context-menu.ui | 7 ++
data/ui/export-view-dialog.ui | 127 +++++++++++++++++++++++
src/contextMenu.js | 38 +++++++-
src/exportViewDialog.js | 174 ++++++++++++++++++++++++++++++++
src/org.gnome.Maps.src.gresource.xml | 1 +
6 files changed, 347 insertions(+), 1 deletions(-)
---
diff --git a/data/org.gnome.Maps.data.gresource.xml b/data/org.gnome.Maps.data.gresource.xml
index 6ba6040..7e25b57 100644
--- a/data/org.gnome.Maps.data.gresource.xml
+++ b/data/org.gnome.Maps.data.gresource.xml
@@ -6,6 +6,7 @@
<file preprocess="xml-stripblanks">ui/busy-marker.ui</file>
<file preprocess="xml-stripblanks">ui/check-in-dialog.ui</file>
<file preprocess="xml-stripblanks">ui/context-menu.ui</file>
+ <file preprocess="xml-stripblanks">ui/export-view-dialog.ui</file>
<file preprocess="xml-stripblanks">ui/favorites-popover.ui</file>
<file preprocess="xml-stripblanks">ui/instruction-row.ui</file>
<file preprocess="xml-stripblanks">ui/layers-popover.ui</file>
diff --git a/data/ui/context-menu.ui b/data/ui/context-menu.ui
index 41220e6..9a5e868 100644
--- a/data/ui/context-menu.ui
+++ b/data/ui/context-menu.ui
@@ -17,5 +17,12 @@
<property name="visible">True</property>
</object>
</child>
+ <child>
+ <object class="GtkMenuItem" id="exportItem">
+ <property name="name">export-item</property>
+ <property name="label" translatable="yes">Export to PNG</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
</template>
</interface>
diff --git a/data/ui/export-view-dialog.ui b/data/ui/export-view-dialog.ui
new file mode 100644
index 0000000..092c718
--- /dev/null
+++ b/data/ui/export-view-dialog.ui
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="Gjs_ExportViewDialog" parent="GtkDialog">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="use_header_bar">1</property>
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar" id="headerBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_close_button">False</property>
+ <property name="title" translatable="yes">Export view</property>
+ <style>
+ <class name="titlebar"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="cancelButton">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="text-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="exportButton">
+ <property name="label" translatable="yes">_Export</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="valign">center</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="text-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="contentArea">
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">5</property>
+ <property name="margin_right">5</property>
+ <property name="margin_top">5</property>
+ <property name="margin_bottom">5</property>
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">8</property>
+ <child>
+ <object class="GtkAlignment" id="alignment">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkFrame" id="frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">out</property>
+ <child>
+ <object class="GtkDrawingArea" id="previewArea">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="filenameEntry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="valign">start</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">32</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="fileChooserButton">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="vexpand">True</property>
+ <property name="action">select-folder</property>
+ <property name="local_only">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/contextMenu.js b/src/contextMenu.js
index fd4381f..39633b1 100644
--- a/src/contextMenu.js
+++ b/src/contextMenu.js
@@ -19,6 +19,7 @@
* Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
*/
+const Champlain = imports.gi.Champlain;
const Clutter = imports.gi.Clutter;
const Gdk = imports.gi.Gdk;
const Geocode = imports.gi.GeocodeGlib;
@@ -26,6 +27,7 @@ const Gtk = imports.gi.Gtk;
const Mainloop = imports.mainloop;
const Application = imports.application;
+const ExportViewDialog = imports.exportViewDialog;
const Lang = imports.lang;
const Location = imports.location;
const Utils = imports.utils;
@@ -35,7 +37,8 @@ const ContextMenu = new Lang.Class({
Extends: Gtk.Menu,
Template: 'resource:///org/gnome/Maps/ui/context-menu.ui',
InternalChildren: [ 'whatsHereItem',
- 'geoURIItem' ],
+ 'geoURIItem',
+ 'exportItem' ],
_init: function(params) {
this._mapView = params.mapView;
@@ -50,6 +53,8 @@ const ContextMenu = new Lang.Class({
this._onWhatsHereActivated.bind(this));
this._geoURIItem.connect('activate',
this._onGeoURIActivated.bind(this));
+ this._exportItem.connect('activate',
+ this._onExportActivated.bind(this));
},
_onButtonReleaseEvent: function(actor, event) {
@@ -85,5 +90,36 @@ const ContextMenu = new Lang.Class({
let uri = location.to_uri(Geocode.LocationURIScheme.GEO);
clipboard.set_text(uri, uri.length);
+ },
+
+ _activateExport: function() {
+ let view = this._mapView.view;
+ let surface = view.to_surface(true);
+ let bbox = view.get_bounding_box();
+ let [latitude, longitude] = bbox.get_center();
+
+ let dialog = new ExportViewDialog.ExportViewDialog({
+ transient_for: this.get_toplevel(),
+ surface: surface,
+ latitude: latitude,
+ longitude: longitude,
+ mapView: this._mapView
+ });
+
+ dialog.run();
+ dialog.destroy();
+ },
+
+ _onExportActivated: function() {
+ if (this._mapView.view.state === Champlain.State.DONE) {
+ this._activateExport();
+ } else {
+ let notifyId = this._mapView.view.connect('notify::state', (function() {
+ if (this._mapView.view.state === Champlain.State.DONE) {
+ this._mapView.view.disconnect(notifyId);
+ this._activateExport();
+ }
+ }).bind(this));
+ }
}
});
diff --git a/src/exportViewDialog.js b/src/exportViewDialog.js
new file mode 100644
index 0000000..2894ac9
--- /dev/null
+++ b/src/exportViewDialog.js
@@ -0,0 +1,174 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * GNOME Maps 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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps 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
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Jonas Danielson <jonas threetimestwo org>
+ */
+
+const Cairo = imports.cairo;
+const Gdk = imports.gi.Gdk;
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+
+const Utils = imports.utils;
+
+const Response = {
+ SUCCESS: 0,
+ CANCEL: 1
+};
+
+const _PREVIEW_WIDTH = 150;
+
+const ExportViewDialog = new Lang.Class({
+ Name: 'ExportViewDialog',
+ Extends: Gtk.Dialog,
+ Template: 'resource:///org/gnome/Maps/ui/export-view-dialog.ui',
+ InternalChildren: [ 'exportButton',
+ 'cancelButton',
+ 'filenameEntry',
+ 'fileChooserButton',
+ 'previewArea' ],
+
+ _init: function(params) {
+ this._surface = params.surface;
+ delete params.surface;
+
+ this._latitude = params.latitude;
+ delete params.latitude;
+
+ this._longitude = params.longitude;
+ delete params.longitude;
+
+ this._mapView = params.mapView;
+ delete params.mapView;
+
+ params.use_header_bar = true;
+ this.parent(params);
+
+ this._cancelButton.connect('clicked',
+ this.response.bind(this, Response.CANCEL));
+ this._exportButton.connect('clicked', this._exportView.bind(this));
+ this._filenameEntry.connect('changed',
+ this._onFileNameChanged.bind(this));
+ this._fileChooserButton.connect('file-set',
+ this._onFolderChanged.bind(this));
+
+
+ this._folder = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES);
+ if (!this._folder)
+ this._folder = GLib.get_user_data_dir();
+
+ this._filenameEntry.text = this._fileName = this._getName();
+ this._fileChooserButton.set_current_folder(this._folder);
+ this._setupPreviewArea();
+ },
+
+ _getName: function() {
+ return 'Maps at %f, %f.png'.format(this._latitude.toFixed(2),
+ this._longitude.toFixed(2));
+ },
+
+ _setupPreviewArea: function() {
+ let [surfaceWidth, surfaceHeight] = this._mapView.view.get_size();
+
+ let width = _PREVIEW_WIDTH;
+ this._scaleFactor = width / surfaceWidth;
+ let height = surfaceHeight * this._scaleFactor;
+
+ this._previewArea.set_size_request(width, height);
+ this._previewArea.connect('draw', this._drawPreview.bind(this));
+ },
+
+ _drawPreview: function(widget, cr) {
+ cr.setOperator(Cairo.Operator.CLEAR);
+ cr.paint();
+ cr.setOperator(Cairo.Operator.OVER);
+
+ cr.scale(this._scaleFactor, this._scaleFactor)
+ cr.setSourceSurface(this._surface, 0, 0);
+ cr.paint();
+ },
+
+ _onFileNameChanged: function() {
+ let name = GLib.filename_from_utf8(this._filenameEntry.text, -1)[0];
+ name = name.toString();
+ if (!name) {
+ this._exportButton.sensitive= false;
+ return;
+ }
+
+ try {
+ GLib.build_filenamev([this._folder, name]);
+ this._exportButton.sensitive = true;
+ this._fileName = name;
+ } catch(e) {
+ this._exportButton.sensitive = false;
+ }
+ },
+
+ _onFolderChanged: function() {
+ let folder = this._fileChooserButton.get_filename();
+
+ if (!GLib.file_test(folder, GLib.FileTest.IS_DIR)) {
+ this._exportButton.sensitive= false;
+ return;
+ }
+ if (!GLib.file_test(folder, GLib.FileTest.EXISTS)) {
+ this._exportButton.sensitive = false;
+ return;
+ }
+
+ this._exportButton.sensitive = true;
+ this._folder = folder;
+ },
+
+ _exportView: function() {
+ let [width, height] = this._mapView.view.get_size();
+ let pixbuf = Gdk.pixbuf_get_from_surface(this._surface, 0, 0, width, height);
+ let path = GLib.build_filenamev([this._folder, this._fileName]);
+
+ try {
+ pixbuf.savev(path, "png", [], []);
+ this.response(Response.SUCCESS);
+ } catch(e) {
+ Utils.debug('failed to export view: ' + e.message);
+ let details = null;
+
+ if (e.matches(GLib.FileError, GLib.FileError.ROFS))
+ details = _("Filesystem is read only");
+ else if (e.matches(GLib.FileError, GLib.FileError.ACCES))
+ details = _("You do not have permission to save there");
+ else if (e.matches(GLib.FileError, GLib.FileError.NOENT))
+ details = _("The directory does not exists");
+ else if (e.matches(GLib.FileError, GLib.FileError.ISDIR))
+ details = _("No filename specified");
+
+ let dialog = new Gtk.MessageDialog({
+ transient_for: this,
+ destroy_with_parent: true,
+ message_type: Gtk.MessageType.ERROR,
+ buttons: Gtk.ButtonsType.OK,
+ modal: true,
+ text: _("Unable to export view"),
+ secondary_text: details
+ });
+
+ dialog.run();
+ dialog.destroy();
+ }
+ }
+});
diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index 6a10a06..8d171f1 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -9,6 +9,7 @@
<file>contactPlace.js</file>
<file>contextMenu.js</file>
<file>epaf.js</file>
+ <file>exportViewDialog.js</file>
<file>facebookBackend.js</file>
<file>favoritesPopover.js</file>
<file>foursquareBackend.js</file>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]