[latexila/wip/latexila-next: 44/56] LatexilaSynctex



commit 3d1981ee9a0355d00008a373ad371cbe4ebfcd48
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Sat May 17 01:25:32 2014 +0200

    LatexilaSynctex
    
    It is needed for the build tools for opening a PDF file with synctex
    support with evince. Since I don't want to use the Vala code in C, a
    rewrite of synctex.vala in C was needed.
    
    synctex.vala uses the synchronous D-Bus API. The rewrite in C uses the
    asynchronous API (better).
    
    TODO: send a signal for the sync_source (PDF -> .tex)

 configure.ac                          |    2 +
 docs/reference/latexila-docs.xml      |    1 +
 docs/reference/latexila-sections.txt  |   23 ++
 src/Makefile.am                       |    2 +-
 src/evince/Makefile.am                |   22 ++
 src/evince/evince-gdbus.xml           |   30 ++
 src/liblatexila/Makefile.am           |   34 ++-
 src/liblatexila/latexila-build-tool.c |   19 +-
 src/liblatexila/latexila-synctex.c    |  633 +++++++++++++++++++++++++++++++++
 src/liblatexila/latexila-synctex.h    |   72 ++++
 src/liblatexila/latexila-types.h      |    1 +
 src/liblatexila/latexila-utils.c      |   53 +++
 src/liblatexila/latexila-utils.h      |   10 +-
 src/liblatexila/latexila.h            |    1 +
 14 files changed, 878 insertions(+), 25 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 07a1342..d420cc9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,6 +75,7 @@ AC_PROG_INSTALL
 AM_PROG_CC_C_O
 AC_PATH_PROG([GLIB_COMPILE_RESOURCES], [glib-compile-resources])
 AC_PATH_PROG([GLIB_MKENUMS], [glib-mkenums])
+AC_PATH_PROG([GDBUS_CODEGEN], [gdbus-codegen])
 AM_PROG_VALAC([${VALA_REQUIRED_VERSION}],
               [found_vala=true]
              [found_vala=false])
@@ -168,6 +169,7 @@ AC_CONFIG_FILES([Makefile
                 man/Makefile
                 po/Makefile.in
                  src/Makefile
+                src/evince/Makefile
                 src/gedit/Makefile
                 src/liblatexila/Makefile
                 src/ui/Makefile
diff --git a/docs/reference/latexila-docs.xml b/docs/reference/latexila-docs.xml
index 064f80a..2e09e5f 100644
--- a/docs/reference/latexila-docs.xml
+++ b/docs/reference/latexila-docs.xml
@@ -17,6 +17,7 @@
     <xi:include href="xml/build-tools-default.xml"/>
     <xi:include href="xml/build-tools-personal.xml"/>
     <xi:include href="xml/build-view.xml"/>
+    <xi:include href="xml/synctex.xml"/>
     <xi:include href="xml/utils.xml"/>
   </chapter>
 
diff --git a/docs/reference/latexila-sections.txt b/docs/reference/latexila-sections.txt
index 6730ec5..eaa205a 100644
--- a/docs/reference/latexila-sections.txt
+++ b/docs/reference/latexila-sections.txt
@@ -131,10 +131,33 @@ latexila_build_view_get_type
 </SECTION>
 
 <SECTION>
+<FILE>synctex</FILE>
+<TITLE>LatexilaSynctex</TITLE>
+LatexilaSynctex
+latexila_synctex_get_instance
+latexila_synctex_connect_evince_window
+latexila_synctex_connect_evince_window_async
+latexila_synctex_connect_evince_window_finish
+latexila_synctex_forward_search
+<SUBSECTION Standard>
+LATEXILA_IS_SYNCTEX
+LATEXILA_IS_SYNCTEX_CLASS
+LATEXILA_SYNCTEX
+LATEXILA_SYNCTEX_CLASS
+LATEXILA_SYNCTEX_GET_CLASS
+LATEXILA_TYPE_SYNCTEX
+LatexilaSynctexClass
+LatexilaSynctexPrivate
+latexila_synctex_get_type
+</SECTION>
+
+<SECTION>
 <FILE>utils</FILE>
 <TITLE>LatexilaUtils</TITLE>
 latexila_utils_get_shortname
 latexila_utils_replace_home_dir_with_tilde
 latexila_utils_register_icons
 latexila_utils_str_replace
+latexila_utils_file_query_exists_async
+latexila_utils_file_query_exists_finish
 </SECTION>
diff --git a/src/Makefile.am b/src/Makefile.am
index 508fd87..92f905e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = gedit liblatexila ui
+SUBDIRS = evince gedit liblatexila ui
 
 bin_PROGRAMS = latexila
 
diff --git a/src/evince/Makefile.am b/src/evince/Makefile.am
new file mode 100644
index 0000000..b787b83
--- /dev/null
+++ b/src/evince/Makefile.am
@@ -0,0 +1,22 @@
+noinst_LTLIBRARIES = libevince.la
+
+libevince_la_CFLAGS = $(WARN_CFLAGS)
+
+libevince_built_sources =              \
+       evince-gdbus-generated.c        \
+       evince-gdbus-generated.h
+
+nodist_libevince_la_SOURCES = $(libevince_built_sources)
+
+BUILT_SOURCES = $(libevince_built_sources)
+
+evince-gdbus-generated.c evince-gdbus-generated.h: evince-gdbus.xml Makefile
+       $(AM_V_GEN) $(GDBUS_CODEGEN) \
+                       --interface-prefix=org.gnome.evince. \
+                       --c-namespace=Evince \
+                       --generate-c-code evince-gdbus-generated \
+                       evince-gdbus.xml
+
+EXTRA_DIST = evince-gdbus.xml
+
+-include $(top_srcdir)/git.mk
diff --git a/src/evince/evince-gdbus.xml b/src/evince/evince-gdbus.xml
new file mode 100644
index 0000000..0da995f
--- /dev/null
+++ b/src/evince/evince-gdbus.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Introspection 0.1//EN"
+                      "http://www.freedesktop.org/software/dbus/introspection.dtd";>
+<node>
+  <interface name="org.gnome.evince.Daemon">
+    <method name="FindDocument">
+      <arg type="s" name="uri" direction="in"/>
+      <arg type="b" name="spawn" direction="in"/>
+      <arg type="s" name="owner" direction="out"/>
+    </method>
+  </interface>
+  <interface name='org.gnome.evince.Application'>
+    <method name='GetWindowList'>
+      <arg type='ao' name='window_list' direction='out'/>
+    </method>
+  </interface>
+  <interface name='org.gnome.evince.Window'>
+    <method name='SyncView'>
+      <arg type='s' name='source_file' direction='in'/>
+      <arg type='(ii)' name='source_point' direction='in'/>
+      <arg type='u' name='timestamp' direction='in'/>
+    </method>
+    <signal name='SyncSource'>
+      <arg type='s' name='source_file' direction='out'/>
+      <arg type='(ii)' name='source_point' direction='out'/>
+      <arg type='u' name='timestamp' direction='out'/>
+    </signal>
+    <signal name='Closed'/>
+  </interface>
+</node>
diff --git a/src/liblatexila/Makefile.am b/src/liblatexila/Makefile.am
index 538c4e5..1beb3cc 100644
--- a/src/liblatexila/Makefile.am
+++ b/src/liblatexila/Makefile.am
@@ -2,10 +2,16 @@
 
 noinst_LTLIBRARIES = liblatexila.la
 
-liblatexila_la_CFLAGS = $(WARN_CFLAGS) $(CODE_COVERAGE_CFLAGS)
+liblatexila_la_CFLAGS =                        \
+       $(WARN_CFLAGS)                  \
+       $(CODE_COVERAGE_CFLAGS)         \
+       -I$(top_builddir)/src/evince
+
 liblatexila_la_LDFLAGS = $(CODE_COVERAGE_LDFLAGS)
+liblatexila_la_LIBADD = ../evince/libevince.la
 
 liblatexila_headers =                          \
+       latexila.h                              \
        latexila-build-job.h                    \
        latexila-build-tool.h                   \
        latexila-build-tools.h                  \
@@ -14,14 +20,11 @@ liblatexila_headers =                               \
        latexila-build-view.h                   \
        latexila-post-processor.h               \
        latexila-post-processor-all-output.h    \
+       latexila-synctex.h                      \
        latexila-types.h                        \
        latexila-utils.h
 
-BUILT_SOURCES =                        \
-       latexila-enum-types.c   \
-       latexila-enum-types.h
-
-liblatexila_la_SOURCES =                       \
+liblatexila_sources =                          \
        latexila-build-job.c                    \
        latexila-build-tool.c                   \
        latexila-build-tools.c                  \
@@ -30,9 +33,20 @@ liblatexila_la_SOURCES =                     \
        latexila-build-view.c                   \
        latexila-post-processor.c               \
        latexila-post-processor-all-output.c    \
-       latexila-utils.c                        \
-       $(liblatexila_headers)                  \
-       $(BUILT_SOURCES)
+       latexila-synctex.c                      \
+       latexila-utils.c
+
+liblatexila_built_sources =    \
+       latexila-enum-types.c   \
+       latexila-enum-types.h
+
+liblatexila_la_SOURCES =               \
+       $(liblatexila_headers)          \
+       $(liblatexila_sources)
+
+nodist_liblatexila_la_SOURCES = $(liblatexila_built_sources)
+
+BUILT_SOURCES = $(liblatexila_built_sources)
 
 ENUM_TYPES = $(liblatexila_headers)
 
@@ -64,7 +78,7 @@ Latexila.gir: liblatexila.la
 Latexila_gir_NAMESPACE = Latexila
 Latexila_gir_INCLUDES = Gtk-3.0
 Latexila_gir_LIBS = liblatexila.la
-Latexila_gir_FILES = $(liblatexila_la_SOURCES)
+Latexila_gir_FILES = $(liblatexila_la_SOURCES) $(nodist_liblatexila_la_SOURCES)
 
 noinst_DATA += Latexila.gir Latexila.typelib
 CLEANFILES += Latexila.gir Latexila.typelib
diff --git a/src/liblatexila/latexila-build-tool.c b/src/liblatexila/latexila-build-tool.c
index f954e68..a45ff02 100644
--- a/src/liblatexila/latexila-build-tool.c
+++ b/src/liblatexila/latexila-build-tool.c
@@ -495,13 +495,12 @@ query_exists_cb (GFile             *file,
                  GAsyncResult      *result,
                  LatexilaBuildTool *build_tool)
 {
-  GFileInfo *info;
-  GCancellable *cancellable;
   gboolean file_exists;
+  GCancellable *cancellable;
   gchar *uri = NULL;
   GError *error = NULL;
 
-  info = g_file_query_info_finish (file, result, NULL);
+  file_exists = latexila_utils_file_query_exists_finish (file, result);
 
   cancellable = g_task_get_cancellable (build_tool->priv->task);
   if (g_cancellable_is_cancelled (cancellable))
@@ -513,9 +512,6 @@ query_exists_cb (GFile             *file,
       goto out;
     }
 
-  file_exists = info != NULL;
-  g_clear_object (&info);
-
   uri = g_file_get_uri (file);
 
   if (!file_exists)
@@ -660,13 +656,10 @@ open_file (LatexilaBuildTool *build_tool)
 
   file = g_file_new_for_uri (uri);
 
-  g_file_query_info_async (file,
-                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
-                           G_FILE_QUERY_INFO_NONE,
-                           G_PRIORITY_DEFAULT,
-                           g_task_get_cancellable (build_tool->priv->task),
-                           (GAsyncReadyCallback) query_exists_cb,
-                           build_tool);
+  latexila_utils_file_query_exists_async (file,
+                                          g_task_get_cancellable (build_tool->priv->task),
+                                          (GAsyncReadyCallback) query_exists_cb,
+                                          build_tool);
 
   g_free (filename);
   g_free (shortname);
diff --git a/src/liblatexila/latexila-synctex.c b/src/liblatexila/latexila-synctex.c
new file mode 100644
index 0000000..b8c501f
--- /dev/null
+++ b/src/liblatexila/latexila-synctex.c
@@ -0,0 +1,633 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LaTeXila 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 LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:synctex
+ * @title: LatexilaSynctex
+ * @short_description: SyncTeX support between LaTeXila and Evince
+ *
+ * The #LatexilaSynctex class (a singleton) implements the support of SyncTeX
+ * between LaTeXila and the Evince PDF viewer. It is used to switch between the
+ * source file(s) and the PDF, at the same position in the document. It is called
+ * the forward search: source file -> PDF. And backward search: PDF -> source
+ * file.
+ *
+ * D-Bus is used to communicate between LaTeXila and Evince. The implementation
+ * uses the asynchronous gdbus generated functions.
+ */
+
+#include "latexila-synctex.h"
+#include <glib/gi18n.h>
+#include "evince-gdbus-generated.h"
+#include "latexila-utils.h"
+
+static LatexilaSynctex *instance = NULL;
+
+struct _LatexilaSynctexPrivate
+{
+  /* PDF URI -> EvinceWindow object */
+  GHashTable *evince_windows;
+};
+
+typedef struct
+{
+  GtkTextBuffer *buffer;
+  GFile *buffer_location;
+  gchar *pdf_uri;
+} ForwardSearchData;
+
+typedef struct
+{
+  gchar *pdf_uri;
+  gchar *owner;
+} ConnectEvinceWindowData;
+
+G_DEFINE_TYPE_WITH_PRIVATE (LatexilaSynctex, latexila_synctex, G_TYPE_OBJECT)
+
+static ForwardSearchData *
+forward_search_data_new (void)
+{
+  return g_slice_new0 (ForwardSearchData);
+}
+
+static void
+forward_search_data_free (ForwardSearchData *data)
+{
+  if (data != NULL)
+    {
+      g_clear_object (&data->buffer);
+      g_clear_object (&data->buffer_location);
+      g_free (data->pdf_uri);
+      g_slice_free (ForwardSearchData, data);
+    }
+}
+
+static ConnectEvinceWindowData *
+connect_evince_window_data_new (void)
+{
+  return g_slice_new0 (ConnectEvinceWindowData);
+}
+
+static void
+connect_evince_window_data_free (ConnectEvinceWindowData *data)
+{
+  if (data != NULL)
+    {
+      g_free (data->pdf_uri);
+      g_free (data->owner);
+      g_slice_free (ConnectEvinceWindowData, data);
+    }
+}
+
+static void
+latexila_synctex_dispose (GObject *object)
+{
+  LatexilaSynctex *synctex = LATEXILA_SYNCTEX (object);
+
+  if (synctex->priv->evince_windows != NULL)
+    {
+      g_hash_table_unref (synctex->priv->evince_windows);
+      synctex->priv->evince_windows = NULL;
+    }
+
+  G_OBJECT_CLASS (latexila_synctex_parent_class)->dispose (object);
+}
+
+static void
+latexila_synctex_class_init (LatexilaSynctexClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = latexila_synctex_dispose;
+}
+
+static void
+latexila_synctex_init (LatexilaSynctex *synctex)
+{
+  synctex->priv = latexila_synctex_get_instance_private (synctex);
+
+  synctex->priv->evince_windows = g_hash_table_new_full (g_str_hash,
+                                                         g_str_equal,
+                                                         g_free,
+                                                         g_object_unref);
+}
+
+/**
+ * latexila_synctex_get_instance:
+ *
+ * Returns: (transfer none): the #LatexilaSynctex singleton instance.
+ */
+LatexilaSynctex *
+latexila_synctex_get_instance (void)
+{
+  if (instance == NULL)
+    {
+      instance = g_object_new (LATEXILA_TYPE_SYNCTEX, NULL);
+    }
+
+  return instance;
+}
+
+static void
+show_warning (const gchar *message)
+{
+  GtkApplication *app;
+  GtkWindow *parent;
+  GtkWidget *dialog;
+
+  app = GTK_APPLICATION (g_application_get_default ());
+  parent = gtk_application_get_active_window (app);
+
+  dialog = gtk_message_dialog_new (parent,
+                                   GTK_DIALOG_DESTROY_WITH_PARENT,
+                                   GTK_MESSAGE_ERROR,
+                                   GTK_BUTTONS_OK,
+                                   "%s", _("Impossible to do the forward search."));
+
+  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+                                            "%s", message);
+
+  gtk_dialog_run (GTK_DIALOG (dialog));
+  gtk_widget_destroy (dialog);
+}
+
+static gchar *
+get_pdf_uri (GFile *main_tex_file)
+{
+  gchar *tex_uri;
+  gchar *short_uri;
+  gchar *pdf_uri;
+
+  tex_uri = g_file_get_uri (main_tex_file);
+  short_uri = latexila_utils_get_shortname (tex_uri);
+  pdf_uri = g_strdup_printf ("%s.pdf", short_uri);
+
+  g_free (tex_uri);
+  g_free (short_uri);
+  return pdf_uri;
+}
+
+static GVariant *
+get_buffer_position (GtkTextBuffer *buffer)
+{
+  GtkTextIter iter;
+  gint line;
+  gint column;
+
+  gtk_text_buffer_get_iter_at_mark (buffer,
+                                    &iter,
+                                    gtk_text_buffer_get_insert (buffer));
+
+  line = gtk_text_iter_get_line (&iter) + 1;
+
+  /* Ignore the column, it gives a better result. */
+  column = -1;
+
+  return g_variant_new ("(ii)", line, column);
+}
+
+static void
+window_closed_cb (EvinceWindow *window,
+                  const gchar  *pdf_uri)
+{
+  g_hash_table_remove (instance->priv->evince_windows, pdf_uri);
+}
+
+static void
+sync_source_cb (EvinceWindow    *window,
+                const gchar     *tex_uri,
+                GVariant        *pos,
+                guint            timestamp,
+                LatexilaSynctex *synctex)
+{
+}
+
+static void
+window_proxy_cb (GObject      *object,
+                 GAsyncResult *result,
+                 GTask        *task)
+{
+  EvinceWindow *window;
+  ConnectEvinceWindowData *data;
+  GError *error = NULL;
+
+  window = evince_window_proxy_new_for_bus_finish (result, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("SyncTeX: can not connect to evince window: %s", error->message);
+      g_task_return_boolean (task, FALSE);
+      g_object_unref (task);
+      g_error_free (error);
+      return;
+    }
+
+  data = g_task_get_task_data (task);
+
+  g_hash_table_insert (instance->priv->evince_windows, data->pdf_uri, window);
+
+  g_signal_connect (window,
+                    "closed",
+                    G_CALLBACK (window_closed_cb),
+                    data->pdf_uri);
+
+  g_signal_connect (window,
+                    "sync-source",
+                    G_CALLBACK (sync_source_cb),
+                    instance);
+
+  data->pdf_uri = NULL;
+
+  /* latexila_synctex_connect_evince_window_async() is finally finished! */
+  g_task_return_boolean (task, TRUE);
+  g_object_unref (task);
+}
+
+static void
+get_window_list_cb (EvinceApplication *app,
+                    GAsyncResult      *result,
+                    GTask             *task)
+{
+  ConnectEvinceWindowData *data;
+  gchar **window_list;
+  gchar *window_path;
+  GError *error = NULL;
+
+  evince_application_call_get_window_list_finish (app, &window_list, result, &error);
+  g_object_unref (app);
+
+  if (error != NULL)
+    {
+      g_warning ("SyncTeX: can not get window list: %s", error->message);
+      g_task_return_boolean (task, FALSE);
+      g_object_unref (task);
+      g_error_free (error);
+      return;
+    }
+
+  if (window_list == NULL || window_list[0] == NULL)
+    {
+      g_warning ("SyncTeX: the window list is empty.");
+      g_task_return_boolean (task, FALSE);
+      g_object_unref (task);
+      g_strfreev (window_list);
+      return;
+    }
+
+  data = g_task_get_task_data (task);
+
+  /* There is normally only one window. */
+  window_path = window_list[0];
+
+  evince_window_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                   G_DBUS_PROXY_FLAGS_NONE,
+                                   data->owner,
+                                   window_path,
+                                   NULL,
+                                   (GAsyncReadyCallback) window_proxy_cb,
+                                   task);
+
+  g_strfreev (window_list);
+}
+
+static void
+application_proxy_cb (GObject      *object,
+                      GAsyncResult *result,
+                      GTask        *task)
+{
+  EvinceApplication *app;
+  GError *error = NULL;
+
+  app = evince_application_proxy_new_for_bus_finish (result, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("SyncTeX: can not connect to evince application: %s", error->message);
+      g_task_return_boolean (task, FALSE);
+      g_object_unref (task);
+      g_error_free (error);
+      return;
+    }
+
+  evince_application_call_get_window_list (app,
+                                           NULL,
+                                           (GAsyncReadyCallback) get_window_list_cb,
+                                           task);
+}
+
+static void
+find_document_cb (EvinceDaemon *daemon,
+                  GAsyncResult *result,
+                  GTask        *task)
+{
+  ConnectEvinceWindowData *data;
+  GError *error = NULL;
+
+  data = g_task_get_task_data (task);
+
+  evince_daemon_call_find_document_finish (daemon, &data->owner, result, &error);
+  g_object_unref (daemon);
+
+  if (error != NULL)
+    {
+      g_warning ("SyncTeX: find document: %s", error->message);
+      g_task_return_boolean (task, FALSE);
+      g_object_unref (task);
+      g_error_free (error);
+      return;
+    }
+
+  evince_application_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                        G_DBUS_PROXY_FLAGS_NONE,
+                                        data->owner,
+                                        "/org/gnome/evince/Evince",
+                                        NULL,
+                                        (GAsyncReadyCallback) application_proxy_cb,
+                                        task);
+}
+
+static void
+daemon_proxy_cb (GObject      *object,
+                 GAsyncResult *result,
+                 GTask        *task)
+{
+  EvinceDaemon *daemon;
+  GError *error = NULL;
+  ConnectEvinceWindowData *data;
+
+  daemon = evince_daemon_proxy_new_for_bus_finish (result, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("SyncTeX: can not connect to the evince daemon: %s", error->message);
+      g_task_return_boolean (task, FALSE);
+      g_object_unref (task);
+      g_error_free (error);
+      return;
+    }
+
+  data = g_task_get_task_data (task);
+
+  evince_daemon_call_find_document (daemon, data->pdf_uri, TRUE, NULL,
+                                    (GAsyncReadyCallback) find_document_cb,
+                                    task);
+}
+
+/**
+ * latexila_synctex_connect_evince_window_async:
+ * @synctex: the #LatexilaSynctex instance.
+ * @pdf_uri: the PDF URI
+ * @callback: the callback to call when the operation is finished.
+ * @user_data: the data to pass to the callback function.
+ *
+ * Connects asynchronously the evince window for @pdf_uri. LaTeXila will then
+ * listen the signals emitted by the evince window when the user wants to switch
+ * from the PDF to the corresponding *.tex file.
+ *
+ * The callback will be called when the operation is finished. You can then call
+ * latexila_synctex_connect_evince_window_finish().
+ */
+void
+latexila_synctex_connect_evince_window_async (LatexilaSynctex     *synctex,
+                                              const gchar         *pdf_uri,
+                                              GAsyncReadyCallback  callback,
+                                              gpointer             user_data)
+{
+  GTask *task;
+  ConnectEvinceWindowData *data;
+
+  g_return_if_fail (LATEXILA_IS_SYNCTEX (synctex));
+  g_return_if_fail (pdf_uri != NULL);
+
+  task = g_task_new (synctex, NULL, callback, user_data);
+
+  if (g_hash_table_contains (synctex->priv->evince_windows, pdf_uri))
+    {
+      g_task_return_boolean (task, TRUE);
+      g_object_unref (task);
+      return;
+    }
+
+  data = connect_evince_window_data_new ();
+  data->pdf_uri = g_strdup (pdf_uri);
+
+  g_task_set_task_data (task, data, (GDestroyNotify) connect_evince_window_data_free);
+
+  evince_daemon_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                   G_DBUS_PROXY_FLAGS_NONE,
+                                   "org.gnome.evince.Daemon",
+                                   "/org/gnome/evince/Daemon",
+                                   NULL,
+                                   (GAsyncReadyCallback) daemon_proxy_cb,
+                                   task);
+}
+
+/**
+ * latexila_synctex_connect_evince_window_finish:
+ * @synctex: the #LatexilaSynctex instance.
+ * @result: a #GAsyncResult.
+ *
+ * Finishes the operation started with
+ * latexila_synctex_connect_evince_window_async().
+ */
+void
+latexila_synctex_connect_evince_window_finish (LatexilaSynctex *synctex,
+                                               GAsyncResult    *result)
+{
+  g_return_if_fail (g_task_is_valid (result, synctex));
+
+  g_task_propagate_boolean (G_TASK (result), NULL);
+}
+
+/**
+ * latexila_synctex_connect_evince_window:
+ * @synctex: the #LatexilaSynctex instance.
+ * @pdf_uri: the PDF URI.
+ *
+ * Simple version of latexila_synctex_connect_evince_window_async().
+ * This function is also asynchronous, but without callback.
+ */
+void
+latexila_synctex_connect_evince_window (LatexilaSynctex *synctex,
+                                        const gchar     *pdf_uri)
+{
+  latexila_synctex_connect_evince_window_async (synctex,
+                                                pdf_uri,
+                                                (GAsyncReadyCallback) 
latexila_synctex_connect_evince_window_finish,
+                                                NULL);
+}
+
+static void
+sync_view_cb (EvinceWindow      *evince_window,
+              GAsyncResult      *result,
+              ForwardSearchData *data)
+{
+  GError *error = NULL;
+
+  evince_window_call_sync_view_finish (evince_window, result, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("SyncTeX: can not sync view: %s", error->message);
+      g_error_free (error);
+    }
+
+  /* latexila_synctex_forward_search() finished! */
+  forward_search_data_free (data);
+}
+
+static void
+connect_evince_window_cb (LatexilaSynctex   *synctex,
+                          GAsyncResult      *result,
+                          ForwardSearchData *data)
+{
+  EvinceWindow *evince_window;
+  gchar *buffer_path;
+
+  latexila_synctex_connect_evince_window_finish (synctex, result);
+
+  evince_window = g_hash_table_lookup (synctex->priv->evince_windows, data->pdf_uri);
+
+  if (evince_window == NULL)
+    {
+      show_warning (_("Can not communicate with evince."));
+      forward_search_data_free (data);
+      return;
+    }
+
+  buffer_path = g_file_get_path (data->buffer_location);
+
+  evince_window_call_sync_view (evince_window,
+                                buffer_path,
+                                get_buffer_position (data->buffer),
+                                GDK_CURRENT_TIME,
+                                NULL,
+                                (GAsyncReadyCallback) sync_view_cb,
+                                data);
+
+  g_free (buffer_path);
+}
+
+static void
+synctex_file_query_exists_cb (GFile             *synctex_file,
+                              GAsyncResult      *result,
+                              ForwardSearchData *data)
+{
+  gboolean synctex_file_exists;
+
+  synctex_file_exists = latexila_utils_file_query_exists_finish (synctex_file, result);
+
+  if (!synctex_file_exists)
+    {
+      gchar *basename = g_file_get_basename (synctex_file);
+      gchar *message = g_strdup_printf (_("The file \"%s\" doesn't exist."), basename);
+
+      show_warning (message);
+
+      g_free (basename);
+      g_free (message);
+      g_object_unref (synctex_file);
+      forward_search_data_free (data);
+      return;
+    }
+
+  latexila_synctex_connect_evince_window_async (instance,
+                                                data->pdf_uri,
+                                                (GAsyncReadyCallback) connect_evince_window_cb,
+                                                data);
+
+  g_object_unref (synctex_file);
+}
+
+static void
+pdf_file_query_exists_cb (GFile             *pdf_file,
+                          GAsyncResult      *result,
+                          ForwardSearchData *data)
+{
+  gboolean pdf_file_exists;
+  gchar *short_uri;
+  gchar *synctex_uri;
+  GFile *synctex_file;
+
+  pdf_file_exists = latexila_utils_file_query_exists_finish (pdf_file, result);
+  g_object_unref (pdf_file);
+
+  if (!pdf_file_exists)
+    {
+      show_warning (_("The PDF file doesn't exist."));
+      forward_search_data_free (data);
+      return;
+    }
+
+  short_uri = latexila_utils_get_shortname (data->pdf_uri);
+  synctex_uri = g_strdup_printf ("%s.synctex.gz", short_uri);
+  synctex_file = g_file_new_for_uri (synctex_uri);
+  g_free (short_uri);
+  g_free (synctex_uri);
+
+  latexila_utils_file_query_exists_async (synctex_file,
+                                          NULL,
+                                          (GAsyncReadyCallback) synctex_file_query_exists_cb,
+                                          data);
+}
+
+/**
+ * latexila_synctex_forward_search:
+ * @synctex: the #LatexilaSynctex instance.
+ * @buffer: a #GtkTextBuffer.
+ * @buffer_location: the *.tex file of @buffer.
+ * @main_tex_file: the main *.tex file of @buffer.
+ *
+ * Does a forward search, i.e. switch from the *.tex file to the PDF file at the
+ * same position as the cursor position in @buffer.
+ */
+void
+latexila_synctex_forward_search (LatexilaSynctex *synctex,
+                                 GtkTextBuffer   *buffer,
+                                 GFile           *buffer_location,
+                                 GFile           *main_tex_file)
+{
+  ForwardSearchData *data;
+  GFile *pdf_file;
+
+  g_return_if_fail (LATEXILA_IS_SYNCTEX (synctex));
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (buffer_location == NULL || G_IS_FILE (buffer_location));
+  g_return_if_fail (main_tex_file == NULL || G_IS_FILE (main_tex_file));
+
+  if (buffer_location == NULL)
+    {
+      show_warning (_("The document is not saved."));
+      return;
+    }
+
+  g_return_if_fail (G_IS_FILE (main_tex_file));
+
+  data = forward_search_data_new ();
+  data->buffer = g_object_ref (buffer);
+  data->buffer_location = g_object_ref (buffer_location);
+  data->pdf_uri = get_pdf_uri (main_tex_file);
+
+  pdf_file = g_file_new_for_uri (data->pdf_uri);
+
+  latexila_utils_file_query_exists_async (pdf_file,
+                                          NULL,
+                                          (GAsyncReadyCallback) pdf_file_query_exists_cb,
+                                          data);
+}
diff --git a/src/liblatexila/latexila-synctex.h b/src/liblatexila/latexila-synctex.h
new file mode 100644
index 0000000..3b20130
--- /dev/null
+++ b/src/liblatexila/latexila-synctex.h
@@ -0,0 +1,72 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LaTeXila 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 LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LATEXILA_SYNCTEX_H__
+#define __LATEXILA_SYNCTEX_H__
+
+#include "latexila-types.h"
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define LATEXILA_TYPE_SYNCTEX             (latexila_synctex_get_type ())
+#define LATEXILA_SYNCTEX(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), LATEXILA_TYPE_SYNCTEX, 
LatexilaSynctex))
+#define LATEXILA_SYNCTEX_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), LATEXILA_TYPE_SYNCTEX, 
LatexilaSynctexClass))
+#define LATEXILA_IS_SYNCTEX(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LATEXILA_TYPE_SYNCTEX))
+#define LATEXILA_IS_SYNCTEX_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), LATEXILA_TYPE_SYNCTEX))
+#define LATEXILA_SYNCTEX_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), LATEXILA_TYPE_SYNCTEX, 
LatexilaSynctexClass))
+
+typedef struct _LatexilaSynctexClass   LatexilaSynctexClass;
+typedef struct _LatexilaSynctexPrivate LatexilaSynctexPrivate;
+
+struct _LatexilaSynctex
+{
+  GObject parent;
+
+  LatexilaSynctexPrivate *priv;
+};
+
+struct _LatexilaSynctexClass
+{
+  GObjectClass parent_class;
+};
+
+GType             latexila_synctex_get_type                       (void) G_GNUC_CONST;
+
+LatexilaSynctex * latexila_synctex_get_instance                   (void);
+
+void              latexila_synctex_connect_evince_window_async    (LatexilaSynctex     *synctex,
+                                                                   const gchar         *pdf_uri,
+                                                                   GAsyncReadyCallback  callback,
+                                                                   gpointer             user_data);
+
+void              latexila_synctex_connect_evince_window_finish   (LatexilaSynctex *synctex,
+                                                                   GAsyncResult    *result);
+
+void              latexila_synctex_connect_evince_window          (LatexilaSynctex *synctex,
+                                                                   const gchar     *pdf_uri);
+
+void              latexila_synctex_forward_search                 (LatexilaSynctex *synctex,
+                                                                   GtkTextBuffer   *buffer,
+                                                                   GFile           *buffer_location,
+                                                                   GFile           *main_tex_file);
+
+G_END_DECLS
+
+#endif /* __LATEXILA_SYNCTEX_H__ */
diff --git a/src/liblatexila/latexila-types.h b/src/liblatexila/latexila-types.h
index 9ca5d99..1dd9da4 100644
--- a/src/liblatexila/latexila-types.h
+++ b/src/liblatexila/latexila-types.h
@@ -32,6 +32,7 @@ typedef struct _LatexilaBuildToolsPersonal      LatexilaBuildToolsPersonal;
 typedef struct _LatexilaBuildView               LatexilaBuildView;
 typedef struct _LatexilaPostProcessor           LatexilaPostProcessor;
 typedef struct _LatexilaPostProcessorAllOutput  LatexilaPostProcessorAllOutput;
+typedef struct _LatexilaSynctex                 LatexilaSynctex;
 
 G_END_DECLS
 
diff --git a/src/liblatexila/latexila-utils.c b/src/liblatexila/latexila-utils.c
index 8c06b8a..00f4bcf 100644
--- a/src/liblatexila/latexila-utils.c
+++ b/src/liblatexila/latexila-utils.c
@@ -237,3 +237,56 @@ latexila_utils_str_replace (const gchar *string,
   g_strfreev (chunks);
   return ret;
 }
+
+/**
+ * latexila_utils_file_query_exists_async:
+ * @file: a #GFile.
+ * @cancellable: a #GCancellable.
+ * @callback: the callback to call when the operation is finished.
+ * @user_data: the data to pass to the callback function.
+ *
+ * The asynchronous version of g_file_query_exists(). When the operation is
+ * finished, @callback will be called. You can then call
+ * latexila_utils_file_query_exists_finish() to get the result of the operation.
+ */
+void
+latexila_utils_file_query_exists_async (GFile               *file,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+  g_file_query_info_async (file,
+                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_DEFAULT,
+                           cancellable,
+                           callback,
+                           user_data);
+}
+
+/**
+ * latexila_utils_file_query_exists_finish:
+ * @file: a #GFile.
+ * @result: a #GAsyncResult.
+ *
+ * Finishes the operation started with latexila_utils_file_query_exists_async().
+ * There is no output #GError parameter, so you should check if the operation
+ * has been cancelled (in which case %FALSE will be returned).
+ *
+ * Returns: %TRUE if the file exists and the operation hasn't been cancelled,
+ * %FALSE otherwise.
+ */
+gboolean
+latexila_utils_file_query_exists_finish (GFile        *file,
+                                         GAsyncResult *result)
+{
+  GFileInfo *info = g_file_query_info_finish (file, result, NULL);
+
+  if (info != NULL)
+    {
+      g_object_unref (info);
+      return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/src/liblatexila/latexila-utils.h b/src/liblatexila/latexila-utils.h
index 852a6f0..c1b44a9 100644
--- a/src/liblatexila/latexila-utils.h
+++ b/src/liblatexila/latexila-utils.h
@@ -20,7 +20,7 @@
 #ifndef __LATEXILA_UTILS_H__
 #define __LATEXILA_UTILS_H__
 
-#include <glib.h>
+#include <gio/gio.h>
 
 G_BEGIN_DECLS
 
@@ -34,6 +34,14 @@ gchar *         latexila_utils_str_replace                      (const gchar *st
                                                                  const gchar *search,
                                                                  const gchar *replacement);
 
+void            latexila_utils_file_query_exists_async          (GFile               *file,
+                                                                 GCancellable        *cancellable,
+                                                                 GAsyncReadyCallback  callback,
+                                                                 gpointer             user_data);
+
+gboolean        latexila_utils_file_query_exists_finish         (GFile        *file,
+                                                                 GAsyncResult *result);
+
 G_END_DECLS
 
 #endif /* __LATEXILA_UTILS_H__ */
diff --git a/src/liblatexila/latexila.h b/src/liblatexila/latexila.h
index 0d799f0..a200ab9 100644
--- a/src/liblatexila/latexila.h
+++ b/src/liblatexila/latexila.h
@@ -31,6 +31,7 @@
 #include "latexila-build-view.h"
 #include "latexila-post-processor.h"
 #include "latexila-post-processor-all-output.h"
+#include "latexila-synctex.h"
 #include "latexila-utils.h"
 
 #endif /* __LATEXILA_H__ */


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