[gnome-documents] Add a presentation mode



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">&lt;Primary&gt;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]