[totem] Merge chapters plugin with a master branch



commit 66d1958a89affb91380777409bd863faf668e872
Author: Alexander Saprykin <xelfium gmail com>
Date:   Fri Jul 2 00:17:08 2010 +0400

    Merge chapters plugin with a master branch
    
    New chapters plugin
    Add option to preferences to auto-load external chapters

 configure.in                                  |   11 +-
 data/totem.schemas.in                         |   14 +
 data/totem.ui                                 |   95 ++
 po/POTFILES.in                                |    5 +
 src/plugins/chapters/Makefile.am              |   47 +
 src/plugins/chapters/chapters-edit.ui         |   35 +
 src/plugins/chapters/chapters-list.ui         |  237 +++++
 src/plugins/chapters/chapters.totem-plugin.in |    9 +
 src/plugins/chapters/totem-chapters-utils.c   |   95 ++
 src/plugins/chapters/totem-chapters-utils.h   |   33 +
 src/plugins/chapters/totem-chapters.c         | 1259 +++++++++++++++++++++++++
 src/plugins/chapters/totem-cmml-parser.c      |  826 ++++++++++++++++
 src/plugins/chapters/totem-cmml-parser.h      |   82 ++
 src/plugins/chapters/totem-edit-chapter.c     |  145 +++
 src/plugins/chapters/totem-edit-chapter.h     |   58 ++
 src/totem-preferences.c                       |   43 +-
 16 files changed, 2992 insertions(+), 2 deletions(-)
---
diff --git a/configure.in b/configure.in
index 6f7d9db..8f3c695 100644
--- a/configure.in
+++ b/configure.in
@@ -66,7 +66,7 @@ AC_SUBST(TOTEM_API_VERSION)
 AC_DEFINE_UNQUOTED(TOTEM_API_VERSION, ["$TOTEM_API_VERSION"], [Define to the Totem plugin API version])
 
 # The full list of plugins
-allowed_plugins="bemused brasero-disc-recorder coherence_upnp dbus-service galago gromit iplayer jamendo lirc media-player-keys mythtv ontop opensubtitles properties publish pythonconsole sample-python sample-vala screensaver screenshot sidebar-test skipto thumbnail tracker youtube"
+allowed_plugins="bemused brasero-disc-recorder chapters coherence_upnp dbus-service galago gromit iplayer jamendo lirc media-player-keys mythtv ontop opensubtitles properties publish pythonconsole sample-python sample-vala screensaver screenshot sidebar-test skipto thumbnail tracker youtube"
 
 PLUGINDIR='${libdir}/totem/plugins'
 AC_SUBST(PLUGINDIR)
@@ -537,6 +537,14 @@ for plugin in ${used_plugins}; do
 				add_plugin="0"
 			fi
 		;;
+		chapters)
+			PKG_CHECK_MODULES(CHAPTERS, libxml-2.0 >= 2.6.0 gtk+-3.0 glib-2.0 >= 2.15.0,
+				[BUILD_CHAPTERS=yes], [BUILD_CHAPTERS=no])
+			if test "${BUILD_CHAPTERS}" != "yes" ; then
+				plugin_error_or_ignore "you need gtk+-3.0, glib-2.0 >= 2.15.0 and libxml-2.0 >= 2.6.0 to use the chapters plugin"
+				add_plugin="0"
+			fi
+		;;
 	esac
 
 	# Add the specified plugin
@@ -797,6 +805,7 @@ src/plugins/pythonconsole/Makefile
 src/plugins/publish/Makefile
 src/plugins/jamendo/Makefile
 src/plugins/brasero-disc-recorder/Makefile
+src/plugins/chapters/Makefile
 src/backend/Makefile
 browser-plugin/Makefile
 data/Makefile
diff --git a/data/totem.schemas.in b/data/totem.schemas.in
index 409042e..342c332 100644
--- a/data/totem.schemas.in
+++ b/data/totem.schemas.in
@@ -304,6 +304,20 @@
       </schema>
 
       <schema>
+        <key>/schemas/apps/totem/autoload_chapters</key>
+	<applyto>/apps/totem/autoload_chapters</applyto>
+	<owner>totem</owner>
+	<type>bool</type>
+	<default>true</default>
+	<locale name="C">
+	  <short>Whether to autoload external chapter files when a movie is loaded</short>
+	  <long>
+	    Whether to autoload external chapter files when a movie is loaded.
+	  </long>
+	</locale>
+      </schema>
+
+      <schema>
         <key>/schemas/apps/totem/remember_position</key>
 	<applyto>/apps/totem/remember_position</applyto>
 	<owner>totem</owner>
diff --git a/data/totem.ui b/data/totem.ui
index dfcbaa6..587aa31 100644
--- a/data/totem.ui
+++ b/data/totem.ui
@@ -1109,6 +1109,101 @@
 		  <property name="fill">True</property>
 		</packing>
 	      </child>
+
+	      <child>
+		<object class="GtkVBox" id="vbox7">
+		  <property name="visible">True</property>
+		  <property name="homogeneous">False</property>
+		  <property name="spacing">6</property>
+                  <property name="orientation">vertical</property>
+
+		  <child>
+		    <object class="GtkLabel" id="tpw_ext_chapters_label">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">External Chapters</property>
+		      <property name="use_underline">False</property>
+		      <property name="use_markup">True</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		      <attributes>
+		        <attribute name="weight" value="bold"/>
+                      </attributes>
+		    </object>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">False</property>
+		      <property name="fill">False</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <object class="GtkAlignment" id="alignment3_2">
+		      <property name="visible">True</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xscale">1</property>
+		      <property name="yscale">1</property>
+		      <property name="top_padding">0</property>
+		      <property name="bottom_padding">0</property>
+		      <property name="left_padding">12</property>
+		      <property name="right_padding">0</property>
+
+		      <child>
+			<object class="GtkTable" id="table3_2">
+			  <property name="visible">True</property>
+			  <property name="n_rows">1</property>
+			  <property name="n_columns">2</property>
+			  <property name="homogeneous">False</property>
+			  <property name="row_spacing">6</property>
+			  <property name="column_spacing">12</property>
+
+			  <child>
+			    <object class="GtkCheckButton" id="tpw_auto_chapters_checkbutton">
+			      <property name="visible">True</property>
+			      <property name="can_focus">True</property>
+			      <property name="label" translatable="yes">Load _chapter files when movie is loaded</property>
+			      <property name="use_underline">True</property>
+			      <property name="relief">GTK_RELIEF_NORMAL</property>
+			      <property name="focus_on_click">True</property>
+			      <property name="active">False</property>
+			      <property name="inconsistent">False</property>
+			      <property name="draw_indicator">True</property>
+			      <signal name="toggled" handler="auto_chapters_toggled_cb"/>
+		            </object>
+			    <packing>
+			      <property name="left_attach">0</property>
+			      <property name="right_attach">2</property>
+			      <property name="top_attach">0</property>
+			      <property name="bottom_attach">1</property>
+			      <property name="x_options">fill</property>
+			      <property name="y_options"/>
+		            </packing>
+		          </child>
+			</object>
+		      </child>
+		    </object>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">True</property>
+		      <property name="fill">True</property>
+		    </packing>
+		  </child>
+		</object>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
 	    </object>
 	    <packing>
 	      <property name="tab_expand">False</property>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 70b06a8..a61eb2f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -40,6 +40,11 @@ src/plugins/totem-plugins-engine.c
 src/plugins/bemused/totem-bemused.c
 src/plugins/brasero-disc-recorder/totem-disc-recorder.c
 [type: gettext/ini]src/plugins/brasero-disc-recorder/brasero-disc-recorder.totem-plugin.in
+[type: gettext/ini]src/plugins/chapters/chapters.totem-plugin.in
+[type: gettext/glade]src/plugins/chapters/chapters-edit.ui
+[type: gettext/glade]src/plugins/chapters/chapters-list.ui
+src/plugins/chapters/totem-chapters.c
+src/plugins/chapters/totem-cmml-parser.c
 src/plugins/coherence_upnp/coherence_upnp.py
 [type: gettext/ini]src/plugins/coherence_upnp/coherence_upnp.totem-plugin.in
 [type: gettext/ini]src/plugins/dbus-service/dbus-service.totem-plugin.in
diff --git a/src/plugins/chapters/Makefile.am b/src/plugins/chapters/Makefile.am
new file mode 100644
index 0000000..b4f638f
--- /dev/null
+++ b/src/plugins/chapters/Makefile.am
@@ -0,0 +1,47 @@
+modules_flags = -export_dynamic -avoid-version -module
+
+plugindir = $(PLUGINDIR)/chapters
+plugin_LTLIBRARIES = libchapters.la
+
+plugin_in_files = chapters.totem-plugin.in
+
+%.totem-plugin: %.totem-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.totem-plugin.in=.totem-plugin)
+
+uidir = $(plugindir)
+ui_DATA = chapters-list.ui chapters-edit.ui
+
+common_defines = \
+	-D_REENTRANT					\
+	-DDBUS_API_SUBJECT_TO_CHANGE			\
+	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"	\
+	-DGCONF_PREFIX=\""/apps/totem"\"		\
+	-DDATADIR=\""$(datadir)"\"			\
+	-DLIBEXECDIR=\""$(libexecdir)"\"		\
+	-DBINDIR=\""$(bindir)"\"			\
+	-DTOTEM_PLUGIN_DIR=\""$(libdir)/totem/plugins"\"\
+	$(DISABLE_DEPRECATED)
+
+libchapters_la_SOURCES = totem-chapters.c totem-cmml-parser.c totem-cmml-parser.h totem-chapters-utils.c totem-chapters-utils.h totem-edit-chapter.c totem-edit-chapter.h
+libchapters_la_LDFLAGS = $(modules_flags)
+libchapters_la_LIBADD = $(CHAPTERS_LIBS)
+libchapters_la_CPPFLAGS = $(common_defines)
+
+libchapters_la_CFLAGS = 		\
+	$(DEPENDENCY_CFLAGS)		\
+	$(PEAS_CFLAGS)			\
+	$(CHAPTERS_CFLAGS)		\
+	$(WARN_CFLAGS)			\
+	$(DBUS_CFLAGS)			\
+	$(AM_CFLAGS)			\
+	-I$(top_srcdir)/		\
+	-I$(top_srcdir)/src		\
+	-I$(top_srcdir)/src/backend	\
+	-I$(top_srcdir)/src/plugins
+
+EXTRA_DIST = $(plugin_in_files) $(ui_DATA)
+
+CLEANFILES = $(plugin_DATA) $(BUILT_SOURCES)
+DISTCLEANFILES = $(plugin_DATA)
+
diff --git a/src/plugins/chapters/chapters-edit.ui b/src/plugins/chapters/chapters-edit.ui
new file mode 100644
index 0000000..efa4a8f
--- /dev/null
+++ b/src/plugins/chapters/chapters-edit.ui
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkVBox" id="main_vbox">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkLabel" id="title_label">
+        <property name="visible">True</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">Enter new name for a chapter:</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkEntry" id="title_entry">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="invisible_char">&#x25CF;</property>
+        <property name="activates_default">True</property>
+        <signal name="changed" handler="title_entry_changed_cb"/>
+      </object>
+      <packing>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/src/plugins/chapters/chapters-list.ui b/src/plugins/chapters/chapters-list.ui
new file mode 100644
index 0000000..ca27404
--- /dev/null
+++ b/src/plugins/chapters/chapters-list.ui
@@ -0,0 +1,237 @@
+<?xml version="1.0"?>
+<interface>
+  <object class="GtkUIManager" id="totem-chapters-ui-manager">
+     <child>
+        <object class="GtkActionGroup" id="chapters-action-group">
+           <child>
+              <object class="GtkAction" id="remove">
+                 <property name="label" translatable="yes">_Remove</property>
+                 <property name="tooltip" translatable="yes">Remove chapter from the list</property>
+                 <property name="stock-id">gtk-remove</property>
+                 <signal name="activate" handler="popup_remove_action_cb"/>
+              </object>
+           </child>
+           <child>
+              <object class="GtkAction" id="goto">
+                 <property name="label" translatable="yes">_Go to</property>
+                 <property name="tooltip" translatable="yes">Go to chapter</property>
+                 <property name="stock-id">gtk-jump-to</property>
+                 <signal name="activate" handler="popup_goto_action_cb"/>
+              </object>
+           </child>
+        </object>
+     </child>
+     <ui>
+        <popup name="totem-chapters-popup">
+           <menuitem name="remove" action="remove"/>
+           <menuitem name="goto" action="goto"/>
+        </popup>
+     </ui>
+  </object>
+
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkTreeStore" id="clip_tree_store">
+    <columns>
+      <!-- column-name gdkpixbuf -->
+      <column type="GdkPixbuf"/>
+      <!-- column-name gchararray -->
+      <column type="gchararray"/>
+      <!-- column-name gchararray1 -->
+      <column type="gchararray"/>
+      <!-- column-name gchararray2 -->
+      <column type="gchararray"/>
+      <!-- column-name gint1 -->
+      <column type="gint64"/>
+    </columns>
+  </object>
+  <object class="GtkVBox" id="main_vbox">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkScrolledWindow" id="scrolledwindow1">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hscrollbar_policy">automatic</property>
+        <property name="vscrollbar_policy">automatic</property>
+        <child>
+          <object class="GtkTreeView" id="chapters_tree_view">
+            <property name="visible">True</property>
+            <property name="sensitive">True</property>
+            <property name="can_focus">True</property>
+            <property name="model">clip_tree_store</property>
+            <property name="headers_visible">False</property>
+            <property name="show_expanders">False</property>
+            <property name="tooltip_column">2</property>
+            <signal name="button_press_event" handler="tree_view_button_press_cb"/>
+            <signal name="key_press_event" handler="tree_view_key_press_cb"/>
+            <signal name="row_activated" handler="tree_view_row_activated_cb"/>
+            <signal name="popup_menu" handler="tree_view_popup_menu_cb"/>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="hbox1">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <property name="homogeneous">True</property>
+        <child>
+          <object class="GtkButton" id="add_button">
+            <property name="visible">True</property>
+            <property name="sensitive">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Add...</property>
+            <property name="relief">none</property>
+            <signal name="clicked" handler="add_button_clicked_cb"/>
+            <child>
+              <object class="GtkImage" id="image1">
+                <property name="visible">True</property>
+                <property name="stock">gtk-add</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="remove_button">
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Remove</property>
+            <property name="relief">none</property>
+            <signal name="clicked" handler="remove_button_clicked_cb"/>
+            <child>
+              <object class="GtkImage" id="image2">
+                <property name="visible">True</property>
+                <property name="stock">gtk-remove</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="goto_button">
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Go to chapter</property>
+            <property name="relief">none</property>
+            <signal name="clicked" handler="goto_button_clicked_cb"/>
+            <child>
+              <object class="GtkImage" id="image3">
+                <property name="visible">True</property>
+                <property name="stock">gtk-jump-to</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="save_button">
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Save Changes</property>
+            <property name="relief">none</property>
+            <signal name="clicked" handler="save_button_clicked_cb"/>
+            <child>
+              <object class="GtkImage" id="image4">
+                <property name="visible">True</property>
+                <property name="stock">gtk-save</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+
+  <object class="GtkVBox" id="load_vbox">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">6</property>
+    <property name="homogeneous">True</property>
+    <child>
+      <object class="GtkAlignment" id="alignment1">
+        <property name="visible">True</property>
+        <property name="yalign">1</property>
+        <property name="yscale">0</property>
+        <child>
+          <object class="GtkLabel" id="chapters_label">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">No chapters data</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkAlignment" id="alignment2">
+        <property name="visible">True</property>
+        <property name="yalign">0</property>
+        <property name="xscale">0</property>
+        <property name="yscale">0</property>
+        <child>
+          <object class="GtkHBox" id="hbox2">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkButton" id="load_button">
+                <property name="label" translatable="yes">Load chapters...</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">Load chapters from external file</property>
+                <signal name="clicked" handler="load_button_clicked_cb"/>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="continue_button">
+                <property name="label" translatable="yes">Continue without</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">Continue to watch movie without loaded chapters</property>
+                <signal name="clicked" handler="continue_button_clicked_cb"/>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+
+</interface>
diff --git a/src/plugins/chapters/chapters.totem-plugin.in b/src/plugins/chapters/chapters.totem-plugin.in
new file mode 100644
index 0000000..154d7c3
--- /dev/null
+++ b/src/plugins/chapters/chapters.totem-plugin.in
@@ -0,0 +1,9 @@
+[Totem Plugin]
+Module=chapters
+IAge=1
+Builtin=true
+_Name=Chapters
+_Description=Chapters support
+Authors=Alexander Saprykin
+Copyright=Copyright © 2010 Alexander Saprykin
+Website=http://www.gnome.org/projects/totem/
diff --git a/src/plugins/chapters/totem-chapters-utils.c b/src/plugins/chapters/totem-chapters-utils.c
new file mode 100644
index 0000000..c3aa497
--- /dev/null
+++ b/src/plugins/chapters/totem-chapters-utils.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 Alexander Saprykin <xelfium gmail com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ */
+
+/**
+ * SECTION:totem-chapters-utils
+ * @short_description: misc helper functions
+ * @stability: Unstable
+ * @include: totem-chapters-utils.h
+ *
+ * These functions are used for misc operations within chapters plugin.
+ **/
+
+#include <glib.h>
+
+#include "totem-chapters-utils.h"
+#include <string.h>
+
+/**
+ * totem_remove_file_extension:
+ * @filename: filename to remove extension from
+ *
+ * Removes extension from the @filename.
+ *
+ * Returns: filename without extension on success, %NULL otherwise; free with g_free ().
+ **/
+gchar *
+totem_remove_file_extension (const gchar *filename)
+{
+	gchar	*p, *s;
+
+	g_return_val_if_fail (filename != NULL, NULL);
+	g_return_val_if_fail (strlen (filename) > 0, NULL);
+
+	p = g_strrstr (filename, ".");
+	if (G_UNLIKELY (p == NULL))
+		return NULL;
+
+	s = g_strrstr (p, G_DIR_SEPARATOR_S);
+	if (G_UNLIKELY (s != NULL))
+		return NULL;
+
+	return g_strndup (filename, ABS(p - filename));
+}
+
+/**
+ * totem_change_file_extension:
+ * @filename: filename to change extension in
+ * @ext: new extension for @filename
+ *
+ * Changes extension in @filename to @ext.
+ *
+ * Returns: filename with new extension on success, %NULL otherwise; free with g_free ().
+ **/
+gchar *
+totem_change_file_extension (const gchar	*filename,
+			     const gchar	*ext)
+{
+	gchar	*no_ext, *new_file;
+
+	g_return_val_if_fail (filename != NULL, NULL);
+	g_return_val_if_fail (strlen (filename) > 0, NULL);
+	g_return_val_if_fail (ext != NULL, NULL);
+	g_return_val_if_fail (strlen (ext) > 0, NULL);
+
+	no_ext = totem_remove_file_extension (filename);
+
+	if (no_ext == NULL)
+		return NULL;
+
+	new_file = g_strconcat (no_ext, ".", ext, NULL);
+	g_free (no_ext);
+
+	return new_file;
+}
diff --git a/src/plugins/chapters/totem-chapters-utils.h b/src/plugins/chapters/totem-chapters-utils.h
new file mode 100644
index 0000000..d10ffaa
--- /dev/null
+++ b/src/plugins/chapters/totem-chapters-utils.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Alexander Saprykin <xelfium gmail com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ */
+
+#ifndef TOTEM_CHAPTERS_UTILS_H_
+#define TOTEM_CHAPTERS_UTILS_H_
+
+#include <glib.h>
+
+gchar * totem_remove_file_extension (const gchar *filename);
+gchar * totem_change_file_extension (const gchar *filename, const gchar *ext);
+
+#endif /* TOTEM_CHAPTERS_UTILS_H_ */
diff --git a/src/plugins/chapters/totem-chapters.c b/src/plugins/chapters/totem-chapters.c
new file mode 100644
index 0000000..6ea0173
--- /dev/null
+++ b/src/plugins/chapters/totem-chapters.c
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2010 Alexander Saprykin <xelfium gmail com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gprintf.h>
+#include <gio/gio.h>
+#include <gmodule.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+#include <gdk/gdkkeysyms.h>
+#include <gconf/gconf-client.h>
+#include <unistd.h>
+#include <math.h>
+
+#include "bacon-video-widget.h"
+#include "totem-plugin.h"
+#include "totem-interface.h"
+#include "totem.h"
+#include "totem-cmml-parser.h"
+#include "totem-chapters-utils.h"
+#include "totem-edit-chapter.h"
+
+#define TOTEM_TYPE_CHAPTERS_PLUGIN		(totem_chapters_plugin_get_type ())
+#define TOTEM_CHAPTERS_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPlugin))
+#define TOTEM_CHAPTERS_PLUGIN_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPluginClass))
+#define TOTEM_IS_CHAPTERS_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_CHAPTERS_PLUGIN))
+#define TOTEM_IS_CHAPTERS_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_CHAPTERS_PLUGIN))
+#define TOTEM_CHAPTERS_PLUGIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPluginClass))
+
+#define TOTEM_CHAPTERS_PLUGIN_GET_PRIVATE(obj) 	(G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPluginPrivate))
+
+#define CHAPTER_TOOLTIP(title, start) g_strdup_printf	( _("<b>Title: </b>%s\n<b>Start time: </b>%s"),	\
+							 ( title ), ( start ) )
+
+#define CHAPTER_TITLE(title, start) g_strdup_printf	("<big>%s</big>\n"				\
+							 "<small><span foreground='grey'>%s"		\
+							 "</span></small>",				\
+							 ( title ), ( start ) )
+#define ICON_SCALE_RATIO 2
+
+typedef struct {
+	GtkWidget	*tree;
+	GtkWidget	*add_button,
+			*remove_button,
+			*save_button,
+			*load_button,
+			*goto_button,
+			*continue_button,
+			*list_box,
+			*load_box;
+	GtkActionGroup	*action_group;
+	GtkUIManager	*ui_manager;
+	gboolean	was_played;
+	GdkPixbuf	*last_frame;
+	gint64		last_time;
+	gchar		*cmml_mrl;
+	gboolean	autoload;
+	GCancellable	*cancellable[2];
+	GConfClient	*gconf;
+	guint		autoload_handle_id;
+} TotemChaptersPluginPrivate;
+
+typedef struct {
+	PeasExtensionBase		parent;
+	TotemObject			*totem;
+	TotemEditChapter		*edit_chapter;
+	TotemChaptersPluginPrivate	*priv;
+} TotemChaptersPlugin;
+
+typedef struct {
+	PeasExtensionBaseClass		parent_class;
+} TotemChaptersPluginClass;
+
+enum {
+	CHAPTERS_PIXBUF_COLUMN = 0,
+	CHAPTERS_TITLE_COLUMN,
+	CHAPTERS_TOOLTIP_COLUMN,
+	CHAPTERS_TITLE_PRIV_COLUMN,
+	CHAPTERS_TIME_PRIV_COLUMN,
+	CHAPTERS_N_COLUMNS
+};
+
+G_MODULE_EXPORT GType register_totem_plugin (GTypeModule *module);
+GType totem_chapters_plugin_get_type (void) G_GNUC_CONST;
+static void totem_chapters_plugin_finalize (GObject *object);
+static void totem_file_opened_async_cb (TotemObject *totem, const gchar *uri, TotemChaptersPlugin *plugin);
+static void totem_file_opened_result_cb (gpointer data, gpointer user_data);
+static void totem_file_closed_cb (TotemObject *totem, TotemChaptersPlugin *plugin);
+static void add_chapter_to_the_list (gpointer data, gpointer user_data);
+static void add_chapter_to_the_list_new (TotemChaptersPlugin *plugin, const gchar *title, gint64 time);
+static gboolean check_available_time (TotemChaptersPlugin *plugin, gint64 time);
+static GdkPixbuf * get_chapter_pixbuf (GdkPixbuf *src);
+static void chapter_edit_dialog_response_cb (GtkDialog *dialog, gint response, TotemChaptersPlugin *plugin);
+static void prepare_chapter_edit (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path, gpointer user_data);
+static void finish_chapter_edit (GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data);
+static void chapter_selection_changed_cb (GtkTreeSelection *tree_selection, TotemChaptersPlugin *plugin);
+static void show_chapter_edit_dialog (TotemChaptersPlugin *plugin);
+static void save_chapters_result_cb (gpointer data, gpointer user_data);
+static GList * get_chapters_list (TotemChaptersPlugin *plugin);
+static gboolean show_popup_menu (TotemChaptersPlugin *plugin, GdkEventButton *event);
+static void gconf_autoload_changed_cb (GConfClient *gconf, guint cnx_id, GConfEntry *entry, TotemChaptersPlugin	*plugin);
+static void load_chapters_from_file (const gchar *uri, gboolean from_dialog, TotemChaptersPlugin *plugin);
+static void set_no_data_visible (gboolean visible, gboolean show_buttons, TotemChaptersPlugin *plugin);
+
+/* GtkBuilder callbacks */
+void add_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
+void remove_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
+void save_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
+void goto_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
+void tree_view_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, TotemChaptersPlugin *plugin);
+gboolean tree_view_button_press_cb (GtkTreeView *tree_view, GdkEventButton *event, TotemChaptersPlugin *plugin);
+gboolean tree_view_key_press_cb (GtkTreeView *tree_view, GdkEventKey *event, TotemChaptersPlugin *plugin);
+gboolean tree_view_popup_menu_cb (GtkTreeView *tree_view, TotemChaptersPlugin *plugin);
+void popup_remove_action_cb (GtkAction *action, TotemChaptersPlugin *plugin);
+void popup_goto_action_cb (GtkAction *action, TotemChaptersPlugin *plugin);
+void load_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
+void continue_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
+
+TOTEM_PLUGIN_REGISTER (TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPlugin, totem_chapters_plugin)
+
+static void
+totem_chapters_plugin_class_init (TotemChaptersPluginClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (TotemChaptersPluginPrivate));
+
+	object_class->finalize = totem_chapters_plugin_finalize;
+}
+
+static void
+totem_chapters_plugin_init (TotemChaptersPlugin *plugin)
+{
+	plugin->priv = TOTEM_CHAPTERS_PLUGIN_GET_PRIVATE (plugin);
+}
+
+static void
+totem_chapters_plugin_finalize (GObject *object)
+{
+	G_OBJECT_CLASS (totem_chapters_plugin_parent_class)->finalize (object);
+}
+
+static GdkPixbuf *
+get_chapter_pixbuf (GdkPixbuf *src)
+{
+	GdkPixbuf	*pixbuf;
+	gint		width, height;
+	gfloat		pix_width, pix_height;
+	gfloat		ratio, new_width;
+
+	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &width, &height);
+	height *= ICON_SCALE_RATIO;
+
+	if (src != NULL) {
+		pix_width = (gfloat) gdk_pixbuf_get_width (src);
+		pix_height = (gfloat) gdk_pixbuf_get_height (src);
+
+		/* calc height ratio and apply it to width */
+		ratio = pix_height / height;
+		new_width = pix_width / ratio;
+		width = ceil (new_width);
+
+		/* scale video frame if need */
+		pixbuf = gdk_pixbuf_scale_simple (src, width, height, GDK_INTERP_BILINEAR);
+	} else {
+		/* 16:10 aspect ratio by default */
+		new_width = (gfloat) height * 1.6;
+		width = ceil (new_width);
+
+		pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height);
+		gdk_pixbuf_fill (pixbuf, 0x00000000);
+	}
+
+	return pixbuf;
+}
+
+static void
+add_chapter_to_the_list (gpointer	data,
+			 gpointer	user_data)
+{
+	TotemChaptersPlugin	*plugin;
+	GdkPixbuf		*pixbuf;
+	GtkTreeIter		iter;
+	GtkWidget		*tree;
+	GtkTreeStore		*store;
+	TotemCmmlClip		*clip;
+	gchar			*text, *start, *tip;
+
+	g_return_if_fail (data != NULL);
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (user_data));
+
+	plugin = TOTEM_CHAPTERS_PLUGIN (user_data);
+	tree = plugin->priv->tree;
+	store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree)));
+	clip = ((TotemCmmlClip *) data);
+
+	/* prepare tooltip data */
+	start = totem_cmml_convert_msecs_to_str (clip->time_start);
+	tip = CHAPTER_TOOLTIP (clip->title, start);
+
+	/* append clip to the sidebar list */
+	gtk_tree_store_append (store, &iter, NULL);
+	text = CHAPTER_TITLE (clip->title, start);
+
+	if (G_LIKELY (clip->pixbuf != NULL))
+		pixbuf = g_object_ref (clip->pixbuf);
+	else
+		pixbuf = get_chapter_pixbuf (NULL);
+
+	gtk_tree_store_set (store, &iter,
+			    CHAPTERS_TITLE_COLUMN, text,
+			    CHAPTERS_TOOLTIP_COLUMN, tip,
+			    CHAPTERS_PIXBUF_COLUMN, pixbuf,
+			    CHAPTERS_TITLE_PRIV_COLUMN, clip->title,
+			    CHAPTERS_TIME_PRIV_COLUMN, clip->time_start,
+			    -1);
+
+	g_object_unref (pixbuf);
+	g_free (text);
+	g_free (start);
+	g_free (tip);
+}
+
+static void
+add_chapter_to_the_list_new (TotemChaptersPlugin	*plugin,
+			     const gchar		*title,
+			     gint64			time)
+{
+	GdkPixbuf		*pixbuf;
+	GtkTreeIter		iter, cur_iter, res_iter;
+	GtkTreeModel		*store;
+	gchar 			*text, *start, *tip;
+	gboolean		valid;
+	gint64			cur_time, prev_time = 0;
+	gint			iter_count = 0;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+	g_return_if_fail (title != NULL);
+	g_return_if_fail (time >= 0);
+
+	store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
+	valid = gtk_tree_model_get_iter_first (store, &cur_iter);
+
+	while (valid) {
+		gtk_tree_model_get (store, &cur_iter,
+				    CHAPTERS_TIME_PRIV_COLUMN, &cur_time,
+				    -1);
+
+		if (time < cur_time && time > prev_time)
+			break;
+
+		iter_count += 1;
+		res_iter = cur_iter;
+		prev_time = cur_time;
+
+		valid = gtk_tree_model_iter_next (store, &cur_iter);
+	}
+
+	/* prepare tooltip data */
+	start = totem_cmml_convert_msecs_to_str (time);
+	tip = CHAPTER_TOOLTIP (title, start);
+
+	/* insert clip into the sidebar list at proper position */
+	if (iter_count > 0)
+		gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter, NULL, &res_iter);
+	else
+		gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter, NULL, NULL);
+
+	text = CHAPTER_TITLE (title, start);
+	pixbuf = get_chapter_pixbuf (plugin->priv->last_frame);
+
+	gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
+			    CHAPTERS_TITLE_COLUMN, text,
+			    CHAPTERS_TOOLTIP_COLUMN, tip,
+			    CHAPTERS_PIXBUF_COLUMN, pixbuf,
+			    CHAPTERS_TITLE_PRIV_COLUMN, title,
+			    CHAPTERS_TIME_PRIV_COLUMN, time,
+			    -1);
+
+	g_object_unref (pixbuf);
+	g_free (text);
+	g_free (start);
+	g_free (tip);
+}
+
+static gboolean
+check_available_time (TotemChaptersPlugin	*plugin,
+		      gint64			time)
+{
+	GtkTreeModel	*store;
+	GtkTreeIter	iter;
+	gboolean	valid;
+	gint64		cur_time;
+
+	g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
+
+	store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
+
+	valid = gtk_tree_model_get_iter_first (store, &iter);
+	while (valid) {
+		gtk_tree_model_get (store, &iter,
+				    CHAPTERS_TIME_PRIV_COLUMN, &cur_time,
+				    -1);
+
+		if (cur_time == time)
+			return FALSE;
+
+		if (cur_time > time)
+			return TRUE;
+
+		valid = gtk_tree_model_iter_next (store, &iter);
+	}
+
+	return TRUE;
+}
+
+static void
+totem_file_opened_result_cb (gpointer	data,
+			     gpointer	user_data)
+{
+	TotemCmmlAsyncData	*adata;
+	TotemChaptersPlugin	*plugin;
+
+	g_return_if_fail (data != NULL);
+
+	adata = (TotemCmmlAsyncData *) data;
+	plugin = TOTEM_CHAPTERS_PLUGIN (adata->user_data);
+
+	if (G_UNLIKELY (!adata->successful)) {
+		if (G_UNLIKELY (g_cancellable_is_cancelled (adata->cancellable))) {
+			/* if operation was cancelled due to plugin deactivation only clean up */
+			g_object_unref (adata->cancellable);
+			g_free (adata->file);
+			g_free (adata->error);
+			g_list_foreach (adata->list, (GFunc) totem_cmml_clip_free, NULL);
+			g_list_free (adata->list);
+			g_free (adata);
+			return;
+		} else
+			totem_action_error (plugin->totem, _("Error while reading file with chapters"),
+					    adata->error);
+	}
+
+	if (adata->is_exists && adata->from_dialog) {
+		g_free (plugin->priv->cmml_mrl);
+		plugin->priv->cmml_mrl = g_strdup (adata->file);
+	}
+
+	g_list_foreach (adata->list, (GFunc) add_chapter_to_the_list, plugin);
+	g_list_foreach (adata->list, (GFunc) totem_cmml_clip_free, NULL);
+	g_list_free (adata->list);
+
+	/* do not show tree if read operation failed */
+	set_no_data_visible (!adata->successful || !adata->is_exists, TRUE, plugin);
+
+	g_object_unref (adata->cancellable);
+	g_free (adata->file);
+	g_free (adata->error);
+	g_free (adata);
+}
+
+static void
+totem_file_opened_async_cb (TotemObject 		*totem,
+			    const gchar 		*uri,
+			    TotemChaptersPlugin		*plugin)
+{
+	gchar	*cmml_file;
+
+	g_return_if_fail (TOTEM_IS_OBJECT (totem));
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+	g_return_if_fail (uri != NULL);
+
+	cmml_file = totem_change_file_extension (uri, "cmml");
+	/* if file has no extension - append it */
+	if (cmml_file == NULL)
+		cmml_file = g_strconcat (uri, ".cmml", NULL);
+
+	plugin->priv->cmml_mrl = cmml_file;
+
+	if (!plugin->priv->autoload)
+		set_no_data_visible (TRUE, TRUE, plugin);
+	else
+		load_chapters_from_file (cmml_file, FALSE, plugin);
+}
+
+static void
+totem_file_closed_cb (TotemObject		*totem,
+		      TotemChaptersPlugin	*plugin)
+{
+	GtkTreeStore	*store;
+
+	g_return_if_fail (TOTEM_IS_OBJECT (totem) && TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree)));
+
+	gtk_tree_store_clear (store);
+
+	if (G_UNLIKELY (plugin->edit_chapter != NULL))
+		gtk_widget_destroy (GTK_WIDGET (plugin->edit_chapter));
+
+	if (G_UNLIKELY (plugin->priv->last_frame != NULL))
+		g_object_unref (G_OBJECT (plugin->priv->last_frame));
+
+	g_free (plugin->priv->cmml_mrl);
+	plugin->priv->cmml_mrl = NULL;
+
+	gtk_widget_set_sensitive (plugin->priv->remove_button, FALSE);
+	gtk_widget_set_sensitive (plugin->priv->save_button, FALSE);
+
+	set_no_data_visible (TRUE, FALSE, plugin);
+}
+
+static void
+chapter_edit_dialog_response_cb (GtkDialog		*dialog,
+				 gint			response,
+				 TotemChaptersPlugin	*plugin)
+{
+	gchar		*title;
+
+	g_return_if_fail (TOTEM_IS_EDIT_CHAPTER (dialog));
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	if (response != GTK_RESPONSE_OK) {
+		gtk_widget_destroy (GTK_WIDGET (plugin->edit_chapter));
+
+		if (plugin->priv->last_frame != NULL)
+			g_object_unref (G_OBJECT (plugin->priv->last_frame));
+
+		if (plugin->priv->was_played)
+			totem_action_play (plugin->totem);
+		return;
+	}
+
+	gtk_widget_hide (GTK_WIDGET (dialog));
+
+	title = totem_edit_chapter_get_title (TOTEM_EDIT_CHAPTER (dialog));
+	add_chapter_to_the_list_new (plugin, title, plugin->priv->last_time);
+
+	gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
+
+	if (G_LIKELY (plugin->priv->last_frame != NULL))
+		g_object_unref (G_OBJECT (plugin->priv->last_frame));
+
+	g_free (title);
+	gtk_widget_destroy (GTK_WIDGET (plugin->edit_chapter));
+
+	if (plugin->priv->was_played)
+		totem_action_play (plugin->totem);
+}
+
+static void
+prepare_chapter_edit (GtkCellRenderer	*renderer,
+		      GtkCellEditable	*editable,
+		      gchar		*path,
+		      gpointer		user_data)
+{
+	TotemChaptersPlugin	*plugin;
+	GtkTreeModel		*store;
+	GtkTreeIter		iter;
+	gchar			*title;
+	GtkEntry		*entry;
+
+	g_return_if_fail (GTK_IS_ENTRY (editable));
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (user_data));
+	g_return_if_fail (path != NULL);
+
+	plugin = TOTEM_CHAPTERS_PLUGIN (user_data);
+	entry = GTK_ENTRY (editable);
+	store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
+
+	if (G_UNLIKELY (!gtk_tree_model_get_iter_from_string (store, &iter, path)))
+		return;
+
+	gtk_tree_model_get (store, &iter, CHAPTERS_TITLE_PRIV_COLUMN, &title, -1);
+	gtk_entry_set_text (entry, title);
+
+	g_free (title);
+	return;
+}
+
+static void
+finish_chapter_edit (GtkCellRendererText	*renderer,
+		     gchar			*path,
+		     gchar			*new_text,
+		     gpointer			user_data)
+{
+	TotemChaptersPlugin	*plugin;
+	GtkTreeModel		*store;
+	GtkTreeIter		iter;
+	gchar			*time_str, *tip, *new_title, *old_title;
+	gint64			time;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (user_data));
+	g_return_if_fail (new_text != NULL);
+	g_return_if_fail (path != NULL);
+
+	plugin = TOTEM_CHAPTERS_PLUGIN (user_data);
+	store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
+
+	if (G_UNLIKELY (!gtk_tree_model_get_iter_from_string (store, &iter, path)))
+		return;
+
+	gtk_tree_model_get (store, &iter,
+			    CHAPTERS_TIME_PRIV_COLUMN, &time,
+			    CHAPTERS_TITLE_PRIV_COLUMN, &old_title,
+			    -1);
+
+	if (g_strcmp0 (old_title, new_text) == 0) {
+		g_free (old_title);
+		return;
+	}
+
+	time_str = totem_cmml_convert_msecs_to_str (time);
+	new_title = CHAPTER_TITLE (new_text, time_str);
+	tip = CHAPTER_TOOLTIP (new_text, time_str);
+
+	gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
+			    CHAPTERS_TITLE_COLUMN, new_title,
+			    CHAPTERS_TOOLTIP_COLUMN, tip,
+			    CHAPTERS_TITLE_PRIV_COLUMN, new_text,
+			    -1);
+
+	gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
+
+	g_free (old_title);
+	g_free (new_title);
+	g_free (tip);
+	g_free (time_str);
+}
+
+static void
+show_chapter_edit_dialog (TotemChaptersPlugin	*plugin)
+{
+	GtkWindow		*main_window;
+	BaconVideoWidget	*bvw;
+	gint64			time;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	if (G_UNLIKELY (plugin->edit_chapter != NULL)) {
+		gtk_window_present (GTK_WINDOW (plugin->edit_chapter));
+		return;
+	}
+
+	main_window = totem_get_main_window (plugin->totem);
+	plugin->priv->was_played = totem_is_playing (plugin->totem);
+	totem_action_pause (plugin->totem);
+
+	/* adding a new one, check if it's time available */
+	g_object_get (G_OBJECT (plugin->totem), "current-time", &time, NULL);
+	if (G_UNLIKELY (!check_available_time (plugin, time))) {
+		totem_interface_error_blocking (_("Chapter with the same time already exists"),
+						_("Try another name or remove an existing chapter"),
+						main_window);
+		g_object_unref (main_window);
+		if (plugin->priv->was_played)
+			totem_action_play (plugin->totem);
+		return;
+	}
+	plugin->priv->last_time = time;
+
+	/* capture frame */
+	bvw = BACON_VIDEO_WIDGET (totem_get_video_widget (plugin->totem));
+	plugin->priv->last_frame = bacon_video_widget_get_current_frame (bvw);
+	g_object_add_weak_pointer (G_OBJECT (plugin->priv->last_frame), (gpointer *) &plugin->priv->last_frame);
+	g_object_unref (bvw);
+
+	/* create chapter-edit dialog */
+	plugin->edit_chapter = TOTEM_EDIT_CHAPTER (totem_edit_chapter_new ());
+	g_object_add_weak_pointer (G_OBJECT (plugin->edit_chapter), (gpointer *) &(plugin->edit_chapter));
+
+	g_signal_connect (G_OBJECT (plugin->edit_chapter), "delete-event",
+			  G_CALLBACK (gtk_widget_destroy), NULL);
+	g_signal_connect (G_OBJECT (plugin->edit_chapter), "response",
+			  G_CALLBACK (chapter_edit_dialog_response_cb), plugin);
+
+	gtk_window_set_transient_for (GTK_WINDOW (plugin->edit_chapter),
+				      main_window);
+	gtk_widget_show (GTK_WIDGET (plugin->edit_chapter));
+
+	g_object_unref (main_window);
+}
+
+static void
+chapter_selection_changed_cb (GtkTreeSelection		*tree_selection,
+			      TotemChaptersPlugin	*plugin)
+{
+	gint		count;
+	gboolean	allow_remove, allow_goto;
+
+	g_return_if_fail (GTK_IS_TREE_SELECTION (tree_selection));
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	count = gtk_tree_selection_count_selected_rows (tree_selection);
+	allow_remove = (count > 0);
+	allow_goto = (count == 1);
+
+	gtk_widget_set_sensitive (plugin->priv->remove_button, allow_remove);
+	gtk_widget_set_sensitive (plugin->priv->goto_button, allow_goto);
+}
+
+static void
+gconf_autoload_changed_cb (GConfClient		*gconf,
+			   guint		cnx_id,
+			   GConfEntry		*entry,
+			   TotemChaptersPlugin	*plugin)
+{
+	g_return_if_fail (GCONF_IS_CLIENT (gconf));
+	g_return_if_fail (entry != NULL);
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	if (G_LIKELY (entry->value != NULL))
+		plugin->priv->autoload = gconf_value_get_bool (entry->value);
+	else
+		plugin->priv->autoload = TRUE;
+}
+
+static void
+load_chapters_from_file (const gchar		*uri,
+			 gboolean		from_dialog,
+			 TotemChaptersPlugin	*plugin)
+{
+	TotemCmmlAsyncData	*data;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	if (plugin->priv->cancellable[0] != NULL) {
+		g_cancellable_cancel (plugin->priv->cancellable[0]);
+		g_object_unref (plugin->priv->cancellable[0]);
+	}
+
+	data = g_new0 (TotemCmmlAsyncData, 1);
+	/* do not forget to save this pointer in the result function */
+	data->file = g_strdup (uri);
+	data->final = totem_file_opened_result_cb;
+	data->user_data = (gpointer) plugin;
+	if (from_dialog)
+		data->from_dialog = TRUE;
+	/* cancellable object shouldn't be finalized during result func */
+	plugin->priv->cancellable[0] = g_cancellable_new ();
+	g_object_add_weak_pointer (G_OBJECT (plugin->priv->cancellable[0]),
+				   (gpointer *) &(plugin->priv->cancellable[0]));
+	data->cancellable = plugin->priv->cancellable[0];
+
+	if (G_UNLIKELY (totem_cmml_read_file_async (data) < 0)) {
+		g_warning ("chapters: wrong parameters for reading CMML file, may be a bug");
+
+		set_no_data_visible (TRUE, TRUE, plugin);
+
+		g_object_unref (plugin->priv->cancellable[0]);
+		g_free (data);
+	}
+}
+
+static void
+set_no_data_visible (gboolean			visible,
+		     gboolean			show_buttons,
+		     TotemChaptersPlugin	*plugin)
+{
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	if (visible) {
+		gtk_widget_hide (plugin->priv->list_box);
+		gtk_widget_show (plugin->priv->load_box);
+	} else {
+		gtk_widget_hide (plugin->priv->load_box);
+		gtk_widget_show (plugin->priv->list_box);
+	}
+
+	gtk_widget_set_sensitive (plugin->priv->add_button, !visible);
+	gtk_widget_set_sensitive (plugin->priv->tree, !visible);
+
+	gtk_widget_set_visible (plugin->priv->load_button, show_buttons);
+	gtk_widget_set_visible (plugin->priv->continue_button, show_buttons);
+}
+
+void
+add_button_clicked_cb (GtkButton		*button,
+		       TotemChaptersPlugin	*plugin)
+{
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	show_chapter_edit_dialog (plugin);
+}
+
+void
+remove_button_clicked_cb (GtkButton		*button,
+			  TotemChaptersPlugin	*plugin)
+{
+	GtkTreeSelection	*selection;
+	GtkTreeIter		iter;
+	GtkTreeModel		*store;
+	GList			*list;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (plugin->priv->tree));
+	list = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+	g_return_if_fail (g_list_length (list) != 0);
+
+	list = g_list_last (list);
+	while (list != NULL) {
+		gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, (GtkTreePath *) list->data);
+		gtk_tree_store_remove (GTK_TREE_STORE (store), &iter);
+
+		list = list->prev;
+	}
+
+	gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
+
+	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (list);
+}
+
+static void
+save_chapters_result_cb (gpointer	data,
+			 gpointer	user_data)
+{
+	TotemCmmlAsyncData	*adata;
+	TotemChaptersPlugin	*plugin;
+
+	g_return_if_fail (data != NULL);
+
+	adata = (TotemCmmlAsyncData *) data;
+	plugin = TOTEM_CHAPTERS_PLUGIN (adata->user_data);
+
+	if (G_UNLIKELY (!adata->successful && !g_cancellable_is_cancelled (adata->cancellable))) {
+		totem_action_error (plugin->totem, _("Error while writing file with chapters"),
+				    adata->error);
+		gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
+	}
+
+	g_object_unref (adata->cancellable);
+	g_list_foreach (adata->list, (GFunc) totem_cmml_clip_free, NULL);
+	g_list_free (adata->list);
+	g_free (adata->error);
+	g_free (adata);
+}
+
+static GList *
+get_chapters_list (TotemChaptersPlugin	*plugin)
+{
+	GList		*list = NULL;
+	TotemCmmlClip	*clip;
+	GtkTreeModel	*store;
+	GtkTreeIter	iter;
+	gchar		*title;
+	gint64		time;
+	GdkPixbuf	*pixbuf;
+	gboolean	valid;
+
+	g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), NULL);
+
+	store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
+
+	valid = gtk_tree_model_get_iter_first (store, &iter);
+	while (valid) {
+		gtk_tree_model_get (store, &iter,
+				    CHAPTERS_TITLE_PRIV_COLUMN, &title,
+				    CHAPTERS_TIME_PRIV_COLUMN, &time,
+				    CHAPTERS_PIXBUF_COLUMN, &pixbuf,
+				    -1);
+		clip = totem_cmml_clip_new (title, NULL, time, pixbuf);
+		list = g_list_prepend (list, clip);
+
+		g_free (title);
+		g_object_unref (pixbuf);
+
+		valid = gtk_tree_model_iter_next (store, &iter);
+	}
+	list = g_list_reverse (list);
+
+	return list;
+}
+
+static gboolean
+show_popup_menu (TotemChaptersPlugin	*plugin,
+		 GdkEventButton		*event)
+{
+	guint			button = 0;
+	guint32			_time;
+	GtkTreePath		*path;
+	gint			count;
+	GtkWidget		*menu;
+	GtkAction		*remove_act, *goto_act;
+	GtkTreeSelection	*selection;
+
+	g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (plugin->priv->tree));
+
+	if (event != NULL) {
+		button = event->button;
+		_time = event->time;
+
+		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (plugin->priv->tree),
+						   event->x, event->y, &path, NULL, NULL, NULL)) {
+			if (!gtk_tree_selection_path_is_selected (selection, path)) {
+				gtk_tree_selection_unselect_all (selection);
+				gtk_tree_selection_select_path (selection, path);
+			}
+			gtk_tree_path_free (path);
+		} else
+			gtk_tree_selection_unselect_all (selection);
+	} else
+		_time = gtk_get_current_event_time ();
+
+	count = gtk_tree_selection_count_selected_rows (selection);
+
+	if (count == 0)
+		return FALSE;
+
+	remove_act = gtk_action_group_get_action (plugin->priv->action_group, "remove");
+	goto_act = gtk_action_group_get_action (plugin->priv->action_group, "goto");
+	gtk_action_set_sensitive (remove_act, count > 0);
+	gtk_action_set_sensitive (goto_act, count == 1);
+
+	menu = gtk_ui_manager_get_widget (plugin->priv->ui_manager, "/totem-chapters-popup");
+
+	gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+			button, _time);
+
+	return TRUE;
+}
+
+void
+save_button_clicked_cb (GtkButton		*button,
+			TotemChaptersPlugin	*plugin)
+{
+	TotemCmmlAsyncData	*data;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	if (plugin->priv->cancellable[1] != NULL) {
+		g_cancellable_cancel (plugin->priv->cancellable[1]);
+		g_object_unref (plugin->priv->cancellable[1]);
+	}
+
+	data = g_new0 (TotemCmmlAsyncData, 1);
+	data->file = plugin->priv->cmml_mrl;
+	data->list = get_chapters_list (plugin);
+	data->final = save_chapters_result_cb;
+	data->user_data = (gpointer) plugin;
+	/* cancellable object shouldn't be finalized during result func */
+	data->cancellable = g_cancellable_new ();
+	plugin->priv->cancellable[1] = data->cancellable;
+	g_object_add_weak_pointer (G_OBJECT (plugin->priv->cancellable[1]),
+				   (gpointer *) &(plugin->priv->cancellable[1]));
+
+	if (G_UNLIKELY (totem_cmml_write_file_async (data) < 0)) {
+		totem_action_error (plugin->totem, _("Error occurred while saving chapters"),
+				    _("Please check you rights and free space"));
+		g_free (data);
+		g_object_unref (plugin->priv->cancellable);
+	} else
+		gtk_widget_set_sensitive (plugin->priv->save_button, FALSE);
+}
+
+void
+tree_view_row_activated_cb (GtkTreeView			*tree_view,
+			    GtkTreePath			*path,
+			    GtkTreeViewColumn		*column,
+			    TotemChaptersPlugin		*plugin)
+{
+	GtkTreeModel	*store;
+	GtkTreeIter	iter;
+	gboolean	seekable;
+	gint64		time;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+	g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+	g_return_if_fail (path != NULL);
+
+	store = gtk_tree_view_get_model (tree_view);
+	seekable = totem_is_seekable (plugin->totem);
+	if (!seekable) {
+		g_warning ("chapters: unable to seek stream!");
+		return;
+	}
+
+	gtk_tree_model_get_iter (store, &iter, path);
+	gtk_tree_model_get (store, &iter, CHAPTERS_TIME_PRIV_COLUMN, &time, -1);
+
+	totem_action_seek_time (plugin->totem, time, TRUE);
+}
+
+gboolean
+tree_view_button_press_cb (GtkTreeView			*tree_view,
+			   GdkEventButton		*event,
+			   TotemChaptersPlugin		*plugin)
+{
+	g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+		return show_popup_menu (plugin, event);
+
+	return FALSE;
+}
+
+gboolean
+tree_view_key_press_cb (GtkTreeView		*tree_view,
+			GdkEventKey		*event,
+			TotemChaptersPlugin	*plugin)
+{
+	GtkTreeSelection	*selection;
+
+	g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (plugin->priv->tree));
+
+	/* Special case some shortcuts */
+	if (event->state != 0) {
+		if ((event->state & GDK_CONTROL_MASK) &&
+		    event->keyval == GDK_a) {
+			gtk_tree_selection_select_all (selection);
+			return TRUE;
+		}
+	}
+
+	/* If we have modifiers, and either Ctrl, Mod1 (Alt), or any
+	 * of Mod3 to Mod5 (Mod2 is num-lock...) are pressed, we
+	 * let Gtk+ handle the key */
+	if (event->state != 0 &&
+	    ((event->state & GDK_CONTROL_MASK)
+	    || (event->state & GDK_MOD1_MASK)
+	    || (event->state & GDK_MOD3_MASK)
+	    || (event->state & GDK_MOD4_MASK)
+	    || (event->state & GDK_MOD5_MASK)))
+		return FALSE;
+
+	if (event->keyval == GDK_Delete) {
+		if (gtk_tree_selection_count_selected_rows (selection) > 0)
+			remove_button_clicked_cb (GTK_BUTTON (plugin->priv->remove_button), plugin);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+gboolean
+tree_view_popup_menu_cb (GtkTreeView		*tree_view,
+			 TotemChaptersPlugin	*plugin)
+{
+	g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
+
+	return show_popup_menu (plugin, NULL);
+}
+
+void
+popup_remove_action_cb (GtkAction		*action,
+			TotemChaptersPlugin	*plugin)
+{
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	remove_button_clicked_cb (GTK_BUTTON (plugin->priv->remove_button), plugin);
+}
+
+void
+popup_goto_action_cb (GtkAction			*action,
+		      TotemChaptersPlugin	*plugin)
+{
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	goto_button_clicked_cb (GTK_BUTTON (plugin->priv->goto_button), plugin);
+}
+
+void
+load_button_clicked_cb (GtkButton		*button,
+			TotemChaptersPlugin	*plugin)
+{
+	GtkWindow	*main_window;
+	GtkWidget	*dialog;
+	GFile		*cur, *parent;
+	GtkFileFilter	*filter_supported, *filter_all;
+	gchar		*filename, *mrl, *dir;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	plugin->priv->was_played = totem_is_playing (plugin->totem);
+	totem_action_pause (plugin->totem);
+
+	mrl = totem_get_current_mrl (plugin->totem);
+	main_window = totem_get_main_window (plugin->totem);
+	dialog = gtk_file_chooser_dialog_new (_("Open Chapters File"), main_window, GTK_FILE_CHOOSER_ACTION_OPEN,
+					      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					      GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+					      NULL);
+	gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
+
+	cur = g_file_new_for_uri (mrl);
+	parent = g_file_get_parent (cur);
+
+	if (parent != NULL)
+		dir = g_file_get_uri (parent);
+	else
+		dir = g_strdup (G_DIR_SEPARATOR_S);
+
+	filter_supported = gtk_file_filter_new ();
+	filter_all = gtk_file_filter_new ();
+
+	gtk_file_filter_add_pattern (filter_supported, "*.cmml");
+	gtk_file_filter_add_pattern (filter_supported, "*.CMML");
+	gtk_file_filter_set_name (filter_supported, _("Supported files"));
+
+	gtk_file_filter_add_pattern (filter_all, "*");
+	gtk_file_filter_set_name (filter_all, _("All files"));
+
+	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter_supported);
+	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filter_all);
+
+	gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog), dir);
+
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+		filename = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+
+		load_chapters_from_file (filename, TRUE, plugin);
+
+		g_free (filename);
+	}
+
+	if (plugin->priv->was_played)
+		totem_action_play (plugin->totem);
+
+	gtk_widget_destroy (dialog);
+	g_object_unref (main_window);
+	g_object_unref (cur);
+	g_object_unref (parent);
+	g_free (mrl);
+	g_free (dir);
+}
+
+void
+continue_button_clicked_cb (GtkButton		*button,
+			    TotemChaptersPlugin	*plugin)
+{
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	set_no_data_visible (FALSE, FALSE, plugin);
+}
+
+void
+goto_button_clicked_cb (GtkButton		*button,
+			TotemChaptersPlugin	*plugin)
+{
+	GtkTreeView		*tree;
+	GtkTreeModel		*store;
+	GtkTreeSelection	*selection;
+	GList			*list;
+
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	tree = GTK_TREE_VIEW (plugin->priv->tree);
+	store = gtk_tree_view_get_model (tree);
+	selection = gtk_tree_view_get_selection (tree);
+
+	list = gtk_tree_selection_get_selected_rows (selection, &store);
+
+	tree_view_row_activated_cb (tree, (GtkTreePath *) list->data, NULL, plugin);
+
+	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (list);
+}
+
+static void
+impl_activate (PeasActivatable	*plugin,
+	       GObject		*object)
+{
+	TotemObject		*totem;
+	GtkWindow		*main_window;
+	GConfClient		*gconf;
+	GtkBuilder		*builder;
+	GtkWidget		*main_box;
+	GtkTreeSelection	*selection;
+	TotemChaptersPlugin	*cplugin;
+	GtkCellRenderer		*renderer;
+	GtkTreeViewColumn	*column;
+	gchar			*mrl;
+
+	g_return_if_fail (TOTEM_IS_OBJECT (object));
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	cplugin = TOTEM_CHAPTERS_PLUGIN (plugin);
+	totem = TOTEM_OBJECT (object);
+	main_window = totem_get_main_window (totem);
+
+	builder = totem_interface_load ("chapters-list.ui", TRUE,
+					main_window, cplugin);
+	g_object_unref (main_window);
+
+	if (builder == NULL)
+		return;
+
+	gconf = gconf_client_get_default ();
+	if (G_LIKELY (gconf != NULL)) {
+		cplugin->priv->autoload = gconf_client_get_bool (gconf, GCONF_PREFIX"/autoload_chapters", NULL);
+		cplugin->priv->autoload_handle_id = gconf_client_notify_add (gconf, GCONF_PREFIX"/autoload_chapters",
+									     (GConfClientNotifyFunc) gconf_autoload_changed_cb,
+									      cplugin, NULL, NULL);
+	} else
+		cplugin->priv->autoload = TRUE;
+	cplugin->priv->gconf = gconf;
+
+	cplugin->priv->tree = GTK_WIDGET (gtk_builder_get_object (builder, "chapters_tree_view"));
+	cplugin->priv->action_group = GTK_ACTION_GROUP (gtk_builder_get_object (builder, "chapters-action-group"));
+	g_object_ref (cplugin->priv->action_group);
+	cplugin->priv->ui_manager = GTK_UI_MANAGER (gtk_builder_get_object (builder, "totem-chapters-ui-manager"));
+	g_object_ref (cplugin->priv->ui_manager);
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	column = gtk_tree_view_column_new_with_attributes ("Pixbuf", renderer, "pixbuf", CHAPTERS_PIXBUF_COLUMN, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (cplugin->priv->tree), column);
+
+	renderer = gtk_cell_renderer_text_new ();
+
+	g_object_set (renderer, "editable", TRUE, NULL);
+	g_signal_connect (G_OBJECT (renderer), "editing-started",
+			  G_CALLBACK (prepare_chapter_edit), cplugin);
+	g_signal_connect (G_OBJECT (renderer), "edited",
+			  G_CALLBACK (finish_chapter_edit), cplugin);
+
+	column = gtk_tree_view_column_new_with_attributes ("Title", renderer,
+							   "markup", CHAPTERS_TITLE_COLUMN, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (cplugin->priv->tree), column);
+
+	cplugin->totem = g_object_ref (totem);
+	/* for read operation */
+	cplugin->priv->cancellable[0] = NULL;
+	/* for write operation */
+	cplugin->priv->cancellable[1] = NULL;
+	cplugin->edit_chapter = NULL;
+	cplugin->priv->last_frame = NULL;
+	cplugin->priv->cmml_mrl = NULL;
+	cplugin->priv->last_time = 0;
+
+	cplugin->priv->add_button = GTK_WIDGET (gtk_builder_get_object (builder, "add_button"));
+	cplugin->priv->remove_button = GTK_WIDGET (gtk_builder_get_object (builder, "remove_button"));
+	cplugin->priv->save_button = GTK_WIDGET (gtk_builder_get_object (builder, "save_button"));
+	cplugin->priv->goto_button = GTK_WIDGET (gtk_builder_get_object (builder, "goto_button"));
+	cplugin->priv->load_button = GTK_WIDGET (gtk_builder_get_object (builder, "load_button"));
+	cplugin->priv->continue_button = GTK_WIDGET (gtk_builder_get_object (builder, "continue_button"));
+
+	gtk_widget_hide (cplugin->priv->load_button);
+	gtk_widget_hide (cplugin->priv->continue_button);
+
+	cplugin->priv->list_box = GTK_WIDGET (gtk_builder_get_object (builder, "main_vbox"));
+	cplugin->priv->load_box = GTK_WIDGET (gtk_builder_get_object (builder, "load_vbox"));
+
+	main_box = gtk_vbox_new (FALSE, 6);
+	gtk_box_pack_start (GTK_BOX (main_box), cplugin->priv->list_box, TRUE, TRUE, 0);
+	gtk_box_pack_start (GTK_BOX (main_box), cplugin->priv->load_box, TRUE, TRUE, 0);
+
+	set_no_data_visible (TRUE, FALSE, cplugin);
+
+	totem_add_sidebar_page (totem, "chapters", _("Chapters"), main_box);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (cplugin->priv->tree));
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+
+	g_signal_connect (G_OBJECT (totem),
+			  "file-opened",
+			  G_CALLBACK (totem_file_opened_async_cb),
+			  plugin);
+	g_signal_connect (G_OBJECT (totem),
+			  "file-closed",
+			  G_CALLBACK (totem_file_closed_cb),
+			  plugin);
+	g_signal_connect (G_OBJECT (selection),
+			  "changed",
+			  G_CALLBACK (chapter_selection_changed_cb),
+			  plugin);
+
+	mrl = totem_get_current_mrl (cplugin->totem);
+	if (mrl != NULL)
+		totem_file_opened_async_cb (cplugin->totem, mrl, cplugin);
+
+	g_object_unref (builder);
+	g_free (mrl);
+}
+
+static void
+impl_deactivate (PeasActivatable	*plugin,
+		 GObject		*object)
+{
+	TotemObject		*totem;
+	TotemChaptersPlugin	*cplugin;
+
+	g_return_if_fail (TOTEM_IS_OBJECT (object));
+	g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
+
+	totem = TOTEM_OBJECT (object);
+	cplugin = TOTEM_CHAPTERS_PLUGIN (plugin);
+
+	/* FIXME: do not cancel async operation if any */
+
+	g_signal_handlers_disconnect_by_func (G_OBJECT (totem),
+					      totem_file_opened_async_cb,
+					      plugin);
+	g_signal_handlers_disconnect_by_func (G_OBJECT (totem),
+					      totem_file_closed_cb,
+					      plugin);
+	if (cplugin->priv->gconf != NULL) {
+		gconf_client_notify_remove (cplugin->priv->gconf, cplugin->priv->autoload_handle_id);
+		g_object_unref (cplugin->priv->gconf);
+	}
+
+	if (G_UNLIKELY (cplugin->priv->last_frame != NULL))
+		g_object_unref (G_OBJECT (cplugin->priv->last_frame));
+
+	if (G_UNLIKELY (cplugin->edit_chapter != NULL))
+		gtk_widget_destroy (GTK_WIDGET (cplugin->edit_chapter));
+
+	if (G_LIKELY (cplugin->priv->action_group != NULL))
+		g_object_unref (cplugin->priv->action_group);
+
+	if (G_LIKELY (cplugin->priv->ui_manager != NULL))
+		g_object_unref (cplugin->priv->ui_manager);
+
+	if (G_LIKELY (cplugin->priv->cancellable[0] != NULL))
+		g_cancellable_cancel (cplugin->priv->cancellable[0]);
+
+	if (G_LIKELY (cplugin->priv->cancellable[1] != NULL))
+		g_cancellable_cancel (cplugin->priv->cancellable[1]);
+
+
+	g_object_unref (cplugin->totem);
+	g_free (cplugin->priv->cmml_mrl);
+
+	totem_remove_sidebar_page (totem, "chapters");
+}
diff --git a/src/plugins/chapters/totem-cmml-parser.c b/src/plugins/chapters/totem-cmml-parser.c
new file mode 100644
index 0000000..691f90c
--- /dev/null
+++ b/src/plugins/chapters/totem-cmml-parser.c
@@ -0,0 +1,826 @@
+/*
+ * Copyright (C) 2010 Alexander Saprykin <xelfium gmail com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ */
+
+/**
+ * SECTION:totem-cmml-parser
+ * @short_description: parser for CMML files
+ * @stability: Unstable
+ *
+ * These functions are used to parse CMML files for chapters.
+ **/
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+#include <libxml/xmlreader.h>
+#include <libxml/xmlwriter.h>
+#include <string.h>
+#include <math.h>
+#include "totem-cmml-parser.h"
+
+#define MSECS_IN_HOUR (1000 * 60 * 60)
+#define MSECS_IN_MINUTE (1000 * 60)
+#define MSECS_IN_SECOND (1000)
+
+#define TOTEM_CMML_PREAMBLE 	"<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n"
+
+typedef void (*TotemCmmlCallback) (TotemCmmlClip *, gpointer user_data);
+
+typedef enum {
+	TOTEM_CMML_NONE = 0,
+	TOTEM_CMML_CMML,
+	TOTEM_CMML_HEAD,
+	TOTEM_CMML_CLIP
+} TotemCmmlStatus;
+
+typedef struct {
+	xmlTextReaderPtr	reader;
+	TotemCmmlStatus		status;
+	TotemCmmlClip		*clip;
+	TotemCmmlCallback	callback;
+	gpointer		user_data;
+} TotemCmmlContext;
+
+static TotemCmmlContext * totem_cmml_context_new (void);
+static void totem_cmml_context_free (TotemCmmlContext *context);
+static void totem_cmml_context_set_callback (TotemCmmlContext *context, TotemCmmlCallback cb, gpointer user_data);
+static int totem_cmml_compare_clips (gconstpointer pointer_a, gconstpointer pointer_b);
+static TotemCmmlClip * totem_cmml_clip_new_from_attrs (const xmlChar **attrs);
+static void totem_cmml_clip_insert_img_attr (TotemCmmlClip *clip, const xmlChar **attrs);
+static gdouble totem_cmml_parse_smpte (const gchar *str, gdouble framerate);
+static gdouble totem_cmml_parse_npt (const gchar *str);
+static gdouble totem_cmml_parse_time_str (const gchar *str);
+static void totem_cmml_parse_start (TotemCmmlContext *context, const xmlChar *tag, const xmlChar **attrs);
+static void totem_cmml_parse_end (TotemCmmlContext *context, const xmlChar *tag);
+static void totem_cmml_parse_xml_node (TotemCmmlContext	*context);
+static void totem_cmml_read_clip_cb (TotemCmmlClip *clip, gpointer user_data);
+
+static TotemCmmlContext *
+totem_cmml_context_new (void)
+{
+	TotemCmmlContext *ctx = g_new0 (TotemCmmlContext, 1);
+
+	ctx->status = TOTEM_CMML_NONE;
+	return ctx;
+}
+
+static void
+totem_cmml_context_free (TotemCmmlContext *context)
+{
+	g_free (context);
+}
+
+static void
+totem_cmml_context_set_callback (TotemCmmlContext	*context,
+				 TotemCmmlCallback	cb,
+				 gpointer		user_data)
+{
+	g_return_if_fail (context != NULL);
+
+	context->callback = cb;
+	context->user_data = user_data;
+}
+
+static TotemCmmlClip *
+totem_cmml_clip_new_from_attrs (const xmlChar **attrs)
+{
+	gint		i = 0;
+	gint64		start = -1;
+	gchar		*title = NULL;
+
+	g_return_val_if_fail (attrs != NULL, NULL);
+
+	while (attrs[i] != NULL) {
+		if (g_strcmp0 ((const gchar *) attrs[i], "title") == 0)
+			title = g_strdup ((const gchar *) attrs[i + 1]);
+		else if (g_strcmp0 ((const gchar *) attrs[i], "start") == 0)
+			start = (gint64) (totem_cmml_parse_time_str ((const gchar *) attrs[i + 1]) * 1000);
+		i += 2;
+	}
+
+	return totem_cmml_clip_new (title, NULL, start, NULL);
+}
+
+static void
+totem_cmml_clip_insert_img_attr (TotemCmmlClip	*clip,
+				 const xmlChar	**attrs)
+{
+	gint		i = 0;
+	GdkPixdata	*pixdata;
+	GdkPixbuf	*pixbuf;
+	guchar		*base64_dec;
+	guint		st_len;
+	GError		*error = NULL;
+
+	g_return_if_fail (clip != NULL);
+	g_return_if_fail (attrs != NULL);
+
+	while (attrs[i] != NULL) {
+		if (G_LIKELY (g_strcmp0 ((const gchar *) attrs[i], "src") == 0 &&
+		    xmlStrlen (attrs[i + 1]) > 0)) {
+			pixdata = g_new0 (GdkPixdata, 1);
+			/* decode pixbuf data */
+			base64_dec = g_base64_decode ((const gchar *) attrs[i + 1], &st_len);
+			/* deserialize pixbuf data */
+			if (G_UNLIKELY (!gdk_pixdata_deserialize (pixdata, st_len, base64_dec, NULL))) {
+				g_warning ("chapters: failed to deserialize clip's pixbuf data");
+				pixbuf = NULL;
+			} else {
+				pixbuf = gdk_pixbuf_from_pixdata (pixdata, TRUE, &error);
+				if (error != NULL) {
+					g_warning ("chapters: failed to create pixbuf from pixdata: %s", error->message);
+					pixbuf = NULL;
+					g_free (error);
+				}
+			}
+			g_free (pixdata);
+			g_free (base64_dec);
+
+			if (G_LIKELY (pixbuf != NULL)) {
+				clip->pixbuf = g_object_ref (pixbuf);
+				g_object_unref (pixbuf);
+			} else
+				clip->pixbuf = pixbuf;
+			break;
+		}
+		i += 2;
+	}
+}
+
+/* the idea of parsing time was taken from libcmml (and some of code, too) */
+static gdouble
+totem_cmml_parse_smpte (const gchar	*str,
+			gdouble		framerate)
+{
+	gint		h = 0, m = 0, s = 0;
+	gfloat		frames;
+
+	if (G_UNLIKELY (str == NULL))
+		return -1.0;
+
+	/* all is according to specs */
+	if (sscanf (str, "%d:%d:%d:%f", &h, &m, &s, &frames) == 4);
+	/* this is slightly off the specs, but we can handle it */
+	else if (sscanf (str, "%d:%d:%f", &m, &s, &frames) == 3)
+		h = 0;
+	else
+		return -1.0;
+
+	/* check time and framerate bounds */
+	if (G_UNLIKELY (h < 0))
+		return -1.0;
+	if (G_UNLIKELY (m > 59 || m < 0))
+		return -1.0;
+	if (G_UNLIKELY (s > 59 || s < 0))
+		return -1.0;
+
+	if (G_UNLIKELY (frames > (gfloat) ceil (framerate) || frames < 0))
+		return -1.0;
+
+	return ((h * 3600.0) + (m * 60.0) + s) + (frames / framerate);
+}
+
+static gdouble
+totem_cmml_parse_npt (const gchar *str)
+{
+	gint		h, m;
+	gfloat		s;
+
+	if (G_UNLIKELY (str == NULL))
+		return -1.0;
+
+	/* all is ok */
+	if (sscanf (str, "%d:%d:%f",  &h, &m, &s) == 3);
+	/* slightly off the spec */
+	else if (sscanf (str, "%d:%f",  &m, &s) == 2)
+		h = 0;
+	/* time in seconds, all is ok and we can return it now */
+	else if (G_LIKELY (sscanf (str, "%f", &s)))
+		return s;
+	else
+		return -1.0;
+
+	if (G_UNLIKELY (h < 0))
+		return -1;
+	if (G_UNLIKELY (m > 59 || m < 0))
+		return -1;
+	if (G_UNLIKELY (s >= 60.0 || s < 0.0))
+		return -1;
+
+	return (h * 3600.0) + (m * 60.0) + s;
+}
+
+
+static gdouble
+totem_cmml_parse_time_str (const gchar *str)
+{
+	gchar	timespec[16];
+
+	if (G_UNLIKELY (str == NULL))
+		return -1.0;
+
+	/* we need to choose parsing function to use */
+	if (sscanf (str, "npt:%16s", timespec) == 1)
+		return totem_cmml_parse_npt (str + 4);
+
+	if (sscanf (str, "smpte-24:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 9, 24.0);
+
+	if (sscanf (str, "smpte-24-drop:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 14, 23.976);
+
+	if (sscanf (str, "smpte-25:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 9, 25.0);
+
+	if (sscanf (str, "smpte-30:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 9, 30.0);
+
+	if (sscanf (str, "smpte-30-drop:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 14, 29.97);
+
+	if (sscanf (str, "smpte-50:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 9, 50.0);
+
+	if (sscanf (str, "smpte-60:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 9, 60);
+
+	if (sscanf (str, "smpte-60-drop:%16s", timespec) == 1)
+		return totem_cmml_parse_smpte (str + 14, 59.94);
+
+	/* default is npt */
+	return totem_cmml_parse_npt (str);
+}
+
+static void
+totem_cmml_parse_start (TotemCmmlContext	*context,
+			const xmlChar		*tag,
+			const xmlChar		**attrs)
+{
+	g_return_if_fail (context != NULL);
+	g_return_if_fail (tag != NULL);
+
+	if (g_strcmp0 ((const gchar *) tag, "cmml") == 0) {
+		if (G_UNLIKELY (context->status != TOTEM_CMML_NONE))
+			return;
+
+		/* empty document */
+		if (G_UNLIKELY (xmlTextReaderIsEmptyElement (context->reader)))
+			return;
+
+		context->status = TOTEM_CMML_CMML;
+	} else if (g_strcmp0 ((const gchar *) tag, "head") == 0) {
+		if (G_UNLIKELY (context->status != TOTEM_CMML_CMML))
+			return;
+
+		/* empty head, let it ok */
+		if (G_UNLIKELY (xmlTextReaderIsEmptyElement (context->reader)))
+			context->status = TOTEM_CMML_CMML;
+
+		context->status = TOTEM_CMML_HEAD;
+	} else if (g_strcmp0 ((const gchar *) tag, "clip") == 0) {
+		if (G_UNLIKELY (context->status != TOTEM_CMML_CMML))
+			return;
+
+		context->clip = totem_cmml_clip_new_from_attrs (attrs);
+
+		/* empty clip element, we need to set status to CMML */
+		if (G_UNLIKELY (xmlTextReaderIsEmptyElement (context->reader))) {
+			if (G_LIKELY(context->callback != NULL))
+				(context->callback) (context->clip, context->user_data);
+
+			context->status = TOTEM_CMML_CMML;
+			totem_cmml_clip_free (context->clip);
+			context->clip = NULL;
+		} else
+			context->status = TOTEM_CMML_CLIP;
+	} else if (g_strcmp0 ((const gchar *) tag, "img") == 0) {
+		if (G_UNLIKELY (context->status != TOTEM_CMML_CLIP))
+			return;
+
+		totem_cmml_clip_insert_img_attr (context->clip, attrs);
+	}
+}
+
+static void
+totem_cmml_parse_end (TotemCmmlContext	*context,
+		      const xmlChar	*tag)
+{
+	g_return_if_fail (context != NULL);
+	g_return_if_fail (tag != NULL);
+
+	if (g_strcmp0 ((const gchar *) tag, "cmml") == 0) {
+		if (G_UNLIKELY (context->status != TOTEM_CMML_CMML))
+			return;
+
+		context->status = TOTEM_CMML_NONE;
+	} else if (g_strcmp0 ((const gchar *) tag, "head") == 0) {
+		if (G_UNLIKELY (context->status != TOTEM_CMML_HEAD))
+			return;
+
+		context->status = TOTEM_CMML_CMML;
+	} else if (g_strcmp0 ((const gchar *) tag, "clip") == 0) {
+		if (G_UNLIKELY (context->status != TOTEM_CMML_CLIP))
+			return;
+
+		context->status = TOTEM_CMML_CMML;
+		if (G_LIKELY (context->callback != NULL))
+			(context->callback) (context->clip, context->user_data);
+
+		totem_cmml_clip_free (context->clip);
+		context->clip = NULL;
+		return;
+	} else if (g_strcmp0 ((const gchar *) tag, "img") == 0);
+}
+
+static void
+totem_cmml_parse_xml_node (TotemCmmlContext *context)
+{
+	xmlChar		*tag;
+	xmlChar		**attrs = NULL;
+	gint		j, i = 0;
+
+	g_return_if_fail (context != NULL);
+	g_return_if_fail (context->reader != NULL);
+
+	tag = xmlStrdup (xmlTextReaderName (context->reader));
+
+	if (xmlTextReaderNodeType (context->reader) == XML_READER_TYPE_ELEMENT) {
+		/* read all attributes into the array [name, value, name...] */
+		if (xmlTextReaderHasAttributes (context->reader)) {
+			attrs = g_new0 (xmlChar *, xmlTextReaderAttributeCount (context->reader) * 2 + 1);
+
+			while (xmlTextReaderMoveToNextAttribute (context->reader)) {
+				attrs[i] = xmlStrdup (xmlTextReaderName (context->reader));
+				attrs[i + 1] = xmlStrdup (xmlTextReaderValue (context->reader));
+				i += 2;
+			}
+
+			attrs[i] = NULL;
+			xmlTextReaderMoveToElement (context->reader);
+		}
+
+		totem_cmml_parse_start (context, tag, (const xmlChar **) attrs);
+		/* free resources */
+		for (j = i - 1; j >= 0; j -= 1)
+			xmlFree (attrs[j]);
+		g_free (attrs);
+	} else if (xmlTextReaderNodeType (context->reader) == XML_READER_TYPE_END_ELEMENT)
+		totem_cmml_parse_end (context, tag);
+	xmlFree (tag);
+}
+
+static void
+totem_cmml_read_clip_cb (TotemCmmlClip	*clip,
+			 gpointer	user_data)
+{
+	TotemCmmlClip	*new_clip;
+
+	g_return_if_fail (clip != NULL);
+	g_return_if_fail (user_data != NULL);
+
+	new_clip = totem_cmml_clip_copy (clip);
+
+	if (G_LIKELY (new_clip != NULL && new_clip->time_start >= 0))
+		* ( (GList **) user_data) = g_list_append ( * ( (GList **) user_data), new_clip);
+	/* clip with -1 start time is bad one, remove it */
+	else
+		totem_cmml_clip_free (new_clip);
+}
+
+/**
+ * totem_cmml_convert_msecs_to_str:
+ * @time_msecs: time to convert in msecs
+ *
+ * Converts %time_msecs to string "hh:mm:ss".
+ *
+ * Returns: string in "hh:mm:ss" format.
+ **/
+gchar *
+totem_cmml_convert_msecs_to_str (gint64 time_msecs)
+{
+	gint32		hours, minutes, seconds;
+
+	if (G_UNLIKELY (time_msecs < 0))
+		hours = minutes = seconds = 0;
+	else {
+		hours = time_msecs / MSECS_IN_HOUR;
+		minutes = (time_msecs % MSECS_IN_HOUR) / MSECS_IN_MINUTE;
+		seconds = (time_msecs % MSECS_IN_MINUTE) / MSECS_IN_SECOND;
+	}
+	return g_strdup_printf ("%.2d:%.2d:%.2d", hours, minutes, seconds);
+}
+
+static int
+totem_cmml_compare_clips (gconstpointer pointer_a,
+			  gconstpointer pointer_b)
+{
+	TotemCmmlClip	*clip_a, *clip_b;
+
+	g_return_val_if_fail (pointer_a != NULL && pointer_b != NULL, -1);
+
+	clip_a = (TotemCmmlClip *) pointer_a;
+	clip_b = (TotemCmmlClip *) pointer_b;
+
+	return clip_a->time_start - clip_b->time_start;
+}
+
+/**
+ * totem_cmml_clip_new:
+ * @title: clip title, %NULL allowed
+ * @desc: clip description, %NULL allowed
+ * @start: clip start time in msecs
+ * @pixbuf: clip thumbnail
+ *
+ * Creates new clip structure with appropriate parameters.
+ *
+ * Returns: newly allocated #TotemCmmlClip structure.
+ **/
+TotemCmmlClip *
+totem_cmml_clip_new (const gchar	*title,
+		     const gchar	*desc,
+		     gint64		start,
+		     GdkPixbuf		*pixbuf)
+{
+	TotemCmmlClip		*clip;
+
+	clip = g_new0 (TotemCmmlClip, 1);
+
+	clip->title = g_strdup (title);
+	clip->desc = g_strdup (desc);
+	clip->time_start = start;
+	if (G_LIKELY (pixbuf != NULL))
+		clip->pixbuf = g_object_ref (pixbuf);
+
+	return clip;
+}
+
+/**
+ * totem_cmml_clip_free:
+ * @clip: #TotemCmmlClip to free
+ *
+ * Frees unused clip structure.
+ **/
+void
+totem_cmml_clip_free (TotemCmmlClip *clip)
+{
+	if (clip == NULL)
+		return;
+
+	if (G_LIKELY (clip->pixbuf != NULL))
+		g_object_unref (clip->pixbuf);
+	g_free (clip->title);
+	g_free (clip->desc);
+	g_free (clip);
+}
+
+/**
+ * totem_cmml_clip_copy:
+ * @clip: #TotemCmmlClip structure to copy
+ *
+ * Copies #TotemCmmlClip structure.
+ *
+ * Returns: newly allocated #TotemCmmlClip if @clip != %NULL, %NULL otherwise.
+ **/
+TotemCmmlClip *
+totem_cmml_clip_copy (TotemCmmlClip *clip)
+{
+	g_return_val_if_fail (clip != NULL, NULL);
+
+	return totem_cmml_clip_new (clip->title, clip->desc, clip->time_start, clip->pixbuf);
+}
+
+static void
+totem_cmml_read_file_result (GObject		*source_object,
+			     GAsyncResult	*result,
+			     gpointer		user_data)
+{
+	GError			*error = NULL;
+	xmlTextReaderPtr	reader;
+	TotemCmmlContext	*context;
+	TotemCmmlAsyncData	*data;
+	gint			ret;
+	gchar			*contents;
+	gsize			length;
+
+
+	data = (TotemCmmlAsyncData *) user_data;
+	data->is_exists = TRUE;
+
+	g_file_load_contents_finish (G_FILE (source_object), result, &contents, &length, NULL, &error);
+	g_object_unref (source_object);
+
+	if (G_UNLIKELY (error != NULL)) {
+		g_warning ("chapters: failed to load CMML file %s: %s", data->file, error->message);
+		if (g_error_matches (error, g_io_error_quark(), G_IO_ERROR_NOT_FOUND)) {
+			/* it's ok if file doesn't exist */
+			data->successful = TRUE;
+			data->is_exists = FALSE;
+		} else {
+			data->successful = FALSE;
+			data->error = g_strdup (error->message);
+		}
+		g_error_free (error);
+		(data->final) (data, NULL);
+		return;
+	}
+
+	/* parse in-memory xml data */
+	reader = xmlReaderForMemory (contents, length, "", NULL, 0);
+	if (G_UNLIKELY (reader == NULL)) {
+		g_warning ("chapters: failed to parse CMML file %s", data->file);
+		g_free (contents);
+		data->successful = FALSE;
+		data->error = g_strdup (_("Failed to parse CMML file"));
+		(data->final) (data, NULL);
+		return;
+	}
+
+	context = totem_cmml_context_new ();
+	context->reader = reader;
+	totem_cmml_context_set_callback (context, totem_cmml_read_clip_cb, &(data->list));
+
+	ret = xmlTextReaderRead (reader);
+	while (ret == 1) {
+		totem_cmml_parse_xml_node (context);
+		ret = xmlTextReaderRead (reader);
+	}
+
+	g_free (contents);
+	xmlFreeTextReader (reader);
+	totem_cmml_clip_free (context->clip);
+	totem_cmml_context_free (context);
+
+	/* sort clips by time growth */
+	data->list = g_list_sort (data->list, (GCompareFunc) totem_cmml_compare_clips);
+	data->successful = TRUE;
+	(data->final) (data, NULL);
+}
+
+/**
+ * totem_cmml_read_file_async:
+ * @data: #TotemCmmlAsyncData structure with info needed
+ *
+ * Reads CMML file and parse it for clips in async way.
+ *
+ * Returns: 0 if no errors occurred while starting async reading, -1 otherwise.
+ **/
+gint
+totem_cmml_read_file_async (TotemCmmlAsyncData	*data)
+{
+	GFile	*gio_file;
+
+	g_return_val_if_fail (data != NULL, -1);
+	g_return_val_if_fail (data->file != NULL, -1);
+	g_return_val_if_fail (data->list == NULL, -1);
+	g_return_val_if_fail (data->final != NULL, -1);
+
+	gio_file = g_file_new_for_uri (data->file);
+	g_file_load_contents_async (gio_file, data->cancellable, totem_cmml_read_file_result, data);
+	return 0;
+}
+
+static void
+totem_cmml_write_file_result (GObject		*source_object,
+			      GAsyncResult	*result,
+			      gpointer		user_data)
+{
+	GError			*error = NULL;
+	TotemCmmlAsyncData	*data;
+
+	data = (TotemCmmlAsyncData *) user_data;
+
+	g_file_replace_contents_finish (G_FILE (source_object), result, NULL, &error);
+	g_object_unref (source_object);
+
+	if (G_UNLIKELY (error != NULL)) {
+		g_warning ("chapters: failed to write CMML file %s: %s", data->file, error->message);
+		data->error = g_strdup (error->message);
+		data->successful = FALSE;
+		g_error_free (error);
+		(data->final) (data, NULL);
+		return;
+	}
+
+	g_free (data->buf);
+	data->successful = TRUE;
+	(data->final) (data, NULL);
+}
+
+/**
+ * totem_cmml_write_file_async:
+ * @data: #TotemCmmlAsyncData structure with info needed
+ *
+ * Writes CMML file with clips given.
+ *
+ * Returns: 0 if no errors occurred while starting async writing, -1 otherwise.
+ **/
+gint
+totem_cmml_write_file_async (TotemCmmlAsyncData *data)
+{
+	GFile			*gio_file;
+	gint			res, len;
+	GList			*cur_clip;
+	xmlTextWriterPtr	writer;
+	xmlBufferPtr		buf;
+
+	g_return_val_if_fail (data != NULL, -1);
+	g_return_val_if_fail (data->file != NULL, -1);
+	g_return_val_if_fail (data->final != NULL, -1);
+
+	buf = xmlBufferCreate ();
+	if (G_UNLIKELY (buf == NULL)) {
+		g_warning ("chapters: failed to create xml buffer");
+		return -1;
+	}
+
+	writer = xmlNewTextWriterMemory (buf, 0);
+	if (G_UNLIKELY (writer == NULL)) {
+		g_warning ("chapters: failed to create xml buffer");
+		xmlBufferFree (buf);
+		return -1;
+	}
+
+	res = xmlTextWriterStartDocument (writer, "1.0", "UTF-8", "yes");
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	/* CMML preamble */
+	res = xmlTextWriterWriteRaw (writer, (const xmlChar *) TOTEM_CMML_PREAMBLE);
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	/* start <cmml> tag */
+	res = xmlTextWriterStartElement (writer, (const xmlChar *) "cmml");
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	res = xmlTextWriterWriteRaw (writer, (const xmlChar *) "\n");
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	/* write <head> tag */
+	res = xmlTextWriterWriteElement (writer, (const xmlChar *) "head", (const xmlChar *) "");
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	res = xmlTextWriterWriteRaw (writer, (const xmlChar *) "\n");
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	/* iterate through clip list */
+	cur_clip = data->list;
+	while (cur_clip != NULL) {
+
+		gdouble		time_start;
+		gchar 		*base64_enc;
+		GdkPixdata 	*pixdata;
+		guint		st_len;
+		guint8		*stream;
+		TotemCmmlClip	*clip;
+
+		clip = (TotemCmmlClip *) cur_clip->data;
+		time_start = ((gdouble) clip->time_start) / 1000;
+
+		/* start <clip> tag */
+		res = xmlTextWriterStartElement (writer, (const xmlChar *) "clip");
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		res = xmlTextWriterWriteAttribute (writer, (const xmlChar *) "title", (const xmlChar *) clip->title);
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		res = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "start", "%.3f", time_start);
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		res = xmlTextWriterWriteRaw (writer, (const xmlChar *) "\n");
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		/* start <img> tag */
+		res = xmlTextWriterStartElement (writer, (const xmlChar *) "img");
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		if (G_LIKELY (((TotemCmmlClip *) cur_clip->data)->pixbuf != NULL)) {
+			pixdata = g_new0 (GdkPixdata, 1);
+
+			/* encode and serialize pixbuf data */
+			gdk_pixdata_from_pixbuf (pixdata, ((TotemCmmlClip *) cur_clip->data)->pixbuf, TRUE);
+			stream = gdk_pixdata_serialize (pixdata, &st_len);
+			base64_enc = g_base64_encode (stream, st_len);
+
+			g_free (pixdata->pixel_data);
+			g_free (pixdata);
+			g_free (stream);
+		}
+		else
+			base64_enc = g_strdup ("");
+
+		if (g_strcmp0 (base64_enc, "") != 0) {
+			res = xmlTextWriterWriteAttribute (writer, (const xmlChar *) "src", (const xmlChar *) base64_enc);
+			if (G_UNLIKELY (res < 0)) {
+				g_free (base64_enc);
+				break;
+			}
+		}
+		g_free (base64_enc);
+
+		/* end <img> tag */
+		res = xmlTextWriterEndElement (writer);
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		res = xmlTextWriterWriteRaw (writer, (const xmlChar *) "\n");
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		/* end <clip> tag */
+		res = xmlTextWriterEndElement (writer);
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		res = xmlTextWriterWriteRaw (writer, (const xmlChar *) "\n");
+		if (G_UNLIKELY (res < 0))
+			break;
+
+		cur_clip = cur_clip->next;
+	}
+
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	/* end <cmml> tag*/
+	res = xmlTextWriterEndElement (writer);
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	res = xmlTextWriterEndDocument (writer);
+	if (G_UNLIKELY (res < 0)) {
+		xmlBufferFree (buf);
+		xmlFreeTextWriter (writer);
+		return -1;
+	}
+
+	data->buf = g_strdup ((const char *) xmlBufferContent (buf));
+	len = xmlBufferLength (buf);
+	xmlBufferFree (buf);
+	xmlFreeTextWriter (writer);
+
+	gio_file = g_file_new_for_uri (data->file);
+	g_file_replace_contents_async (gio_file, data->buf, len, NULL, FALSE,
+				       G_FILE_CREATE_NONE, data->cancellable,
+				       (GAsyncReadyCallback) totem_cmml_write_file_result, data);
+
+	return 0;
+}
diff --git a/src/plugins/chapters/totem-cmml-parser.h b/src/plugins/chapters/totem-cmml-parser.h
new file mode 100644
index 0000000..649a29c
--- /dev/null
+++ b/src/plugins/chapters/totem-cmml-parser.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 Alexander Saprykin <xelfium gmail com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ */
+
+#ifndef TOTEM_CMML_PARSER_H_
+#define TOTEM_CMML_PARSER_H_
+
+#include <glib.h>
+#include <libxml/xmlreader.h>
+
+/**
+ * TotemCmmlClip:
+ * @title: clip title
+ * @desc: clip description
+ * @time_start: start time of clip in msecs
+ * @pixbuf: clip thumbnail
+ *
+ *  Structure to handle clip data.
+ **/
+typedef struct {
+	gchar		*title;
+	gchar		*desc;
+	gint64		time_start;
+	GdkPixbuf	*pixbuf;
+} TotemCmmlClip;
+
+/**
+ * TotemCmmlAsyncData:
+ * @file: file to read
+ * @list: list to store chapters to read in/write
+ * @final: function to call at final, %NULL is allowed
+ * @user_data: user data passed to @final callback
+ * @buf: buffer for writing
+ * @error: last error message string, or %NULL if not any
+ * @successful: whether operation was successful or not
+ * @is_exists: whether @file exists or not
+ * @from_dialog: whether read operation was started from open dialog
+ * @cancellable: object to cancel operation, %NULL is allowed
+ *
+ * Structure to handle data for async reading/writing clip data.
+ **/
+typedef struct {
+	gchar		*file;
+	GList		*list;
+	GFunc		final;
+	gpointer	user_data;
+	gchar		*buf;
+	gchar		*error;
+	gboolean	successful;
+	gboolean	is_exists;
+	gboolean	from_dialog;
+	GCancellable	*cancellable;
+} TotemCmmlAsyncData;
+
+gchar *	totem_cmml_convert_msecs_to_str (gint64 time_msecs);
+TotemCmmlClip * totem_cmml_clip_new (const gchar *title, const gchar *desc, gint64 start, GdkPixbuf *pixbuf);
+void totem_cmml_clip_free (TotemCmmlClip *clip);
+TotemCmmlClip * totem_cmml_clip_copy (TotemCmmlClip *clip);
+gint totem_cmml_read_file_async (TotemCmmlAsyncData *data);
+gint totem_cmml_write_file_async (TotemCmmlAsyncData *data);
+
+#endif /* TOTEM_CMML_PARSER_H_ */
diff --git a/src/plugins/chapters/totem-edit-chapter.c b/src/plugins/chapters/totem-edit-chapter.c
new file mode 100644
index 0000000..aa0fd14
--- /dev/null
+++ b/src/plugins/chapters/totem-edit-chapter.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2010 Alexander Saprykin <xelfium gmail com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ */
+
+/**
+ * SECTION:totem-edit-chapter
+ * @short_description: dialog to add new chapters in chapters plugin
+ * @stability: Unstable
+ * @include: totem-edit-chapter.h
+ *
+ * #TotemEditChapter dialog is used for entering names of new chapters in the chapters plugin
+ **/
+
+#include <gtk/gtk.h>
+
+#include "totem.h"
+#include "totem-interface.h"
+#include "totem-edit-chapter.h"
+#include <string.h>
+
+struct TotemEditChapterPrivate {
+	GtkEntry	*title_entry;
+	GtkWidget	*container;
+};
+
+
+#define TOTEM_EDIT_CHAPTER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_EDIT_CHAPTER, TotemEditChapterPrivate))
+
+G_DEFINE_TYPE (TotemEditChapter, totem_edit_chapter, GTK_TYPE_DIALOG)
+
+/* GtkBuilder callbacks */
+void title_entry_changed_cb (GtkEditable *entry, gpointer user_data);
+
+static void
+totem_edit_chapter_class_init (TotemEditChapterClass *klass)
+{
+	g_type_class_add_private (klass, sizeof (TotemEditChapterPrivate));
+}
+
+static void
+totem_edit_chapter_init (TotemEditChapter *self)
+{
+	GtkBuilder	*builder;
+
+	self->priv = TOTEM_EDIT_CHAPTER_GET_PRIVATE (self);
+	builder = totem_interface_load ("chapters-edit.ui", FALSE, NULL, self);
+
+	if (builder == NULL) {
+		self->priv->container = NULL;
+		return;
+	}
+
+	self->priv->container = GTK_WIDGET (gtk_builder_get_object (builder, "main_vbox"));
+	g_object_ref (self->priv->container);
+	self->priv->title_entry = GTK_ENTRY (gtk_builder_get_object (builder, "title_entry"));
+
+	g_object_unref (builder);
+}
+
+GtkWidget*
+totem_edit_chapter_new (void)
+{
+	TotemEditChapter	*edit_chapter;
+	GtkWidget		*dialog_area;
+
+	edit_chapter = TOTEM_EDIT_CHAPTER (g_object_new (TOTEM_TYPE_EDIT_CHAPTER, NULL));
+
+	if (G_UNLIKELY (edit_chapter->priv->container == NULL)) {
+		g_object_unref (edit_chapter);
+		return NULL;
+	}
+
+	gtk_window_set_title (GTK_WINDOW (edit_chapter), "Add Chapter");
+
+	gtk_dialog_add_buttons (GTK_DIALOG (edit_chapter),
+				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+				GTK_STOCK_OK, GTK_RESPONSE_OK,
+				NULL);
+	gtk_dialog_set_has_separator (GTK_DIALOG (edit_chapter), FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (edit_chapter), 5);
+	gtk_dialog_set_default_response (GTK_DIALOG (edit_chapter), GTK_RESPONSE_OK);
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (edit_chapter), GTK_RESPONSE_OK, FALSE);
+
+	dialog_area = gtk_dialog_get_content_area (GTK_DIALOG (edit_chapter));
+	gtk_box_pack_start (GTK_BOX (dialog_area),
+			    edit_chapter->priv->container,
+			    FALSE, TRUE, 0);
+
+	gtk_widget_show_all (dialog_area);
+
+	return GTK_WIDGET (edit_chapter);
+}
+
+void
+totem_edit_chapter_set_title (TotemEditChapter	*edit_chapter,
+			      const gchar	*title)
+{
+	g_return_if_fail (TOTEM_IS_EDIT_CHAPTER (edit_chapter));
+
+	gtk_entry_set_text (edit_chapter->priv->title_entry, title);
+}
+
+gchar *
+totem_edit_chapter_get_title (TotemEditChapter *edit_chapter)
+{
+	g_return_val_if_fail (TOTEM_IS_EDIT_CHAPTER (edit_chapter), NULL);
+
+	return g_strdup (gtk_entry_get_text (edit_chapter->priv->title_entry));
+}
+
+void
+title_entry_changed_cb (GtkEditable	*entry,
+			gpointer	user_data)
+{
+	GtkDialog	*dialog;
+	gboolean	sens;
+
+	g_return_if_fail (GTK_IS_ENTRY (entry));
+	g_return_if_fail (GTK_IS_DIALOG (user_data));
+
+	dialog = GTK_DIALOG (user_data);
+	sens = (gtk_entry_get_text_length (GTK_ENTRY (entry)) > 0);
+
+	gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, sens);
+}
diff --git a/src/plugins/chapters/totem-edit-chapter.h b/src/plugins/chapters/totem-edit-chapter.h
new file mode 100644
index 0000000..8a61e9e
--- /dev/null
+++ b/src/plugins/chapters/totem-edit-chapter.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 Alexander Saprykin <xelfium gmail com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ */
+
+#ifndef TOTEM_EDIT_CHAPTER_H
+#define TOTEM_EDIT_CHAPTER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TOTEM_TYPE_EDIT_CHAPTER			(totem_edit_chapter_get_type ())
+#define TOTEM_EDIT_CHAPTER(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), TOTEM_TYPE_EDIT_CHAPTER, TotemEditChapter))
+#define TOTEM_EDIT_CHAPTER_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_EDIT_CHAPTER, TotemEditChapterClass))
+#define TOTEM_IS_EDIT_CHAPTER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), TOTEM_TYPE_EDIT_CHAPTER))
+#define TOTEM_IS_EDIT_CHAPTER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_EDIT_CHAPTER))
+
+typedef struct TotemEditChapter			TotemEditChapter;
+typedef struct TotemEditChapterClass		TotemEditChapterClass;
+typedef struct TotemEditChapterPrivate		TotemEditChapterPrivate;
+
+struct TotemEditChapter {
+	GtkDialog parent;
+	TotemEditChapterPrivate *priv;
+};
+
+struct TotemEditChapterClass {
+	GtkDialogClass parent_class;
+};
+
+GType totem_edit_chapter_get_type (void);
+GtkWidget * totem_edit_chapter_new (void);
+void totem_edit_chapter_set_title (TotemEditChapter *edit_chapter, const gchar *title);
+gchar * totem_edit_chapter_get_title (TotemEditChapter *edit_chapter);
+
+G_END_DECLS
+
+#endif /* TOTEM_EDIT_CHAPTER_H */
diff --git a/src/totem-preferences.c b/src/totem-preferences.c
index 8dbae16..e0015cc 100644
--- a/src/totem-preferences.c
+++ b/src/totem-preferences.c
@@ -63,6 +63,7 @@ G_MODULE_EXPORT void font_set_cb (GtkFontButton * fb, Totem * totem);
 G_MODULE_EXPORT void encoding_set_cb (GtkComboBox *cb, Totem *totem);
 G_MODULE_EXPORT void font_changed_cb (GConfClient *client, guint cnxn_id, GConfEntry *entry, Totem *totem);
 G_MODULE_EXPORT void encoding_changed_cb (GConfClient *client, guint cnxn_id, GConfEntry *entry, Totem *totem);
+G_MODULE_EXPORT void auto_chapters_toggled_cb (GtkToggleButton *togglebutton, Totem *totem);
 
 static void
 totem_action_info (char *reason, Totem *totem)
@@ -353,6 +354,23 @@ autoload_subtitles_changed_cb (GConfClient *client, guint cnxn_id,
 			G_CALLBACK (checkbutton3_toggled_cb), totem);
 }
 
+static void
+autoload_chapters_changed_cb (GConfClient *client, guint cnxn_id,
+			      GConfEntry *entry, Totem *totem)
+{
+	GObject *item;
+
+	item = gtk_builder_get_object (totem->xml, "tpw_auto_chapters_checkbutton");
+	g_signal_handlers_disconnect_by_func (item,
+					      auto_chapters_toggled_cb, totem);
+
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item),
+				      gconf_client_get_bool (totem->gc, GCONF_PREFIX"/autoload_chapters", NULL));
+
+	g_signal_connect (item, "toggled",
+			  G_CALLBACK (auto_chapters_toggled_cb), totem);
+}
+
 void
 connection_combobox_changed (GtkComboBox *combobox, Totem *totem)
 {
@@ -523,10 +541,20 @@ encoding_changed_cb (GConfClient *client, guint cnxn_id,
 }
 
 void
+auto_chapters_toggled_cb (GtkToggleButton *togglebutton, Totem *totem)
+{
+	gboolean value;
+
+	value = gtk_toggle_button_get_active (togglebutton);
+
+	gconf_client_set_bool (totem->gc, GCONF_PREFIX"/autoload_chapters", value, NULL);
+}
+
+void
 totem_setup_preferences (Totem *totem)
 {
 	GtkWidget *menu, *content_area;
-	gboolean show_visuals, auto_resize, is_local, no_deinterlace, lock_screensaver_on_audio;
+	gboolean show_visuals, auto_resize, is_local, no_deinterlace, lock_screensaver_on_audio, auto_chapters;
 	int connection_speed;
 	guint i, hidden;
 	char *visual, *font, *encoding;
@@ -657,6 +685,19 @@ totem_setup_preferences (Totem *totem)
 				 (GConfClientNotifyFunc) autoload_subtitles_changed_cb,
 				 totem, NULL, NULL);
 
+	/* Auto-load external chapters */
+	item = gtk_builder_get_object (totem->xml, "tpw_auto_chapters_checkbutton");
+	auto_chapters = gconf_client_get_bool (totem->gc,
+					       GCONF_PREFIX"/autoload_chapters", NULL);
+
+	g_signal_handlers_disconnect_by_func (item, auto_chapters_toggled_cb, totem);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item), auto_chapters);
+	g_signal_connect (item, "toggled", G_CALLBACK (auto_chapters_toggled_cb), totem);
+
+	gconf_client_notify_add (totem->gc, GCONF_PREFIX"/autoload_chapters",
+				 (GConfClientNotifyFunc) autoload_chapters_changed_cb,
+				 totem, NULL, NULL);
+
 	/* Visuals list */
 	list = bacon_video_widget_get_visuals_list (totem->bvw);
 	menu = gtk_menu_new ();



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