[gnome-documents] Add a presentation mode
- From: William Jon McCann <mccann src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-documents] Add a presentation mode
- Date: Fri, 15 Feb 2013 21:57:07 +0000 (UTC)
commit 61d077f11bbd7366fbc8a1ade225356ef5978ae6
Author: William Jon McCann <jmccann redhat com>
Date: Wed Feb 13 16:45:46 2013 -0500
Add a presentation mode
https://bugzilla.gnome.org/show_bug.cgi?id=691255
.gitmodules | 3 +
Makefile.am | 2 +-
autogen.sh | 4 +
configure.ac | 12 ++-
egg-list-box | 1 +
m4/ax_config_dir.m4 | 109 ++++++++++++++++++++
src/Makefile-js.am | 1 +
src/Makefile.am | 2 +-
src/application.js | 6 +
src/presentation.js | 223 +++++++++++++++++++++++++++++++++++++++++
src/preview.js | 54 ++++++++++-
src/resources/preview-menu.ui | 5 +
12 files changed, 418 insertions(+), 4 deletions(-)
---
diff --git a/.gitmodules b/.gitmodules
index bfd964e..ee3430a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "libgd"]
path = libgd
url = git://git.gnome.org/libgd
+[submodule "egg-list-box"]
+ path = egg-list-box
+ url = git://git.gnome.org/egg-list-box
diff --git a/Makefile.am b/Makefile.am
index 1b458e8..69386bc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
ACLOCAL_AMFLAGS = -I m4 -I libgd ${ACLOCAL_FLAGS}
-SUBDIRS = libgd src data po
+SUBDIRS = libgd egg-list-box src data po
EXTRA_DIST = \
autogen.sh \
diff --git a/autogen.sh b/autogen.sh
index b87ffaa..8eeeed4 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -20,6 +20,10 @@ which gnome-autogen.sh || {
git submodule update --init --recursive
+cd egg-list-box
+sh autogen.sh --no-configure
+cd ..
+
REQUIRED_AUTOCONF_VERSION=2.59
REQUIRED_AUTOMAKE_VERSION=1.9
REQUIRED_INTLTOOL_VERSION=0.40.0
diff --git a/configure.ac b/configure.ac
index 18fcab4..2450901 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,6 +49,15 @@ GLIB_GSETTINGS
AC_CHECK_LIBM
AC_SUBST(LIBM)
+# EggListBox submodule
+prev_top_build_prefix=$ac_top_build_prefix
+prev_ac_configure_args=$ac_configure_args
+AX_CONFIG_DIR([egg-list-box])
+ac_top_build_prefix=$prev_top_build_prefix
+ac_configure_args=$prev_ac_configure_args
+
+export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:"$ac_top_build_prefix"egg-list-box
+
EVINCE_MIN_VERSION=3.7.4
WEBKITGTK_MIN_VERSION=1.10.0
GLIB_MIN_VERSION=2.35.1
@@ -73,7 +82,8 @@ PKG_CHECK_MODULES(DOCUMENTS,
tracker-sparql-0.16 >= $TRACKER_MIN_VERSION
goa-1.0 >= $GOA_MIN_VERSION
libgdata >= $GDATA_MIN_VERSION
- zapojit-0.0 >= $ZAPOJIT_MIN_VERSION)
+ zapojit-0.0 >= $ZAPOJIT_MIN_VERSION
+ egg-list-box)
PKG_CHECK_MODULES(MINER,
tracker-sparql-0.16 >= $TRACKER_MIN_VERSION
diff --git a/egg-list-box b/egg-list-box
new file mode 160000
index 0000000..491db4b
--- /dev/null
+++ b/egg-list-box
@@ -0,0 +1 @@
+Subproject commit 491db4b5eb181f1e13b909644dfd0ad971231099
diff --git a/m4/ax_config_dir.m4 b/m4/ax_config_dir.m4
new file mode 100644
index 0000000..0ba313f
--- /dev/null
+++ b/m4/ax_config_dir.m4
@@ -0,0 +1,109 @@
+dnl Copied from Audacity 1.3.10 which itself is licensed under the GPL v2 or
+dnl any later version
+
+dnl Function to configure a sub-library now, because we need to know the result
+dnl of the configuration now in order to take decisions.
+dnl We don't worry about whether the configuration worked or not - it is
+dnl assumed that the next thing after this will be a package-specific check to
+dnl see if the package is actually available. (Hint: use pkg-config and
+dnl -uninstalled.pc files if available).
+dnl code based on a simplification of _AC_OUTPUT_SUBDIRS in
+dnl /usr/share/autoconf/autoconf/status.m4 which implements part of
+dnl AC_CONFIG_SUBDIRS
+
+AC_DEFUN([AX_CONFIG_DIR],
+[AC_REQUIRE([AC_DISABLE_OPTION_CHECKING])]
+[m4_append([_AC_LIST_SUBDIRS], [$1], [])]
+[
+ # Remove --cache-file and --srcdir arguments so they do not pile up.
+ ax_sub_configure_args=
+ ax_prev=
+ eval "set x $ac_configure_args"
+ shift
+ for ax_arg
+ do
+ if test -n "$ax_prev"; then
+ ax_prev=
+ continue
+ fi
+ case $ax_arg in
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ax_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* \
+ | --c=*)
+ ;;
+ --config-cache | -C)
+ ;;
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ax_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ ;;
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ax_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ ;;
+ *)
+ case $ax_arg in
+ *\'*) ax_arg=`echo "$ax_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ ax_sub_configure_args="$ax_sub_configure_args '$ax_arg'" ;;
+ esac
+ done
+
+ # Always prepend --prefix to ensure using the same prefix
+ # in subdir configurations.
+ ax_arg="--prefix=$prefix"
+ case $ax_arg in
+ *\'*) ax_arg=`echo "$ax_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ ax_sub_configure_args="'$ax_arg' $ax_sub_configure_args"
+
+ # Pass --silent
+ if test "$silent" = yes; then
+ ax_sub_configure_args="--silent $ax_sub_configure_args"
+ fi
+
+ ax_popdir=`pwd`
+ AC_MSG_NOTICE([Configuring sources in $1])
+ dnl for out-of-place builds srcdir and builddir will be different, and
+ dnl builddir may not exist, so we must create it
+ AS_MKDIR_P(["$1"])
+ dnl and also set the variables. As this isn't autoconf, the following may be
+ dnl risky:
+ _AC_SRCDIRS(["$1"])
+ cd "$1"
+
+ # Check for guested configure; otherwise get Cygnus style configure.
+ if test -f "configure.gnu"; then
+ ax_sub_configure=$ac_srcdir/configure.gnu
+ elif test -f "$ac_srcdir/configure"; then
+ ax_sub_configure=$ac_srcdir/configure
+ elif test -f "$ac_srcdir/configure.in"; then
+ # This should be Cygnus configure.
+ ax_sub_configure=$ac_aux_dir/configure
+ else
+ AC_MSG_WARN([no configuration information is in $1])
+ ax_sub_configure=
+ fi
+
+ # The recursion is here.
+ if test -n "$ax_sub_configure"; then
+ # Make the cache file name correct relative to the subdirectory.
+ case $cache_file in
+ [[\\/]]* | ?:[[\\/]]* ) ax_sub_cache_file=$cache_file ;;
+ *) # Relative name.
+ ax_sub_cache_file=$ac_top_build_prefix$cache_file ;;
+ esac
+
+ AC_MSG_NOTICE([running $SHELL $ax_sub_configure $ax_sub_configure_args --cache-file=$ax_sub_cache_file
--srcdir=$ac_srcdir])
+ # The eval makes quoting arguments work.
+ eval "\$SHELL \"\$ax_sub_configure\" $ax_sub_configure_args \
+ --cache-file=\"\$ax_sub_cache_file\" --srcdir=\"\$ax_srcdir\""
+ fi
+
+ cd "$ax_popdir"
+ AC_MSG_NOTICE([Done configuring in $1])
+])
+
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index d81a992..9387e0f 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -12,6 +12,7 @@ dist_js_DATA = \
miners.js \
notifications.js \
places.js \
+ presentation.js \
preview.js \
properties.js\
query.js \
diff --git a/src/Makefile.am b/src/Makefile.am
index f8d7966..07f82bd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -51,7 +51,7 @@ resource_DATA = gnome-documents.gresource
CLEANFILES += gnome-documents.gresource
-gir_DATA += $(INTROSPECTION_GIRS)
+gir_DATA += ../egg-list-box/Egg-1.0.gir $(INTROSPECTION_GIRS)
typelib_DATA += $(gir_DATA:.gir=.typelib)
CLEANFILES += $(gir_DATA) $(typelib_DATA)
diff --git a/src/application.js b/src/application.js
index a7dbbe9..b255afd 100644
--- a/src/application.js
+++ b/src/application.js
@@ -371,6 +371,12 @@ const Application = new Lang.Class({
window_mode: WindowMode.WindowMode.PREVIEW },
{ name: 'view-current',
window_mode: WindowMode.WindowMode.EDIT },
+ { name: 'present-current',
+ window_mode: WindowMode.WindowMode.PREVIEW,
+ callback: this._onActionToggle,
+ state: GLib.Variant.new('b', false),
+ accel: 'F5',
+ },
{ name: 'print-current', accel: '<Primary>p',
callback: this._onActionPrintCurrent,
window_mode: WindowMode.WindowMode.PREVIEW },
diff --git a/src/presentation.js b/src/presentation.js
new file mode 100644
index 0000000..b048c79
--- /dev/null
+++ b/src/presentation.js
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * Gnome Documents 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 Documents 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 Documents; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+const Egg = imports.gi.Egg;
+const EvDocument = imports.gi.EvinceDocument;
+const EvView = imports.gi.EvinceView;
+const GnomeDesktop = imports.gi.GnomeDesktop;
+const GdPrivate = imports.gi.GdPrivate;
+const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
+const _ = imports.gettext.gettext;
+
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Signals = imports.signals;
+
+const Application = imports.application;
+
+const PresentationWindow = new Lang.Class({
+ Name: 'PresentationWindow',
+
+ _init: function(model) {
+ this._model = model;
+ this._inhibitId = 0;
+
+ let toplevel = Application.application.get_windows()[0];
+ this.window = new Gtk.Window ({ type: Gtk.WindowType.TOPLEVEL,
+ transient_for: toplevel,
+ destroy_with_parent: true,
+ title: _("Presentation"),
+ hexpand: true });
+ this.window.connect('key-press-event',
+ Lang.bind(this, this._onKeyPressEvent));
+
+ this._model.connect('page-changed',
+ Lang.bind(this, this._onPageChanged));
+
+ this._createView();
+ this.window.fullscreen();
+ this.window.show_all();
+ },
+
+ _onPageChanged: function() {
+ this.view.current_page = this._model.page;
+ },
+
+ _onPresentationPageChanged: function() {
+ this._model.page = this.view.current_page;
+ },
+
+ _onKeyPressEvent: function(widget, event) {
+ let keyval = event.get_keyval()[1];
+ if (keyval == Gdk.KEY_Escape)
+ this.close();
+ },
+
+ setOutput: function(output) {
+ this.window.move(output.x, output.y);
+ },
+
+ _createView: function() {
+ let doc = this._model.get_document();
+ let inverted = this._model.inverted_colors;
+ let page = this._model.page;
+ let rotation = this._model.rotation;
+ this.view = new EvView.ViewPresentation({ document: doc,
+ current_page: page,
+ rotation: rotation,
+ inverted_colors: inverted });
+ this.view.connect('finished', Lang.bind(this, this.close));
+ this.view.connect('notify::current-page', Lang.bind(this, this._onPresentationPageChanged));
+
+ this.window.add(this.view);
+ this.view.show();
+
+ this._inhibitIdle();
+ },
+
+ close: function() {
+ this._uninhibitIdle();
+ this.window.destroy();
+ },
+
+ _inhibitIdle: function() {
+ this._inhibitId = Application.application.inhibit(null,
+ Gtk.ApplicationInhibitFlags.IDLE,
+ _("Running in presentation mode"));
+ },
+
+ _uninhibitIdle: function() {
+ if (this._inhibitId == 0)
+ return;
+
+ Application.application.uninhibit(this._inhibitId);
+ this._inhibitId = 0;
+ }
+});
+
+const PresentationOutputChooser = new Lang.Class({
+ Name: 'PresentationOutputChooser',
+
+ _init: function(outputs) {
+ this.output = null;
+ this._outputs = outputs;
+ this._createWindow();
+ this._populateList();
+ this.window.show_all();
+ },
+
+ _populateList: function() {
+ for (let i = 0; i < this._outputs.list.length; i++) {
+ let output = this._outputs.list[i];
+ let markup = '<b>' + output.display_name + '</b>';
+ let label = new Gtk.Label({ label: markup,
+ use_markup: true,
+ margin_top: 5,
+ margin_bottom: 5 });
+ label.show();
+ label.output = output;
+ this._box.add(label);
+ }
+ },
+
+ _onActivated: function(box, child) {
+ this.output = child.output;
+ this.emit('output-activated', this.output);
+ this.close();
+ },
+
+ close: function() {
+ this.window.destroy();
+ },
+
+ _createWindow: function() {
+ let toplevel = Application.application.get_windows()[0];
+ this.window = new Gtk.Dialog ({ resizable: true,
+ modal: true,
+ transient_for: toplevel,
+ destroy_with_parent: true,
+ title: _("Present On"),
+ default_width: 300,
+ default_height: 150,
+ hexpand: true });
+ this.window.connect('response', Lang.bind(this,
+ function(widget, response) {
+ this.emit('output-activated', null);
+ }));
+
+ this._box = new Egg.ListBox({ valign: Gtk.Align.CENTER });
+ this._box.connect('child-activated', Lang.bind(this, this._onActivated));
+ let contentArea = this.window.get_content_area();
+ contentArea.pack_start(this._box, true, false, 0);
+ }
+});
+Signals.addSignalMethods(PresentationOutputChooser.prototype);
+
+const PresentationOutput = new Lang.Class({
+ Name: 'PresentationOutput',
+ _init: function() {
+ this.id = null;
+ this.name = null;
+ this.display_name = null;
+ this.is_primary = false;
+ this.x = 0;
+ this.y = 0;
+ }
+});
+
+const PresentationOutputs = new Lang.Class({
+ Name: 'PresentationOutputs',
+
+ _init: function() {
+ this.list = [];
+
+ let gdkscreen = Gdk.Screen.get_default();
+ this._screen = GnomeDesktop.RRScreen.new(gdkscreen, null);
+ this._screen.connect('changed', Lang.bind(this, this._onScreenChanged));
+
+ this.load();
+ },
+
+ _onScreenChanged: function() {
+ this.load();
+ },
+
+ load: function() {
+ this._outputs = this._screen.list_outputs();
+ this.list = [];
+ for (let idx in this._outputs) {
+ let output = this._outputs[idx];
+ if (!output.is_connected())
+ continue;
+
+ let out = new PresentationOutput();
+ out.name = output.get_name();
+ out.display_name = output.get_display_name();
+ out.is_primary = output.get_is_primary();
+ let [x, y] = output.get_position();
+ out.x = x;
+ out.y = y;
+
+ this.list.push(out);
+ }
+ }
+});
diff --git a/src/preview.js b/src/preview.js
index 10fe697..d178e69 100644
--- a/src/preview.js
+++ b/src/preview.js
@@ -41,6 +41,7 @@ const Searchbar = imports.searchbar;
const Utils = imports.utils;
const View = imports.view;
const WindowMode = imports.windowMode;
+const Presentation = imports.presentation;
const _FULLSCREEN_TOOLBAR_TIMEOUT = 2; // seconds
@@ -124,6 +125,10 @@ const PreviewView = new Lang.Class({
let showPlaces = Application.application.lookup_action('places');
showPlaces.connect('activate', Lang.bind(this, this._showPlaces));
+ this._togglePresentation = Application.application.lookup_action('present-current');
+ Application.application.connect('action-state-changed::present-current',
+ Lang.bind(this, this._onPresentStateChanged));
+
Application.documentManager.connect('load-started',
Lang.bind(this, this._onLoadStarted));
Application.documentManager.connect('load-finished',
@@ -138,6 +143,7 @@ const PreviewView = new Lang.Class({
_onLoadFinished: function(manager, doc, docModel) {
this._showPlaces.enabled = true;
+ this._togglePresentation.enabled = true;
if (!Application.documentManager.metadata)
return;
@@ -166,6 +172,16 @@ const PreviewView = new Lang.Class({
this._bookmarks.remove(bookmark);
},
+ _onPresentStateChanged: function(source, actionName, state) {
+ if (!this._model)
+ return;
+
+ if (state.get_boolean())
+ this._promptPresentation();
+ else
+ this._hidePresentation();
+ },
+
_onPageChanged: function() {
this._pageChanged = true;
@@ -184,6 +200,40 @@ const PreviewView = new Lang.Class({
}));
},
+ _hidePresentation: function() {
+ if (this._presentation) {
+ this._presentation.close();
+ this._presentation = null;
+ }
+
+ Application.application.change_action_state('present-current', GLib.Variant.new('b', false));
+ },
+
+ _showPresentation: function(output) {
+ this._presentation = new Presentation.PresentationWindow(this._model);
+ this._presentation.window.connect('destroy', Lang.bind(this, this._hidePresentation));
+ if (output)
+ this._presentation.setOutput(output);
+ },
+
+ _promptPresentation: function() {
+ let outputs = new Presentation.PresentationOutputs();
+ if (outputs.list.length < 2) {
+ this._showPresentation();
+ } else {
+ let chooser = new Presentation.PresentationOutputChooser(outputs);
+ chooser.connect('output-activated', Lang.bind(this,
+ function(chooser, output) {
+ if (output) {
+ this._showPresentation(output);
+ } else {
+ this._hidePresentation();
+ }
+ }));
+
+ }
+ },
+
_onViewSelectionChanged: function() {
this._viewSelectionChanged = true;
if (!this.view.get_has_selection())
@@ -290,8 +340,10 @@ const PreviewView = new Lang.Class({
_onWindowModeChanged: function() {
let windowMode = Application.modeController.getWindowMode();
- if (windowMode != WindowMode.WindowMode.PREVIEW)
+ if (windowMode != WindowMode.WindowMode.PREVIEW) {
this.controlsVisible = false;
+ this._hidePresentation();
+ }
},
_onFullscreenChanged: function() {
diff --git a/src/resources/preview-menu.ui b/src/resources/preview-menu.ui
index 27590f5..c0cd2b0 100644
--- a/src/resources/preview-menu.ui
+++ b/src/resources/preview-menu.ui
@@ -14,6 +14,11 @@
<attribute name="label" translatable="yes">Printâ</attribute>
<attribute name="accel"><Primary>p</attribute>
</item>
+ <item>
+ <attribute name="action">app.present-current</attribute>
+ <attribute name="label" translatable="yes">Present</attribute>
+ <attribute name="accel">F5</attribute>
+ </item>
</section>
<section>
<item>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]