[telegnome] Translate to Vala



commit 4acc73ad94bdd094e961a07e11468ad50b0a8dac
Author: Colin Watson <cjwatson debian org>
Date:   Mon Feb 15 12:06:23 2016 +0000

    Translate to Vala
    
    * src/app.c, src/app.h: Translate to ...
    * src/app.vala: ... this.
    * src/channel.c, src/channel.h: Translate to ...
    * src/channel.vala: ... this.
    * src/gui.c, src/gui.h: Translate to ...
    * src/gui.vala: ... this.
    * src/http.c, src/http.h: Translate to ...
    * src/http.vala: ... this.
    * src/legacy-config.c, src/legacy-config.h: Translate to ...
    * src/legacy-config.vala: ... this.
    * src/main.c, src/main.h: Translate to ...
    * src/main.vala: ... this.
    * src/paths.vala.in: New file.
    * src/pixpack.c, src/pixpack.h: Translate to ...
    * src/pixpack.vala: ... this.
    * src/prefs.c, src/prefs.h: Translate to ...
    * src/prefs.vala: ... this.
    * src/uuid.vapi: New file, downloaded from
    <https://git.gnome.org/browse/vala-extra-vapis/plain/uuid.vapi>.
    
    * src/prefs.ui, src/telegnome.ui: Remove signal connections; these
    are now made in code instead, partly to separate code from
    presentation and partly because this makes it easier to ensure we
    connect them to methods on the correct objects.
    
    * src/Makefile.am (AM_CPPFLAGS): Add -include
    $(top_builddir)/config.h.  Move *_CFLAGS to ...
    (AM_CFLAGS): ... here.  Add $(VALA_CFLAGS).
    (AM_VALAFLAGS): Add.
    (telegnome_SOURCES): Replace with Vala source files.
    (CLEANFILES): Add generated C files.
    (paths.vala): New rule.
    * configure.ac: Test for src/main.vala rather than src/main.c.  Test
    for valac.
    * autogen.sh: Test for src/main.vala rather than src/gui.c.
    * po/POTFILES.in: Replace src/gui.c with src/gui.vala.
    * README: Document new valac requirement.
    * src/.gitignore: Add *.c, paths.vala, and telegnome_vala.stamp*.

 README                 |    1 +
 autogen.sh             |    2 +-
 configure.ac           |    4 +-
 po/POTFILES.in         |    2 +-
 src/.gitignore         |    3 +
 src/Makefile.am        |   52 ++-
 src/app.c              |   91 -----
 src/app.h              |   42 ---
 src/app.vala           |   89 +++++
 src/channel.c          |  269 --------------
 src/channel.h          |   48 ---
 src/channel.vala       |   82 +++++
 src/gui.c              |  928 ------------------------------------------------
 src/gui.h              |   83 -----
 src/gui.vala           |  557 +++++++++++++++++++++++++++++
 src/http.c             |  177 ---------
 src/http.h             |   44 ---
 src/http.vala          |  105 ++++++
 src/legacy-config.c    |  209 -----------
 src/legacy-config.h    |   37 --
 src/legacy-config.vala |  150 ++++++++
 src/main.c             |   45 ---
 src/main.h             |   51 ---
 src/main.vala          |   29 ++
 src/paths.vala.in      |   32 ++
 src/pixpack.c          |  257 -------------
 src/pixpack.h          |   44 ---
 src/pixpack.vala       |  110 ++++++
 src/prefs.c            |  406 ---------------------
 src/prefs.h            |   39 --
 src/prefs.ui           |   20 +-
 src/prefs.vala         |  270 ++++++++++++++
 src/telegnome.ui       |   17 +-
 src/uuid.vapi          |   65 ++++
 34 files changed, 1542 insertions(+), 2818 deletions(-)
---
diff --git a/README b/README
index d05bcbc..ea743ed 100644
--- a/README
+++ b/README
@@ -6,6 +6,7 @@ http://telegnome.sourceforge.net
 To compile this package, you need:
 
 An ANSI C-compiler
+valac
 GLib >= 2.44
 GTK+ >= 3.8
 gdk-pixbuf >= 2.26
diff --git a/autogen.sh b/autogen.sh
index 951545c..800d3a0 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -8,7 +8,7 @@ PKG_NAME="TeleGNOME"
 
 (test -f $srcdir/configure.ac \
   && test -d $srcdir/src \
-  && test -f $srcdir/src/gui.c) || {
+  && test -f $srcdir/src/main.vala) || {
     echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
     echo " top-level $PKG_NAME directory"
     exit 1
diff --git a/configure.ac b/configure.ac
index b626fc4..a021396 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 AC_INIT([TeleGNOME], [0.2.1], [cjwatson debian org], [telegnome])
 AC_PREREQ(2.50)
-AC_CONFIG_SRCDIR([src/main.c])
+AC_CONFIG_SRCDIR([src/main.vala])
 AC_CONFIG_MACRO_DIR([m4])
 
 AM_CONFIG_HEADER(config.h)
@@ -23,6 +23,8 @@ AC_PROG_INSTALL
 
 AC_DEFINE_UNQUOTED([SYSCONFDIR], "$sysconfdir", [System configuration directory.])
 
+AM_PROG_VALAC
+
 AM_PATH_GLIB_2_0([2.44.0], [], [AC_MSG_ERROR([GLib >= 2.44.0 is required])], [gobject gio])
 PKG_CHECK_MODULES([TELEGNOME], [gtk+-3.0 >= 3.8 gdk-pixbuf-2.0 >= 2.26 cairo >= 1.10 dconf uuid])
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2ab0517..e6a431c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,7 +1,7 @@
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
 data/org.gnome.telegnome.gschema.xml
-src/gui.c
+src/gui.vala
 [type: gettext/glade] src/menus.ui
 [type: gettext/glade] src/prefs.ui
 [type: gettext/glade] src/telegnome.ui
diff --git a/src/.gitignore b/src/.gitignore
index 3bd8fd9..5f4fbf2 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1,5 +1,8 @@
+*.c
 *.o
 .deps
+paths.vala
 resources.c
 resources.h
 telegnome
+telegnome_vala.stamp*
diff --git a/src/Makefile.am b/src/Makefile.am
index 8fd662e..6074160 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,31 +1,47 @@
-AM_CPPFLAGS = -I. -I$(srcdir)/ $(GLIB_CFLAGS) $(TELEGNOME_CFLAGS)
+AM_CPPFLAGS = -I. -I$(srcdir)/ -include $(top_builddir)/config.h
+AM_CFLAGS = $(GLIB_CFLAGS) $(TELEGNOME_CFLAGS) $(VALA_CFLAGS)
+AM_VALAFLAGS = \
+       --pkg posix \
+       --pkg gtk+-3.0 \
+       --pkg cairo \
+       --pkg dconf
 
 bin_PROGRAMS = telegnome
 
 telegnome_SOURCES = \
-       main.h \
-       main.c \
-       app.h \
-       app.c \
-       http.h \
-       http.c \
-       gui.h \
-       gui.c \
-       prefs.h \
-       prefs.c \
-       pixpack.c \
-       pixpack.h \
-       channel.c \
-       channel.h \
-       legacy-config.c \
-       legacy-config.h
+       app.vala \
+       channel.vala \
+       gui.vala \
+       http.vala \
+       legacy-config.vala \
+       main.vala \
+       paths.vala \
+       pixpack.vala \
+       prefs.vala \
+       uuid.vapi
 
 telegnome_built = \
        resources.c \
        resources.h
 BUILT_SOURCES = $(telegnome_built)
 nodist_telegnome_SOURCES = $(telegnome_built)
-CLEANFILES = $(telegnome_built)
+CLEANFILES = \
+       $(telegnome_built) \
+       app.c \
+       channel.c \
+       gui.c \
+       http.c \
+       legacy-config.c \
+       main.c \
+       pixpack.c \
+       prefs.c
+
+do_subst = sed \
+       -e 's,[ ]sysconfdir[@],$(sysconfdir),g' \
+       -e 's,[ ]localedir[@],$(localedir),g'
+
+paths.vala: paths.vala.in Makefile
+       $(do_subst) < $(srcdir)/paths.vala.in > $@
 
 resources.c resources.h: telegnome.gresource.xml Makefile $(shell $(GLIB_COMPILE_RESOURCES) 
--generate-dependencies --sourcedir $(srcdir) $(srcdir)/telegnome.gresource.xml)
        $(AM_V_GEN) XMLLINT=$(XMLLINT) $(GLIB_COMPILE_RESOURCES) --target $@ --sourcedir $(srcdir) --generate 
--c-name tg $<
diff --git a/src/app.vala b/src/app.vala
new file mode 100644
index 0000000..031522d
--- /dev/null
+++ b/src/app.vala
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/* The main TeleGNOME application. */
+
+namespace Tg {
+
+[CCode (cname = "GETTEXT_PACKAGE")]
+extern const string GETTEXT_PACKAGE;
+
+public class App : Gtk.Application {
+
+       private Gui _gui;
+       public Gui gui { get { return _gui; } }
+
+       private const ActionEntry[] app_entries = {
+               { "quit", activate_quit },
+               { "preferences", activate_preferences },
+               { "help-contents", activate_help_contents },
+               { "about", activate_about },
+               { "set-channel", null, "s", "''", change_state_set_channel }
+       };
+
+       public App () {
+               Object (application_id: "org.gnome.telegnome",
+                       flags: ApplicationFlags.NON_UNIQUE);
+
+               this.startup.connect_after (on_startup);
+       }
+
+       /* These are bodges since we can't pass the Gui object as user data.
+        * It may be better to simply turn Gui into a subclass of
+        * Gtk.Application.
+        */
+
+       public void activate_quit (SimpleAction action, Variant? parameter) {
+               gui.activate_quit (action, parameter);
+       }
+
+       public void activate_preferences (SimpleAction action,
+                                         Variant? parameter) {
+               gui.activate_preferences (action, parameter);
+       }
+
+       public void activate_help_contents (SimpleAction action,
+                                           Variant? parameter) {
+               gui.activate_help_contents (action, parameter);
+       }
+
+       public void activate_about (SimpleAction action, Variant? parameter) {
+               gui.activate_about (action, parameter);
+       }
+
+       public void change_state_set_channel (SimpleAction action,
+                                             Variant? value) {
+               gui.change_state_set_channel (action, value);
+       }
+
+       private void on_startup () {
+               var settings = new Settings (get_application_id ());
+               legacy_convert (settings);
+
+               _gui = new Gui (this, settings);
+               add_action_entries (app_entries, this);
+       }
+
+       public override void activate () {
+               gui.window.present ();
+       }
+
+}
+
+}
diff --git a/src/channel.vala b/src/channel.vala
new file mode 100644
index 0000000..30727f9
--- /dev/null
+++ b/src/channel.vala
@@ -0,0 +1,82 @@
+/*
+ *    Copyright (C) 1999, 2000,
+ *    Dirk-Jan C. Binnema <djcb dds nl>,
+ *    Arjan Scherpenisse <acscherp wins uva nl>
+ *    Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *  
+ * This program 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.
+ *  
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* A channel. */
+
+namespace Tg {
+
+public class Channel : Object {
+       public Settings settings { get; private set; }
+
+       [Description (nick = "UUID", blurb = "UUID")]
+       public string uuid { get; construct; }
+       [Description (nick = "Name", blurb = "Name")]
+       public string? name { get; set; }
+       [Description (nick = "Description", blurb = "Description")]
+       public string? description { get; set; }
+       [Description (nick = "Page URL", blurb = "Page URL")]
+       public string? page_url { get; set; }
+       [Description (nick = "Subpage URL", blurb = "Subpage URL")]
+       public string? subpage_url { get; set; }
+       [Description (nick = "Country", blurb = "Country")]
+       public string? country { get; set; }
+
+       construct {
+               if (uuid == null) {
+                       uint8[] uu = new uint8[16];
+                       UUID.generate (uu);
+                       char[] real_uuid = new char[37];
+                       UUID.unparse (uu, real_uuid);
+                       uuid = (string) real_uuid;
+               }
+               bind_settings ();
+       }
+
+       public Channel (string? uuid = null) {
+               Object (uuid: uuid);
+       }
+
+       public Channel.with_parameters (string? name, string? description,
+                                       string? page_url, string? subpage_url,
+                                       string? country) {
+               Object (name: name, description: description,
+                       page_url: page_url, subpage_url: subpage_url,
+                       country: country);
+       }
+
+       private void bind_settings () {
+               var path = @"/org/gnome/telegnome/channel/$uuid/";
+               settings = new Settings.with_path
+                       ("org.gnome.telegnome.channel", path);
+               settings.bind ("name", this, "name",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("description", this, "description",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("page-url", this, "page_url",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("subpage-url", this, "subpage_url",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("country", this, "country",
+                              SettingsBindFlags.DEFAULT);
+       }
+}
+
+}
diff --git a/src/gui.vala b/src/gui.vala
new file mode 100644
index 0000000..493019a
--- /dev/null
+++ b/src/gui.vala
@@ -0,0 +1,557 @@
+/*
+ *    Copyright (C) 1999, 2000,
+ *    Dirk-Jan C. Binnema <djcb dds nl>,
+ *    Arjan Scherpenisse <acscherp wins uva nl>
+ *    Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *  
+ * This program 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.
+ *  
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* The main user interface. */
+
+namespace Tg {
+
+private static const string notfound_pixmap =
+       "/org/gnome/telegnome/pixmaps/testbeeld.png";
+private static const string logo_pixmap =
+       "/org/gnome/telegnome/pixmaps/telegnome-logo.png";
+private static const string main_ui = "/org/gnome/telegnome/telegnome.ui";
+
+/* The timeout before the input field resets. */
+private static const int kb_timeout = 2500;
+/* The time for which the logo gets displayed. */
+private static const int logo_timeout = 7500;
+
+private enum InputStatus {
+       NEW,
+       CONTINUED
+}
+
+public class Gui : Object {
+
+       [Description (nick = "Channel children", blurb = "List of relative settings paths at which channels 
are stored")]
+       public string[] channel_children { get; set; }
+
+       private string? _current_channel_uuid;
+       [Description (nick = "Current channel", blurb = "Current channel")]
+       public string? current_channel_uuid {
+               get { return _current_channel_uuid; }
+               set {
+                       _current_channel_uuid = value;
+                       var channel = channel_find_by_uuid (value);
+                       if (channel != null)
+                               channel_select (channel);
+               }
+       }
+
+       [Description (nick = "Zoom factor", blurb = "Page zoom factor.  Larger numbers produce larger text.")]
+       public int zoom_factor { get; set; default = 1; }
+       [Description (nick = "Paging enabled", blurb = "Automatically switch page at periodic intervals")]
+       public bool paging_enabled { get; set; default = false; }
+       [Description (nick = "Paging interval", blurb = "Specifies the interval for the auto-pager, in 
milliseconds.")]
+       public int paging_interval { get; set; default = 12000; }
+       [Description (nick = "Current page number", blurb = "Current page number")]
+       public int page_number { get; set; default = -1; }
+       [Description (nick = "Current subpage number", blurb = "Current subpage number")]
+       public int subpage_number { get; set; default = -1; }
+
+       private Channel? _current_channel;
+       public Channel? current_channel { get { return _current_channel; } }
+
+       private Gtk.Builder builder;
+       public Gtk.Window window { get; private set; }
+       private Gtk.Entry entry;
+       private Pixpack pixpack;
+       private Gtk.ProgressBar progress_bar;
+       private Gtk.Statusbar status_bar;
+       private Menu channel_menu;
+       private Settings settings;
+
+       /* For timer input. */
+       private uint? logo_timer_id;
+       private uint? kb_timer_id;
+       private InputStatus kb_status;
+
+       private uint? page_timer_id;
+       private int page_progress;
+
+       private SList<Channel> channels;
+
+       public Gui (Gtk.Application app, Settings settings) {
+               base ();
+
+               /* Register custom type. */
+               assert (typeof (Pixpack).name() != "");
+
+               builder = new Gtk.Builder.from_resource (main_ui);
+               window = builder.get_object ("main_window") as Gtk.Window;
+               app.add_window (window);
+               entry = builder.get_object ("page_entry") as Gtk.Entry;
+               pixpack = builder.get_object ("pixpack") as Pixpack;
+               progress_bar = builder.get_object ("progress_bar")
+                       as Gtk.ProgressBar;
+               status_bar = builder.get_object ("status_bar")
+                       as Gtk.Statusbar;
+
+               window.key_press_event.connect
+                       ((event) => on_keypress (event));
+               entry.activate.connect (() => on_goto_page ());
+               var goto_button = builder.get_object ("goto_button")
+                       as Gtk.ToolButton;
+               goto_button.clicked.connect (() => on_goto_page ());
+               var prev_button = builder.get_object ("prev_button")
+                       as Gtk.ToolButton;
+               prev_button.clicked.connect (() => on_prev_page ());
+               var next_button = builder.get_object ("next_button")
+                       as Gtk.ToolButton;
+               next_button.clicked.connect (() => on_next_page ());
+               var home_button = builder.get_object ("home_button")
+                       as Gtk.ToolButton;
+               home_button.clicked.connect (() => on_home ());
+               var paging_button = builder.get_object("paging_button")
+                       as Gtk.ToggleToolButton;
+               paging_button.clicked.connect (() => on_toggle_paging ());
+
+               channel_menu = app.get_menu_by_id ("channels");
+
+               this.settings = settings;
+               settings.bind ("channel-children", this, "channel_children",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("current-channel", this, "current_channel_uuid",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("zoom-factor", this, "zoom_factor",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("paging-enabled", this, "paging_enabled",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("paging-interval", this, "paging_interval",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("current-page-number", this, "page_number",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("current-subpage-number",
+                              this, "subpage_number",
+                              SettingsBindFlags.DEFAULT);
+
+               window.show_all ();
+
+               kb_timer_id = null;
+               kb_status = InputStatus.NEW;
+
+               channels = new SList<Channel> ();
+               refresh_channel_menu ();
+               _current_channel = channel_find_by_uuid (current_channel_uuid);
+               update_title_bar ();
+
+               update_entry (0, 0);
+               pixpack.load_image_from_resource (logo_pixmap);
+
+               if (page_number > 0)
+                       logo_timer_id = Timeout.add (logo_timeout, logo_timer);
+               else
+                       logo_timer_id = null;
+       }
+
+       private void update_title_bar () {
+               if (current_channel != null) {
+                       var name = current_channel.name;
+                       var desc = current_channel.description;
+                       if (name != null && desc != null) {
+                               var title = _("TeleGNOME: %s (%s)").printf
+                                       (name, desc);
+                               window.set_title (title);
+                       }
+               }
+       }
+
+       /**
+        * logo_timer:
+        *
+        * Remove the logo from the screen and go to a page.
+        */
+       private bool logo_timer () {
+               get_the_page ();
+               logo_timer_id = null;
+               return false;
+       }
+
+       private Channel? channel_find_by_uuid (string uuid) {
+               foreach (var channel in channels) {
+                       if (uuid == channel.uuid)
+                               return channel;
+               }
+               return null;
+       }
+
+       private void channel_select (Channel channel) {
+               _current_channel = channel;
+               update_title_bar ();
+       }
+
+       public void change_state_set_channel (SimpleAction action,
+                                             Variant? value) {
+               current_channel_uuid = value.get_string ();
+               page_number = 100;
+               subpage_number = 0;
+               get_the_page ();
+       }
+
+       private void populate_channel_menu () {
+               channel_menu.remove_all ();
+
+               foreach (var channel in channels) {
+                       var action = @"app.set-channel::$(channel.uuid)";
+                       channel_menu.append (channel.name, action);
+               }
+       }
+
+       /**
+        * reload_channels:
+        *
+        * Load all the channels from settings and store them in the
+        * "channels" property.
+        */
+       private void reload_channels () {
+               var current_uuid = current_channel_uuid;
+
+               channels = new SList<Channel> ();
+               foreach (var uuid in channel_children)
+                       channels.append (new Channel (uuid));
+
+               if (channels == null) {
+                       /* Nothing set up yet; fill in some defaults. */
+                       /* TODO: This is terrible; move into a separate
+                        * file.
+                        */
+                       string[] children = new string[7];
+                       var i = 0;
+                       Channel channel;
+
+                       channel = new Channel.with_parameters
+                               ("Teletext ČT, Czech Republic",
+                                "Czech teletext",
+                                
"http://www.ceskatelevize.cz/services/teletext/picture.php?channel=CT1&page=%03d";,
+                                null,
+                                "cz");
+                       channels.append (channel);
+                       children[i++] = channel.uuid;
+                       channel = new Channel.with_parameters
+                               ("YLE Teksti-TV, Finland",
+                                "Finnish teletext (YLE)",
+                                "http://www.yle.fi/tekstitv/images/P%03d_01.gif";,
+                                "http://www.yle.fi/tekstitv/images/P%03d_%02d.gif";,
+                                "fi");
+                       channels.append (channel);
+                       children[i++] = channel.uuid;
+                       channel = new Channel.with_parameters
+                               ("MTV3 Tekstikanava, Finland",
+                                "Finnish teletext (MTV3)",
+                                "http://www.mtvtekstikanava.fi/new2008/images/%03d-01.gif";,
+                                "http://www.mtvtekstikanava.fi/new2008/images/%03d-%02d.gif";,
+                                "fi");
+                       channels.append (channel);
+                       children[i++] = channel.uuid;
+                       channel = new Channel.with_parameters
+                               ("MTV1, Hungary",
+                                "Hungarian teletext",
+                                "http://www.teletext.hu/mtv1/images/%03d-01.gif";,
+                                "http://www.teletext.hu/mtv1/images/%03d-%02d.gif";,
+                                "hu");
+                       channels.append (channel);
+                       children[i++] = channel.uuid;
+                       channel = new Channel.with_parameters
+                               ("Televideo RAI, Italia",
+                                "Televideo (RAI)",
+                                
"http://www.servizitelevideo.rai.it/televideo/pub/tt4web/Nazionale/16_9_page-%03d.png";,
+                                
"http://www.servizitelevideo.rai.it/televideo/pub/tt4web/Nazionale/16_9_page-%03d.%d.png";,
+                                "it");
+                       channels.append (channel);
+                       children[i++] = channel.uuid;
+                       channel = new Channel.with_parameters
+                               ("Ceefax, United Kingdom",
+                                "UK teletext (BBC)",
+                                "http://www.ceefax.tv/cgi-bin/gfx.cgi?page=%03d_0&font=big&channel=bbc1";,
+                                "http://www.ceefax.tv/cgi-bin/gfx.cgi?page=%03d_%d&font=big&channel=bbc1";,
+                                "gb");
+                       channels.append (channel);
+                       children[i++] = channel.uuid;
+                       settings.set_strv ("channel-children", children);
+               }
+
+               if (current_uuid != null)
+                       current_channel_uuid = current_uuid;
+               if (current_channel == null) {
+                       current_channel_uuid = channels.data.uuid;
+                       page_number = 100;
+                       subpage_number = 0;
+                       if (current_uuid != null)
+                               get_the_page ();
+               }
+       }
+
+       private void refresh_channel_menu () {
+               /* load the channels from settings */
+               reload_channels ();
+
+               /* re-populate the menu */
+               populate_channel_menu ();
+       }
+
+       /**
+        * print_in_statusbar:
+        * @status: A status string.
+        *
+        * Print a string in the status bar.
+        */
+       private void print_in_statusbar (string? status) {
+               var context_id = status_bar.get_context_id ("errors");
+               status_bar.remove_all (context_id);
+               if (status != null)
+                       status_bar.push (context_id, status);
+       }
+
+       private bool pager_timer () {
+               page_progress += paging_interval / 100;
+               progress_bar.set_fraction
+                       (page_progress / (double) paging_interval);
+
+               if (page_progress >= paging_interval) {
+                       page_progress = 0;
+                       progress_bar.set_fraction (0.0);
+                       on_next_page ();
+               }
+               return true;
+       }
+
+       private void on_toggle_paging () {
+               progress_bar.set_fraction (0.0);
+               if (paging_enabled) {
+                       if (page_timer_id != null)
+                               Source.remove (page_timer_id);
+                       page_timer_id = null;
+                       paging_enabled = false;
+               } else {
+                       page_timer_id = Timeout.add (paging_interval / 100,
+                                                    pager_timer);
+                       paging_enabled = true;
+               }
+               page_progress = 0;
+       }
+
+       private void update_page () {
+               /* Save these and restore them, if necessary. */
+               int old_page = page_number;
+               int old_subpage = subpage_number;
+
+               try {
+                       var pixbuf = http_get_image (this);
+                       pixpack.load_image (pixbuf);
+               } catch (HttpError e) {
+                       if (e is HttpError.PIXBUF) {
+                               /* We got an error from the web page.  Maybe
+                                * we forgot the subpage number, or used it
+                                * when we shouldn't.
+                                */
+                               if (subpage_number == 0)
+                                       subpage_number = 1;
+                               else
+                                       subpage_number = 0;
+                               try {
+                                       var pixbuf = http_get_image (this);
+                                       pixpack.load_image (pixbuf);
+                               } catch (HttpError e2) {
+                                       if (subpage_number != 1) {
+                                               /* Maybe we've run out of
+                                                * subpages.  Go to the next
+                                                * main page.
+                                                */
+                                               ++page_number;
+                                               subpage_number = 1;
+                                               update_entry
+                                                       (page_number,
+                                                        subpage_number);
+                                               get_the_page ();
+                                       } else {
+                                               print_in_statusbar
+                                                       (_("Web server error: Wrong page number?"));
+                                               /* Restore. */
+                                               page_number = old_page;
+                                               subpage_number = old_subpage;
+                                               update_entry
+                                                       (page_number,
+                                                        subpage_number);
+                                               pixpack.load_image_from_resource
+                                                       (notfound_pixmap);
+                                       }
+                               }
+                       } else if (e is HttpError.VFS)
+                               print_in_statusbar
+                                       (_("Error making HTTP connection"));
+                       else if (e is HttpError.HTTPQUERY)
+                               print_in_statusbar
+                                       (_("Internal error in HTTP query code"));
+                       else
+                               assert_not_reached ();
+               }
+       }
+
+       /**
+        * update_entry:
+        *
+        * Update the entry box with the current values of page and subpage.
+        */
+       private void update_entry (int page_nr, int subpage_nr) {
+               string full_num;
+               if (subpage_nr > 0)
+                       full_num = @"$page_nr/$subpage_nr";
+               else
+                       full_num = @"$page_nr";
+               entry.set_text (full_num);
+       }
+
+       /**
+        * get_the_page:
+        *
+        * Try to get the page, and do something smart if it fails.
+        */
+       private void get_the_page () {
+               if (logo_timer_id != null)
+                       Source.remove (logo_timer_id);
+               logo_timer_id = null;
+
+               if (current_channel != null)
+                       update_page ();
+
+               update_entry (page_number, subpage_number);
+               print_in_statusbar (null);
+       }
+
+       public void activate_quit (SimpleAction action, Variant? parameter) {
+               Application.get_default ().quit ();
+       }
+
+       public void activate_help_contents (SimpleAction action,
+                                           Variant? parameter) {
+               try {
+                       Gtk.show_uri (window.get_screen (),
+                                     "ghelp:telegnome", Gdk.CURRENT_TIME);
+               } catch (Error e) {
+                       warning ("Error displaying help: %s", e.message);
+               }
+       }
+
+       public void activate_about (SimpleAction action, Variant? parameter) {
+               Gtk.Dialog about = builder.get_object ("about_dialog")
+                       as Gtk.Dialog;
+               about.run ();
+               about.hide ();
+       }
+
+       private void refresh_timer () {
+               var perc = progress_bar.get_fraction ();
+               progress_bar.set_fraction (perc);
+               if (paging_enabled) {
+                       Source.remove (page_timer_id);
+                       page_timer_id = Timeout.add (paging_interval / 100,
+                                                    pager_timer);
+               }
+               page_progress = (int) ((paging_interval / 100) * perc);
+       }
+
+       public void activate_preferences (SimpleAction action,
+                                         Variant? parameter) {
+               new Prefs (this).show ();
+               refresh_channel_menu ();
+               refresh_timer ();
+       }
+
+       private void on_next_page () {
+               if (subpage_number == 0)
+                       ++page_number;
+               else
+                       ++subpage_number;
+
+               update_entry (page_number, subpage_number);
+               get_the_page ();
+       }
+
+       private void on_prev_page () {
+               if (subpage_number > 0)
+                       --subpage_number;
+               if (subpage_number == 0)
+                       --page_number;
+
+               update_entry (page_number, subpage_number);
+               get_the_page ();
+       }
+
+       private void on_home () {
+               page_number = 100;
+               subpage_number = 0;
+
+               update_entry (page_number, subpage_number);
+               get_the_page ();
+       }
+
+       private void on_goto_page () {
+               kb_status = InputStatus.NEW;
+               var entry_text = entry.get_text ();
+               var tokens = entry_text.split ("/", 2);
+               int64? new_page = null;
+               int64 new_subpage = 0;
+               if (tokens.length >= 1) {
+                       if (!int64.try_parse (tokens[0], out new_page))
+                               new_page = null;
+               }
+               if (tokens.length >= 2) {
+                       if (!int64.try_parse (tokens[1], out new_subpage))
+                               new_page = null;
+               }
+               if (new_page == null)
+                       print_in_statusbar (_("Error in page entry"));
+               else {
+                       page_number = (int) new_page;
+                       subpage_number = (int) new_subpage;
+                       get_the_page ();
+               }
+       }
+
+       private bool keyboard_timer () {
+               kb_status = InputStatus.NEW;
+               kb_timer_id = null;
+               return false;
+       }
+
+       private bool on_keypress (Gdk.EventKey event) {
+               if (event.keyval == Gdk.Key.KP_Enter) {
+                       on_goto_page ();
+                       update_entry (page_number, subpage_number);
+                       return false;
+               }
+
+               if (!entry.is_focus)
+                       entry.grab_focus ();
+
+               if (kb_status == InputStatus.NEW)
+                       entry.set_text ("");
+
+               if (kb_timer_id != null)
+                       Source.remove (kb_timer_id);
+               kb_timer_id = Timeout.add (kb_timeout, keyboard_timer);
+               kb_status = InputStatus.CONTINUED;
+               return false;
+       }
+
+}
+
+}
diff --git a/src/http.vala b/src/http.vala
new file mode 100644
index 0000000..3dfd095
--- /dev/null
+++ b/src/http.vala
@@ -0,0 +1,105 @@
+/*
+ *    Copyright (C) 1999, 2000,
+ *    Dirk-Jan C. Binnema <djcb dds nl>,
+ *    Arjan Scherpenisse <acscherp wins uva nl>
+ *    Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *  
+ * This program 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.
+ *  
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* HTTP fetches. */
+
+namespace Tg {
+
+public errordomain HttpError {
+       /**
+        * Corrupt image.
+        */
+       PIXBUF,
+       /**
+        * Error from the VFS layer.
+        */
+       VFS,
+       /**
+        * Error constructing an HTTP query URL.
+        */
+       HTTPQUERY
+}
+
+public Gdk.Pixbuf http_get_image (Gui gui) throws HttpError {
+       var channel = gui.current_channel;
+       string? url = null;
+       if (gui.subpage_number > 0) {
+               var subpage_url = channel.subpage_url;
+               if (subpage_url != null && subpage_url != "")
+                       url = subpage_url.printf (gui.page_number,
+                                                 gui.subpage_number);
+       }
+       if (url == null || url == "") {
+               var page_url = channel.page_url;
+               if (page_url != null && page_url != "")
+                       url = page_url.printf (gui.page_number);
+       }
+       if (url == null || url == "")
+               throw new HttpError.HTTPQUERY ("");
+
+       var http_file = File.new_for_uri (url);
+       FileInputStream http_input;
+       try {
+               http_input = http_file.read ();
+       } catch (Error e) {
+               warning ("Unable to fetch '%s': %s", url, e.message);
+               throw new HttpError.VFS (e.message);
+       }
+
+       var loader = new Gdk.PixbufLoader ();
+       var buf = new uint8[4096];
+       for (;;) {
+               ssize_t bytes_read;
+               try {
+                       bytes_read = http_input.read (buf);
+                       if (bytes_read == 0)
+                               break;
+               } catch (IOError e) {
+                       warning ("Unable to read data from '%s': %s",
+                                url, e.message);
+                       throw new HttpError.VFS (e.message);
+               }
+               try {
+                       loader.write (buf[0:bytes_read]);
+               } catch (Error e) {
+                       warning ("Unable to parse image from '%s': %s",
+                                url, e.message);
+                       throw new HttpError.PIXBUF (e.message);
+               }
+       }
+       try {
+               loader.close ();
+       } catch (Error e) {
+               warning ("Unable to parse image from '%s': %s",
+                        url, e.message);
+               throw new HttpError.PIXBUF (e.message);
+       }
+
+       var pixbuf = loader.get_pixbuf ();
+       if (pixbuf == null) {
+               warning ("Pixbuf loader did not create a pixbuf from '%s'",
+                        url);
+               throw new HttpError.PIXBUF ("");
+       }
+       return (owned) pixbuf;
+}
+
+}
diff --git a/src/legacy-config.vala b/src/legacy-config.vala
new file mode 100644
index 0000000..2f10960
--- /dev/null
+++ b/src/legacy-config.vala
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/* Compatibility with old (gnome-config) configuration files. */
+
+namespace Tg {
+
+/* There is no sensible conversion path from gnome-config to GSettings,
+ * especially as we skipped GConf.  Fortunately, gnome-config files were
+ * pretty much just GKeyFiles, so we can roll our own without too much
+ * difficulty.
+ */
+
+private SList<KeyFile> read_files () {
+       var names = new SList<string> ();
+       names.append (Path.build_filename (get_sysconf_dir (), "gnome",
+                                          "config-override", "telegnome"));
+       names.append (Path.build_filename (Environment.get_home_dir (),
+                                          ".gnome2", "telegnome"));
+       names.append (Path.build_filename (get_sysconf_dir (), "gnome",
+                                          "config", "telegnome"));
+       var keyfiles = new SList<KeyFile> ();
+       foreach (var name in names) {
+               var keyfile = new KeyFile ();
+               try {
+                       keyfile.load_from_file (name, KeyFileFlags.NONE);
+                       keyfiles.append ((owned) keyfile);
+               } catch (FileError e) {
+               } catch (KeyFileError e) {
+               }
+       }
+       return keyfiles;
+}
+
+private string? get_value (SList<KeyFile> keyfiles, string group, string key) {
+       foreach (unowned KeyFile keyfile in keyfiles) {
+               try {
+                       return keyfile.get_string (group, key);
+               } catch (KeyFileError e) {
+               }
+       }
+
+       return null;
+}
+
+public void legacy_convert (Settings settings) {
+       if (settings.get_boolean ("legacy-migration-complete"))
+               return;
+
+       var keyfiles = read_files ();
+
+       string val;
+       var channel_count = 0;
+       var current_channel = -1;
+
+       val = get_value (keyfiles, "Channels", "count");
+       if (val != null)
+               channel_count = int.parse (val);
+
+       val = get_value (keyfiles, "Default", "server");
+       if (val != null)
+               current_channel = int.parse (val);
+
+       val = get_value (keyfiles, "Zooming", "factor");
+       if (val != null)
+               /* clip to 1 or 2 */
+               settings.set_int ("zoom-factor", int.parse (val) == 1 ? 1 : 2);
+
+       val = get_value (keyfiles, "Paging", "enabled");
+       if (val != null) {
+               bool paging_enabled;
+               if (val[0].tolower () == 't' || val[0].tolower () == 'y' ||
+                   int.parse (val) != 0)
+                       paging_enabled = true;
+               else
+                       paging_enabled = false;
+               settings.set_boolean ("paging-enabled", paging_enabled);
+       }
+
+       val = get_value (keyfiles, "Paging", "interval");
+       if (val != null)
+               settings.set_int ("paging-interval", int.parse (val));
+
+       val = get_value (keyfiles, "Paging", "page_nr");
+       if (val != null)
+               settings.set_int ("current-page-number", int.parse (val));
+
+       val = get_value (keyfiles, "Paging", "subpage_nr");
+       if (val != null)
+               settings.set_int ("current-subpage-number", int.parse (val));
+
+       string[] children = new string[channel_count + 1];
+       for (var i = 0; i < channel_count; ++i) {
+               var channel_group = @"Channel$i";
+               uint8[] uu = new uint8[16];
+               UUID.generate (uu);
+               char[] uuid_bytes = new char[37];
+               UUID.unparse (uu, uuid_bytes);
+               string uuid = (string) uuid_bytes;
+               var child_path = @"/org/gnome/telegnome/channel/$uuid/";
+               children[i] = uuid;
+               var channel_settings = new Settings.with_path
+                       ("org.gnome.telegnome.channel", child_path);
+
+               val = get_value (keyfiles, channel_group, "name");
+               if (val != null)
+                       channel_settings.set_string ("name", val);
+
+               val = get_value (keyfiles, channel_group, "desc");
+               if (val != null)
+                       channel_settings.set_string ("description", val);
+
+               val = get_value (keyfiles, channel_group, "page_url");
+               if (val != null)
+                       channel_settings.set_string ("page-url", val);
+
+               val = get_value (keyfiles, channel_group, "subpage_url");
+               if (val != null)
+                       channel_settings.set_string ("subpage-url", val);
+
+               val = get_value (keyfiles, channel_group, "country");
+               if (val != null)
+                       channel_settings.set_string ("country", val);
+
+               if (i == current_channel)
+                       settings.set_string ("current-channel", uuid);
+       }
+       settings.set_strv ("channel-children", children);
+
+       settings.set_boolean ("legacy-migration-complete", true);
+       Settings.sync ();
+}
+
+}
diff --git a/src/main.vala b/src/main.vala
new file mode 100644
index 0000000..aa6d758
--- /dev/null
+++ b/src/main.vala
@@ -0,0 +1,29 @@
+/*
+ *    Copyright (C) 1999, 2000,
+ *    Dirk-Jan C. Binnema <djcb dds nl>,
+ *    Arjan Scherpenisse <acscherp wins uva nl>
+ *    Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *  
+ * This program 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.
+ *  
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+public static int main (string[] args) {
+       Intl.bindtextdomain ("telegnome", Tg.get_locale_dir ());
+       Intl.bind_textdomain_codeset ("telegnome", "UTF-8");
+       Intl.textdomain ("telegnome");
+
+       var app = new Tg.App ();
+       return app.run (args);
+}
diff --git a/src/paths.vala.in b/src/paths.vala.in
new file mode 100644
index 0000000..5d07b66
--- /dev/null
+++ b/src/paths.vala.in
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/* Configured paths. */
+
+namespace Tg {
+
+public string get_sysconf_dir () {
+       return "@sysconfdir@";
+}
+
+public string get_locale_dir () {
+       return "@localedir@";
+}
+
+}
diff --git a/src/pixpack.vala b/src/pixpack.vala
new file mode 100644
index 0000000..53423f1
--- /dev/null
+++ b/src/pixpack.vala
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2000 Dirk-Jan C. Binnema <djcb dds nl>
+ * Copyright (C) 2008-2016 Colin Watson <cjwatson debian org>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* Image-rendering widget. */
+
+namespace Tg {
+
+public class Pixpack : Gtk.Widget {
+       private Gdk.Pixbuf? pixbuf;
+
+       [Description (nick = "Autosize", blurb = "If true, set the widget's size request to the size of the 
image.")]
+       public bool autosize { get; set; default = false; }
+
+       public Pixpack () {
+               base (can_focus: true, receives_default: true);
+
+               this.pixbuf = null;
+
+               this.set_has_window (true);
+
+               this.unrealize.connect (on_unrealize);
+       }
+
+       public override void realize () {
+               set_realized (true);
+               Gtk.Allocation allocation;
+               get_allocation (out allocation);
+               var attributes = Gdk.WindowAttr () {
+                       window_type = Gdk.WindowType.CHILD,
+                       x = allocation.x,
+                       y = allocation.y,
+                       height = allocation.height,
+                       width = allocation.width,
+                       wclass = Gdk.WindowWindowClass.INPUT_OUTPUT,
+                       visual = get_visual (),
+                       event_mask = get_events () |
+                                    Gdk.EventMask.EXPOSURE_MASK
+               };
+               var attributes_mask = Gdk.WindowAttributesType.X |
+                                     Gdk.WindowAttributesType.Y |
+                                     Gdk.WindowAttributesType.VISUAL;
+               var window = new Gdk.Window (get_parent_window (), attributes,
+                                            attributes_mask);
+               set_window (window);
+               window.set_user_data (this);
+       }
+
+       private void on_unrealize () {
+               if (get_mapped ())
+                       unmap ();
+
+               set_mapped (false);
+       }
+
+       public override bool draw (Cairo.Context cr) {
+               if (pixbuf == null)
+                       return false;
+
+               var width = get_allocated_width ();
+               var height = get_allocated_height ();
+
+               cr.set_source_rgb (0, 0, 0);
+               cr.fill ();
+               Gdk.cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+               cr.scale (width / pixbuf.get_width (),
+                         height / pixbuf.get_height ());
+               cr.paint ();
+
+               return true;
+       }
+
+       public void load_image (Gdk.Pixbuf pixbuf) {
+               this.pixbuf = pixbuf;
+
+               /* This forces a repaint. */
+               if (autosize)
+                       set_size_request (this.pixbuf.get_width (),
+                                         this.pixbuf.get_height ());
+               queue_draw ();
+       }
+
+       public void load_image_from_resource (string resource_path) {
+               try {
+                       var pixbuf = new Gdk.Pixbuf.from_resource
+                               (resource_path);
+                       load_image (pixbuf);
+               } catch (Error e) {
+                       assert_no_error (e);
+               }
+       }
+
+}
+
+}
diff --git a/src/prefs.ui b/src/prefs.ui
index 7a1bae6..b4a04f2 100644
--- a/src/prefs.ui
+++ b/src/prefs.ui
@@ -78,11 +78,6 @@
                         </child>
                       </object>
                     </child>
-                    <child internal-child="selection">
-                      <object class="GtkTreeSelection">
-                        <signal name="changed" handler="tg_prefs_channel_selection_changed_cb"/>
-                      </object>
-                    </child>
                   </object>
                   <packing>
                     <property name="left-attach">0</property>
@@ -101,9 +96,8 @@
                     <property name="row-homogeneous">TRUE</property>
                     <property name="row-spacing">4</property>
                     <child>
-                      <object class="GtkButton">
+                      <object class="GtkButton" id="move_up_button">
                         <property name="label" translatable="yes">Move up</property>
-                        <signal name="clicked" handler="tg_prefs_channel_move_up_cb"/>
                       </object>
                       <packing>
                         <property name="left-attach">0</property>
@@ -111,9 +105,8 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkButton">
+                      <object class="GtkButton" id="move_down_button">
                         <property name="label" translatable="yes">Move down</property>
-                        <signal name="clicked" handler="tg_prefs_channel_move_down_cb"/>
                       </object>
                       <packing>
                         <property name="left-attach">0</property>
@@ -121,9 +114,8 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkButton">
+                      <object class="GtkButton" id="add_button">
                         <property name="label" translatable="yes">Add...</property>
-                        <signal name="clicked" handler="tg_prefs_channel_add_cb"/>
                       </object>
                       <packing>
                         <property name="left-attach">0</property>
@@ -131,9 +123,8 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkButton">
+                      <object class="GtkButton" id="delete_button">
                         <property name="label" translatable="yes">Delete</property>
-                        <signal name="clicked" handler="tg_prefs_channel_delete_cb"/>
                       </object>
                       <packing>
                         <property name="left-attach">0</property>
@@ -141,9 +132,8 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkButton">
+                      <object class="GtkButton" id="edit_button">
                         <property name="label" translatable="yes">Edit</property>
-                        <signal name="clicked" handler="tg_prefs_channel_edit_cb"/>
                       </object>
                       <packing>
                         <property name="left-attach">0</property>
diff --git a/src/prefs.vala b/src/prefs.vala
new file mode 100644
index 0000000..2605efe
--- /dev/null
+++ b/src/prefs.vala
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2000 Arjan Scherpenisse <acscherp wins uva nl>
+ * Copyright (C) 2016 Colin Watson <cjwatson debian org>
+ *  
+ * This program 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.
+ *  
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* The preferences dialog. */
+
+namespace Tg {
+
+private static const string prefs_ui = "/org/gnome/telegnome/prefs.ui";
+
+private enum Column {
+       COUNTRY,
+       NAME,
+       CHANNEL
+}
+
+public class Prefs : Object {
+
+       private Settings settings;
+       private Gtk.Builder builder;
+       private Gtk.Dialog dialog;
+       private Gtk.ListStore channel_store;
+       private Gtk.TreeView channel_view;
+       private Gtk.Label channel_label;
+       private Gtk.Dialog channel_dialog;
+       private Gtk.Entry name_entry;
+       private Gtk.Entry description_entry;
+       private Gtk.Entry page_url_entry;
+       private Gtk.Entry subpage_url_entry;
+       private Gtk.Entry country_entry;
+
+       public Prefs (Gui gui) {
+               settings = new Settings ("org.gnome.telegnome");
+
+               builder = new Gtk.Builder ();
+               builder.expose_object ("main_window", gui.window);
+               try {
+                       builder.add_from_resource (prefs_ui);
+               } catch (Error e) {
+                       error ("failed to add UI: %s", e.message);
+               }
+               dialog = builder.get_object ("prefs_dialog") as Gtk.Dialog;
+               channel_store = builder.get_object ("prefs_channel_store")
+                       as Gtk.ListStore;
+               channel_view = builder.get_object ("prefs_channel_view")
+                       as Gtk.TreeView;
+               channel_label = builder.get_object ("prefs_channel_label")
+                       as Gtk.Label;
+               channel_dialog = builder.get_object ("channel_dialog")
+                       as Gtk.Dialog;
+               name_entry = builder.get_object ("name_entry") as Gtk.Entry;
+               description_entry = builder.get_object ("description_entry")
+                       as Gtk.Entry;
+               page_url_entry = builder.get_object ("page_url_entry")
+                       as Gtk.Entry;
+               subpage_url_entry = builder.get_object ("subpage_url_entry")
+                       as Gtk.Entry;
+               country_entry = builder.get_object ("country_entry")
+                       as Gtk.Entry;
+
+               channel_view.get_selection ().changed.connect
+                       ((selection) =>
+                        on_channel_selection_changed (selection));
+               var move_up_button = builder.get_object ("move_up_button")
+                       as Gtk.Button;
+               move_up_button.clicked.connect (() => on_channel_move_up ());
+               var move_down_button = builder.get_object ("move_down_button")
+                       as Gtk.Button;
+               move_down_button.clicked.connect
+                       (() => on_channel_move_down ());
+               var add_button = builder.get_object ("add_button")
+                       as Gtk.Button;
+               add_button.clicked.connect (() => on_channel_add ());
+               var delete_button = builder.get_object ("delete_button")
+                       as Gtk.Button;
+               delete_button.clicked.connect (() => on_channel_delete ());
+               var edit_button = builder.get_object ("edit_button")
+                       as Gtk.Button;
+               edit_button.clicked.connect (() => on_channel_edit ());
+       }
+
+       private void append_channel (Channel channel) {
+               Gtk.TreeIter iter;
+               channel_store.append (out iter);
+               channel_store.set (iter,
+                                  Column.COUNTRY, channel.country,
+                                  Column.NAME, channel.name,
+                                  Column.CHANNEL, channel,
+                                  -1);
+       }
+
+       private void fill_channel_list () {
+               channel_store.clear ();
+               var children = settings.get_strv ("channel-children");
+               foreach (var child in children)
+                       append_channel (new Channel (child));
+       }
+
+       private bool edit_channel (Channel orig) {
+               var settings = orig.settings;
+               settings.delay ();
+               settings.bind ("name", name_entry, "text",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("description", description_entry, "text",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("page-url", page_url_entry, "text",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("subpage-url", subpage_url_entry, "text",
+                              SettingsBindFlags.DEFAULT);
+               settings.bind ("country", country_entry, "text",
+                              SettingsBindFlags.DEFAULT);
+
+               channel_dialog.show_all ();
+               var reply = channel_dialog.run ();
+               bool changed;
+               switch (reply) {
+                       case Gtk.ResponseType.OK:
+                               settings.apply ();
+                               changed = true;
+                               break;
+                       default:
+                               settings.revert ();
+                               changed = false;
+                               break;
+               }
+               channel_dialog.hide ();
+
+               return changed;
+       }
+
+       private void on_channel_selection_changed
+                       (Gtk.TreeSelection selection) {
+               Gtk.TreeIter iter;
+               string description;
+               if (selection.get_selected (null, out iter)) {
+                       Channel channel;
+                       channel_store.get (iter, Column.CHANNEL, out channel,
+                                          -1);
+                       description = channel.description;
+               } else
+                       description = "";
+               channel_label.set_text (description);
+       }
+
+       private void sync_channel_children () {
+               var rows = 0;
+               Gtk.TreeIter iter;
+               var valid = channel_store.get_iter_first (out iter);
+               while (valid) {
+                       ++rows;
+                       valid = channel_store.iter_next (ref iter);
+               }
+
+               var children = new string[rows + 1];
+               var i = 0;
+               valid = channel_store.get_iter_first (out iter);
+               while (valid) {
+                       assert (i < rows);
+                       Channel channel;
+                       channel_store.get (iter, Column.CHANNEL, out channel,
+                                          -1);
+                       children[i++] = channel.uuid;
+                       valid = channel_store.iter_next (ref iter);
+               }
+               settings.set_strv ("channel-children", children);
+       }
+
+       private void on_channel_add () {
+               Channel channel = new Channel ();
+               if (edit_channel (channel) && channel.name.length > 0)
+                       append_channel (channel);
+       }
+
+       private void on_channel_move_up () {
+               var selection = channel_view.get_selection ();
+               Gtk.TreeIter iter;
+               if (!selection.get_selected (null, out iter))
+                       return;
+               var path = channel_store.get_path (iter);
+               assert (path != null);
+               var depth = path.get_depth ();
+               assert (depth == 1);
+               var indices = path.get_indices ();
+               if (indices[0] > 0) {
+                       Gtk.TreeIter previous_iter;
+                       channel_store.iter_nth_child (out previous_iter, null,
+                                                     indices[0] - 1);
+                       channel_store.swap (iter, previous_iter);
+                       sync_channel_children ();
+               }
+       }
+
+       private void on_channel_move_down () {
+               var selection = channel_view.get_selection ();
+               Gtk.TreeIter iter;
+               if (!selection.get_selected (null, out iter))
+                       return;
+               Gtk.TreeIter next_iter = iter;
+               if (channel_store.iter_next (ref next_iter)) {
+                       channel_store.swap (iter, next_iter);
+                       sync_channel_children ();
+               }
+       }
+
+       private void on_channel_edit () {
+               var selection = channel_view.get_selection ();
+               Gtk.TreeIter iter;
+               if (!selection.get_selected (null, out iter))
+                       return;
+               Channel channel;
+               channel_store.get (iter, Column.CHANNEL, out channel, -1);
+               if (edit_channel (channel))
+                       channel_store.set (iter,
+                                          Column.COUNTRY, channel.country,
+                                          Column.NAME, channel.name,
+                                          -1);
+       }
+
+       private void on_channel_delete () {
+               var selection = channel_view.get_selection ();
+               Gtk.TreeIter iter;
+               if (!selection.get_selected (null, out iter))
+                       return;
+
+               Channel channel;
+               channel_store.get (iter, Column.CHANNEL, out channel, -1);
+               string old_uuid = channel.uuid;
+               channel_store.remove (iter);
+               sync_channel_children ();
+
+               /* Clear out settings debris from the deleted channel if
+                * possible.
+                */
+               if (settings.backend.get_type ().name () ==
+                   "DConfSettingsBackend") {
+                       var path = @"/org/gnome/telegnome/channel/$old_uuid/";
+                       var client = new DConf.Client ();
+                       try {
+                               client.write_sync (path, null);
+                       } catch (Error e) {
+                               assert_no_error (e);
+                       }
+               }
+       }
+
+       public void show () {
+               fill_channel_list ();
+               dialog.show_all ();
+               dialog.run ();
+               dialog.hide ();
+       }
+
+}
+
+}
diff --git a/src/telegnome.ui b/src/telegnome.ui
index fd43de9..86f3155 100644
--- a/src/telegnome.ui
+++ b/src/telegnome.ui
@@ -22,7 +22,6 @@
   <object class="GtkApplicationWindow" id="main_window">
     <property name="title" translatable="TRUE">TeleGNOME: Teletext for GNOME</property>
     <property name="resizable">FALSE</property>
-    <signal name="key-press-event" handler="tg_cb_keypress"/>
     <child>
       <object class="GtkGrid">
         <child>
@@ -47,7 +46,6 @@
                         <property name="width-chars">6</property>
                         <property name="tooltip-text" translatable="yes">Page number</property>
                         <property name="margin">5</property>
-                        <signal name="activate" handler="tg_gui_cb_goto_page"/>
                       </object>
                       <packing>
                         <property name="left-attach">1</property>
@@ -59,43 +57,38 @@
               </object>
             </child>
             <child>
-              <object class="GtkToolButton">
+              <object class="GtkToolButton" id="goto_button">
                 <property name="label" translatable="yes">_Jump to</property>
                 <property name="icon-name">go-jump</property>
                 <property name="tooltip-text" translatable="yes">Go To Page</property>
-                <signal name="clicked" handler="tg_gui_cb_goto_page"/>
               </object>
             </child>
             <child>
-              <object class="GtkToolButton">
+              <object class="GtkToolButton" id="prev_button">
                 <property name="label" translatable="yes">_Back</property>
                 <property name="icon-name">go-previous</property>
                 <property name="tooltip-text" translatable="yes">Get Previous Page</property>
-                <signal name="clicked" handler="tg_gui_cb_prev_page"/>
               </object>
             </child>
             <child>
-              <object class="GtkToolButton">
+              <object class="GtkToolButton" id="next_button">
                 <property name="label" translatable="yes">_Forward</property>
                 <property name="icon-name">go-next</property>
                 <property name="tooltip-text" translatable="yes">Get Next Page</property>
-                <signal name="clicked" handler="tg_gui_cb_next_page"/>
               </object>
             </child>
             <child>
-              <object class="GtkToolButton">
+              <object class="GtkToolButton" id="home_button">
                 <property name="label" translatable="yes">_Home</property>
                 <property name="icon-name">go-home</property>
                 <property name="tooltip-text" translatable="yes">Go to the home page</property>
-                <signal name="clicked" handler="tg_gui_cb_home"/>
               </object>
             </child>
             <child>
-              <object class="GtkToggleToolButton">
+              <object class="GtkToggleToolButton" id="paging_button">
                 <property name="label" translatable="yes">_Play</property>
                 <property name="icon-name">media-playback-start</property>
                 <property name="tooltip-text" translatable="yes">Toggles auto-paging</property>
-                <signal name="toggled" handler="tg_gui_cb_toggle_paging"/>
               </object>
             </child>
           </object>
diff --git a/src/uuid.vapi b/src/uuid.vapi
new file mode 100644
index 0000000..ce02bbe
--- /dev/null
+++ b/src/uuid.vapi
@@ -0,0 +1,65 @@
+/* Downloaded from:
+ *   https://git.gnome.org/browse/vala-extra-vapis/plain/uuid.vapi
+ * on 2016-02-08.
+ */
+
+/* libuuid Vala Bindings
+ * Copyright 2014 Evan Nemerson <evan nemerson com>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+[CCode (cheader_filename = "uuid.h", lower_case_cprefix = "uuid_")]
+namespace UUID {
+       [CCode (cname = "int", has_type_id = false)]
+       public enum Variant {
+               NCS,
+               DCE,
+               MICROSOFT,
+               OTHER
+       }
+
+       [CCode (cname = "int", has_type_id = false)]
+       public enum Type {
+               DCE_TIME,
+               DCE_RANDOM
+       }
+
+       public static void clear ([CCode (array_length = false)] uint8 uu[16]);
+       public static void copy (uint8 dst[16], uint8 src[16]);
+
+       public static void generate ([CCode (array_length = false)] uint8 @out[16]);
+       public static void generate_random ([CCode (array_length = false)] uint8 @out[16]);
+       public static void generate_time ([CCode (array_length = false)] uint8 @out[16]);
+       public static void generate_time_safe ([CCode (array_length = false)] uint8 @out[16]);
+
+       public static bool is_null ([CCode (array_length = false)] uint8 uu[16]);
+
+       public static int parse (string in, [CCode (array_length = false)] uint8 uu[16]);
+
+       public static void unparse ([CCode (array_length = false)] uint8 uu[16], [CCode (array_length = 
false)] char @out[37]);
+       public static void unparse_lower ([CCode (array_length = false)] uint8 uu[16], [CCode (array_length = 
false)] char @out[37]);
+       public static void unparse_upper ([CCode (array_length = false)] uint8 uu[16], [CCode (array_length = 
false)] char @out[37]);
+
+       public static time_t time ([CCode (array_length = false)] uint8 uu[16], out Posix.timeval ret_tv);
+       public static UUID.Type type ([CCode (array_length = false)] uint8 uu[16]);
+       public static UUID.Variant variant ([CCode (array_length = false)] uint8 uu[16]);
+}


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