2
* Copyright (C) 2010 Alexander Saprykin <xelfium@gmail.com>
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
* The Totem project hereby grant permission for non-gpl compatible GStreamer
20
* plugins to be used and distributed together with GStreamer and Totem. This
21
* permission are above and beyond the permissions granted by the GPL license
22
* Totem is covered by.
28
#include <glib-object.h>
29
#include <glib/gi18n-lib.h>
30
#include <glib/gprintf.h>
33
#include <gdk-pixbuf/gdk-pixdata.h>
34
#include <gdk/gdkkeysyms.h>
38
#include "backend/bacon-video-widget.h"
39
#include "totem-plugin.h"
40
#include "totem-dirs.h"
41
#include "totem-interface.h"
43
#include "totem-cmml-parser.h"
44
#include "totem-chapters-utils.h"
45
#include "totem-edit-chapter.h"
47
#define TOTEM_TYPE_CHAPTERS_PLUGIN (totem_chapters_plugin_get_type ())
48
#define TOTEM_CHAPTERS_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPlugin))
49
#define TOTEM_CHAPTERS_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPluginClass))
50
#define TOTEM_IS_CHAPTERS_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_CHAPTERS_PLUGIN))
51
#define TOTEM_IS_CHAPTERS_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_CHAPTERS_PLUGIN))
52
#define TOTEM_CHAPTERS_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPluginClass))
54
#define TOTEM_CHAPTERS_PLUGIN_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPluginPrivate))
56
#define CHAPTER_TOOLTIP(title, start) g_strdup_printf ( _("<b>Title: </b>%s\n<b>Start time: </b>%s"), \
57
( title ), ( start ) )
59
#define CHAPTER_TITLE(title, start) g_strdup_printf ("<big>%s</big>\n" \
60
"<small><span foreground='grey'>%s" \
62
( title ), ( start ) )
63
#define ICON_SCALE_RATIO 2
67
TotemEditChapter *edit_chapter;
69
GtkWidget *add_button,
77
GtkActionGroup *action_group;
78
GtkUIManager *ui_manager;
80
GdkPixbuf *last_frame;
84
GCancellable *cancellable[2];
86
guint autoload_handle_id;
87
} TotemChaptersPluginPrivate;
89
TOTEM_PLUGIN_REGISTER (TOTEM_TYPE_CHAPTERS_PLUGIN, TotemChaptersPlugin, totem_chapters_plugin)
92
CHAPTERS_PIXBUF_COLUMN = 0,
93
CHAPTERS_TITLE_COLUMN,
94
CHAPTERS_TOOLTIP_COLUMN,
95
CHAPTERS_TITLE_PRIV_COLUMN,
96
CHAPTERS_TIME_PRIV_COLUMN,
100
static void totem_file_opened_async_cb (TotemObject *totem, const gchar *uri, TotemChaptersPlugin *plugin);
101
static void totem_file_opened_result_cb (gpointer data, gpointer user_data);
102
static void totem_file_closed_cb (TotemObject *totem, TotemChaptersPlugin *plugin);
103
static void add_chapter_to_the_list (gpointer data, gpointer user_data);
104
static void add_chapter_to_the_list_new (TotemChaptersPlugin *plugin, const gchar *title, gint64 time);
105
static gboolean check_available_time (TotemChaptersPlugin *plugin, gint64 time);
106
static GdkPixbuf * get_chapter_pixbuf (GdkPixbuf *src);
107
static void chapter_edit_dialog_response_cb (GtkDialog *dialog, gint response, TotemChaptersPlugin *plugin);
108
static void prepare_chapter_edit (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path, gpointer user_data);
109
static void finish_chapter_edit (GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data);
110
static void chapter_selection_changed_cb (GtkTreeSelection *tree_selection, TotemChaptersPlugin *plugin);
111
static void show_chapter_edit_dialog (TotemChaptersPlugin *plugin);
112
static void save_chapters_result_cb (gpointer data, gpointer user_data);
113
static GList * get_chapters_list (TotemChaptersPlugin *plugin);
114
static gboolean show_popup_menu (TotemChaptersPlugin *plugin, GdkEventButton *event);
115
static void autoload_changed_cb (GSettings *settigs, const gchar *key, TotemChaptersPlugin *plugin);
116
static void load_chapters_from_file (const gchar *uri, gboolean from_dialog, TotemChaptersPlugin *plugin);
117
static void set_no_data_visible (gboolean visible, gboolean show_buttons, TotemChaptersPlugin *plugin);
119
/* GtkBuilder callbacks */
120
void add_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
121
void remove_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
122
void save_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
123
void goto_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
124
void tree_view_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, TotemChaptersPlugin *plugin);
125
gboolean tree_view_button_press_cb (GtkTreeView *tree_view, GdkEventButton *event, TotemChaptersPlugin *plugin);
126
gboolean tree_view_key_press_cb (GtkTreeView *tree_view, GdkEventKey *event, TotemChaptersPlugin *plugin);
127
gboolean tree_view_popup_menu_cb (GtkTreeView *tree_view, TotemChaptersPlugin *plugin);
128
void popup_remove_action_cb (GtkAction *action, TotemChaptersPlugin *plugin);
129
void popup_goto_action_cb (GtkAction *action, TotemChaptersPlugin *plugin);
130
void load_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
131
void continue_button_clicked_cb (GtkButton *button, TotemChaptersPlugin *plugin);
134
get_chapter_pixbuf (GdkPixbuf *src)
138
gfloat pix_width, pix_height;
139
gfloat ratio, new_width;
141
gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &width, &height);
142
height *= ICON_SCALE_RATIO;
145
pix_width = (gfloat) gdk_pixbuf_get_width (src);
146
pix_height = (gfloat) gdk_pixbuf_get_height (src);
148
/* calc height ratio and apply it to width */
149
ratio = pix_height / height;
150
new_width = pix_width / ratio;
151
width = ceil (new_width);
153
/* scale video frame if need */
154
pixbuf = gdk_pixbuf_scale_simple (src, width, height, GDK_INTERP_BILINEAR);
156
/* 16:10 aspect ratio by default */
157
new_width = (gfloat) height * 1.6;
158
width = ceil (new_width);
160
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height);
161
gdk_pixbuf_fill (pixbuf, 0x00000000);
168
add_chapter_to_the_list (gpointer data,
171
TotemChaptersPlugin *plugin;
176
gchar *text, *start, *tip;
178
g_return_if_fail (data != NULL);
179
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (user_data));
181
plugin = TOTEM_CHAPTERS_PLUGIN (user_data);
182
store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree)));
183
clip = ((TotemCmmlClip *) data);
185
/* prepare tooltip data */
186
start = totem_cmml_convert_msecs_to_str (clip->time_start);
187
tip = CHAPTER_TOOLTIP (clip->title, start);
189
/* append clip to the sidebar list */
190
gtk_tree_store_append (store, &iter, NULL);
191
text = CHAPTER_TITLE (clip->title, start);
193
if (G_LIKELY (clip->pixbuf != NULL))
194
pixbuf = g_object_ref (clip->pixbuf);
196
pixbuf = get_chapter_pixbuf (NULL);
198
gtk_tree_store_set (store, &iter,
199
CHAPTERS_TITLE_COLUMN, text,
200
CHAPTERS_TOOLTIP_COLUMN, tip,
201
CHAPTERS_PIXBUF_COLUMN, pixbuf,
202
CHAPTERS_TITLE_PRIV_COLUMN, clip->title,
203
CHAPTERS_TIME_PRIV_COLUMN, clip->time_start,
206
g_object_unref (pixbuf);
213
add_chapter_to_the_list_new (TotemChaptersPlugin *plugin,
218
GtkTreeIter iter, cur_iter, res_iter;
220
gchar *text, *start, *tip;
222
gint64 cur_time, prev_time = 0;
225
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
226
g_return_if_fail (title != NULL);
227
g_return_if_fail (_time >= 0);
229
store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
230
valid = gtk_tree_model_get_iter_first (store, &cur_iter);
233
gtk_tree_model_get (store, &cur_iter,
234
CHAPTERS_TIME_PRIV_COLUMN, &cur_time,
237
if (_time < cur_time && _time > prev_time)
242
prev_time = cur_time;
244
valid = gtk_tree_model_iter_next (store, &cur_iter);
247
/* prepare tooltip data */
248
start = totem_cmml_convert_msecs_to_str (_time);
249
tip = CHAPTER_TOOLTIP (title, start);
251
/* insert clip into the sidebar list at proper position */
253
gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter, NULL, &res_iter);
255
gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter, NULL, NULL);
257
text = CHAPTER_TITLE (title, start);
258
pixbuf = get_chapter_pixbuf (plugin->priv->last_frame);
260
gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
261
CHAPTERS_TITLE_COLUMN, text,
262
CHAPTERS_TOOLTIP_COLUMN, tip,
263
CHAPTERS_PIXBUF_COLUMN, pixbuf,
264
CHAPTERS_TITLE_PRIV_COLUMN, title,
265
CHAPTERS_TIME_PRIV_COLUMN, _time,
268
g_object_unref (pixbuf);
275
check_available_time (TotemChaptersPlugin *plugin,
283
g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
285
store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
287
valid = gtk_tree_model_get_iter_first (store, &iter);
289
gtk_tree_model_get (store, &iter,
290
CHAPTERS_TIME_PRIV_COLUMN, &cur_time,
293
if (cur_time == _time)
296
if (cur_time > _time)
299
valid = gtk_tree_model_iter_next (store, &iter);
306
totem_file_opened_result_cb (gpointer data,
309
TotemCmmlAsyncData *adata;
310
TotemChaptersPlugin *plugin;
312
g_return_if_fail (data != NULL);
314
adata = (TotemCmmlAsyncData *) data;
315
plugin = TOTEM_CHAPTERS_PLUGIN (adata->user_data);
317
if (G_UNLIKELY (!adata->successful)) {
318
if (G_UNLIKELY (g_cancellable_is_cancelled (adata->cancellable))) {
319
/* if operation was cancelled due to plugin deactivation only clean up */
320
g_object_unref (adata->cancellable);
321
g_free (adata->file);
322
g_free (adata->error);
323
g_list_foreach (adata->list, (GFunc) totem_cmml_clip_free, NULL);
324
g_list_free (adata->list);
328
totem_action_error (plugin->priv->totem, _("Error while reading file with chapters"),
332
if (adata->is_exists && adata->from_dialog) {
333
g_free (plugin->priv->cmml_mrl);
334
plugin->priv->cmml_mrl = g_strdup (adata->file);
337
g_list_foreach (adata->list, (GFunc) add_chapter_to_the_list, plugin);
338
g_list_foreach (adata->list, (GFunc) totem_cmml_clip_free, NULL);
339
g_list_free (adata->list);
341
/* do not show tree if read operation failed */
342
set_no_data_visible (!adata->successful || !adata->is_exists, TRUE, plugin);
344
g_object_unref (adata->cancellable);
345
g_free (adata->file);
346
g_free (adata->error);
351
totem_file_opened_async_cb (TotemObject *totem,
353
TotemChaptersPlugin *plugin)
357
g_return_if_fail (TOTEM_IS_OBJECT (totem));
358
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
359
g_return_if_fail (uri != NULL);
361
if (g_str_has_prefix (uri, "http") != FALSE)
364
cmml_file = totem_change_file_extension (uri, "cmml");
365
/* if file has no extension - append it */
366
if (cmml_file == NULL)
367
cmml_file = g_strconcat (uri, ".cmml", NULL);
369
plugin->priv->cmml_mrl = cmml_file;
371
if (!plugin->priv->autoload)
372
set_no_data_visible (TRUE, TRUE, plugin);
374
load_chapters_from_file (cmml_file, FALSE, plugin);
378
totem_file_closed_cb (TotemObject *totem,
379
TotemChaptersPlugin *plugin)
383
g_return_if_fail (TOTEM_IS_OBJECT (totem) && TOTEM_IS_CHAPTERS_PLUGIN (plugin));
385
store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree)));
387
gtk_tree_store_clear (store);
389
if (G_UNLIKELY (plugin->priv->edit_chapter != NULL))
390
gtk_widget_destroy (GTK_WIDGET (plugin->priv->edit_chapter));
392
if (G_UNLIKELY (plugin->priv->last_frame != NULL))
393
g_object_unref (G_OBJECT (plugin->priv->last_frame));
395
g_free (plugin->priv->cmml_mrl);
396
plugin->priv->cmml_mrl = NULL;
398
gtk_widget_set_sensitive (plugin->priv->remove_button, FALSE);
399
gtk_widget_set_sensitive (plugin->priv->save_button, FALSE);
401
set_no_data_visible (TRUE, FALSE, plugin);
405
chapter_edit_dialog_response_cb (GtkDialog *dialog,
407
TotemChaptersPlugin *plugin)
411
g_return_if_fail (TOTEM_IS_EDIT_CHAPTER (dialog));
412
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
414
if (response != GTK_RESPONSE_OK) {
415
gtk_widget_destroy (GTK_WIDGET (plugin->priv->edit_chapter));
417
if (plugin->priv->last_frame != NULL)
418
g_object_unref (G_OBJECT (plugin->priv->last_frame));
420
if (plugin->priv->was_played)
421
totem_action_play (plugin->priv->totem);
425
gtk_widget_hide (GTK_WIDGET (dialog));
427
title = totem_edit_chapter_get_title (TOTEM_EDIT_CHAPTER (dialog));
428
add_chapter_to_the_list_new (plugin, title, plugin->priv->last_time);
430
gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
432
if (G_LIKELY (plugin->priv->last_frame != NULL))
433
g_object_unref (G_OBJECT (plugin->priv->last_frame));
436
gtk_widget_destroy (GTK_WIDGET (plugin->priv->edit_chapter));
438
if (plugin->priv->was_played)
439
totem_action_play (plugin->priv->totem);
443
prepare_chapter_edit (GtkCellRenderer *renderer,
444
GtkCellEditable *editable,
448
TotemChaptersPlugin *plugin;
454
g_return_if_fail (GTK_IS_ENTRY (editable));
455
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (user_data));
456
g_return_if_fail (path != NULL);
458
plugin = TOTEM_CHAPTERS_PLUGIN (user_data);
459
entry = GTK_ENTRY (editable);
460
store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
462
if (G_UNLIKELY (!gtk_tree_model_get_iter_from_string (store, &iter, path)))
465
gtk_tree_model_get (store, &iter, CHAPTERS_TITLE_PRIV_COLUMN, &title, -1);
466
gtk_entry_set_text (entry, title);
473
finish_chapter_edit (GtkCellRendererText *renderer,
478
TotemChaptersPlugin *plugin;
481
gchar *time_str, *tip, *new_title, *old_title;
484
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (user_data));
485
g_return_if_fail (new_text != NULL);
486
g_return_if_fail (path != NULL);
488
plugin = TOTEM_CHAPTERS_PLUGIN (user_data);
489
store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
491
if (G_UNLIKELY (!gtk_tree_model_get_iter_from_string (store, &iter, path)))
494
gtk_tree_model_get (store, &iter,
495
CHAPTERS_TIME_PRIV_COLUMN, &_time,
496
CHAPTERS_TITLE_PRIV_COLUMN, &old_title,
499
if (g_strcmp0 (old_title, new_text) == 0) {
504
time_str = totem_cmml_convert_msecs_to_str (_time);
505
new_title = CHAPTER_TITLE (new_text, time_str);
506
tip = CHAPTER_TOOLTIP (new_text, time_str);
508
gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
509
CHAPTERS_TITLE_COLUMN, new_title,
510
CHAPTERS_TOOLTIP_COLUMN, tip,
511
CHAPTERS_TITLE_PRIV_COLUMN, new_text,
514
gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
523
show_chapter_edit_dialog (TotemChaptersPlugin *plugin)
525
GtkWindow *main_window;
526
BaconVideoWidget *bvw;
529
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
531
if (G_UNLIKELY (plugin->priv->edit_chapter != NULL)) {
532
gtk_window_present (GTK_WINDOW (plugin->priv->edit_chapter));
536
main_window = totem_get_main_window (plugin->priv->totem);
537
plugin->priv->was_played = totem_is_playing (plugin->priv->totem);
538
totem_action_pause (plugin->priv->totem);
540
/* adding a new one, check if it's time available */
541
g_object_get (G_OBJECT (plugin->priv->totem), "current-time", &_time, NULL);
542
if (G_UNLIKELY (!check_available_time (plugin, _time))) {
543
totem_interface_error_blocking (_("Chapter with the same time already exists"),
544
_("Try another name or remove an existing chapter."),
546
g_object_unref (main_window);
547
if (plugin->priv->was_played)
548
totem_action_play (plugin->priv->totem);
551
plugin->priv->last_time = _time;
554
bvw = BACON_VIDEO_WIDGET (totem_get_video_widget (plugin->priv->totem));
555
plugin->priv->last_frame = bacon_video_widget_get_current_frame (bvw);
556
g_object_add_weak_pointer (G_OBJECT (plugin->priv->last_frame), (gpointer *) &plugin->priv->last_frame);
557
g_object_unref (bvw);
559
/* create chapter-edit dialog */
560
plugin->priv->edit_chapter = TOTEM_EDIT_CHAPTER (totem_edit_chapter_new ());
561
g_object_add_weak_pointer (G_OBJECT (plugin->priv->edit_chapter), (gpointer *) &(plugin->priv->edit_chapter));
563
g_signal_connect (G_OBJECT (plugin->priv->edit_chapter), "delete-event",
564
G_CALLBACK (gtk_widget_destroy), NULL);
565
g_signal_connect (G_OBJECT (plugin->priv->edit_chapter), "response",
566
G_CALLBACK (chapter_edit_dialog_response_cb), plugin);
568
gtk_window_set_transient_for (GTK_WINDOW (plugin->priv->edit_chapter),
570
gtk_widget_show (GTK_WIDGET (plugin->priv->edit_chapter));
572
g_object_unref (main_window);
576
chapter_selection_changed_cb (GtkTreeSelection *tree_selection,
577
TotemChaptersPlugin *plugin)
580
gboolean allow_remove, allow_goto;
582
g_return_if_fail (GTK_IS_TREE_SELECTION (tree_selection));
583
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
585
count = gtk_tree_selection_count_selected_rows (tree_selection);
586
allow_remove = (count > 0);
587
allow_goto = (count == 1);
589
gtk_widget_set_sensitive (plugin->priv->remove_button, allow_remove);
590
gtk_widget_set_sensitive (plugin->priv->goto_button, allow_goto);
594
autoload_changed_cb (GSettings *settings,
596
TotemChaptersPlugin *plugin)
598
g_return_if_fail (G_IS_SETTINGS (settings));
599
g_return_if_fail (key != NULL);
600
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
602
plugin->priv->autoload = g_settings_get_boolean (settings, key);
606
load_chapters_from_file (const gchar *uri,
607
gboolean from_dialog,
608
TotemChaptersPlugin *plugin)
610
TotemCmmlAsyncData *data;
612
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
614
if (plugin->priv->cancellable[0] != NULL) {
615
g_cancellable_cancel (plugin->priv->cancellable[0]);
616
g_object_unref (plugin->priv->cancellable[0]);
619
data = g_new0 (TotemCmmlAsyncData, 1);
620
/* do not forget to save this pointer in the result function */
621
data->file = g_strdup (uri);
622
data->final = totem_file_opened_result_cb;
623
data->user_data = (gpointer) plugin;
625
data->from_dialog = TRUE;
626
/* cancellable object shouldn't be finalized during result func */
627
plugin->priv->cancellable[0] = g_cancellable_new ();
628
g_object_add_weak_pointer (G_OBJECT (plugin->priv->cancellable[0]),
629
(gpointer *) &(plugin->priv->cancellable[0]));
630
data->cancellable = plugin->priv->cancellable[0];
632
if (G_UNLIKELY (totem_cmml_read_file_async (data) < 0)) {
633
g_warning ("chapters: wrong parameters for reading CMML file, may be a bug");
635
set_no_data_visible (TRUE, TRUE, plugin);
637
g_object_unref (plugin->priv->cancellable[0]);
643
set_no_data_visible (gboolean visible,
644
gboolean show_buttons,
645
TotemChaptersPlugin *plugin)
647
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
650
gtk_widget_hide (plugin->priv->list_box);
651
gtk_widget_show (plugin->priv->load_box);
653
gtk_widget_hide (plugin->priv->load_box);
654
gtk_widget_show (plugin->priv->list_box);
657
gtk_widget_set_sensitive (plugin->priv->add_button, !visible);
658
gtk_widget_set_sensitive (plugin->priv->tree, !visible);
660
gtk_widget_set_visible (plugin->priv->load_button, show_buttons);
661
gtk_widget_set_visible (plugin->priv->continue_button, show_buttons);
665
add_button_clicked_cb (GtkButton *button,
666
TotemChaptersPlugin *plugin)
668
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
670
show_chapter_edit_dialog (plugin);
674
remove_button_clicked_cb (GtkButton *button,
675
TotemChaptersPlugin *plugin)
677
GtkTreeSelection *selection;
682
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
684
store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
685
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (plugin->priv->tree));
686
list = gtk_tree_selection_get_selected_rows (selection, NULL);
688
g_return_if_fail (g_list_length (list) != 0);
690
list = g_list_last (list);
691
while (list != NULL) {
692
gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, (GtkTreePath *) list->data);
693
gtk_tree_store_remove (GTK_TREE_STORE (store), &iter);
698
gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
700
g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
705
save_chapters_result_cb (gpointer data,
708
TotemCmmlAsyncData *adata;
709
TotemChaptersPlugin *plugin;
711
g_return_if_fail (data != NULL);
713
adata = (TotemCmmlAsyncData *) data;
714
plugin = TOTEM_CHAPTERS_PLUGIN (adata->user_data);
716
if (G_UNLIKELY (!adata->successful && !g_cancellable_is_cancelled (adata->cancellable))) {
717
totem_action_error (plugin->priv->totem, _("Error while writing file with chapters"),
719
gtk_widget_set_sensitive (plugin->priv->save_button, TRUE);
722
g_object_unref (adata->cancellable);
723
g_list_foreach (adata->list, (GFunc) totem_cmml_clip_free, NULL);
724
g_list_free (adata->list);
725
g_free (adata->error);
730
get_chapters_list (TotemChaptersPlugin *plugin)
741
g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), NULL);
743
store = gtk_tree_view_get_model (GTK_TREE_VIEW (plugin->priv->tree));
745
valid = gtk_tree_model_get_iter_first (store, &iter);
747
gtk_tree_model_get (store, &iter,
748
CHAPTERS_TITLE_PRIV_COLUMN, &title,
749
CHAPTERS_TIME_PRIV_COLUMN, &_time,
750
CHAPTERS_PIXBUF_COLUMN, &pixbuf,
752
clip = totem_cmml_clip_new (title, NULL, _time, pixbuf);
753
list = g_list_prepend (list, clip);
756
g_object_unref (pixbuf);
758
valid = gtk_tree_model_iter_next (store, &iter);
760
list = g_list_reverse (list);
766
show_popup_menu (TotemChaptersPlugin *plugin,
767
GdkEventButton *event)
774
GtkAction *remove_act, *goto_act;
775
GtkTreeSelection *selection;
777
g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
779
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (plugin->priv->tree));
782
button = event->button;
785
if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (plugin->priv->tree),
786
event->x, event->y, &path, NULL, NULL, NULL)) {
787
if (!gtk_tree_selection_path_is_selected (selection, path)) {
788
gtk_tree_selection_unselect_all (selection);
789
gtk_tree_selection_select_path (selection, path);
791
gtk_tree_path_free (path);
793
gtk_tree_selection_unselect_all (selection);
795
_time = gtk_get_current_event_time ();
797
count = gtk_tree_selection_count_selected_rows (selection);
802
remove_act = gtk_action_group_get_action (plugin->priv->action_group, "remove");
803
goto_act = gtk_action_group_get_action (plugin->priv->action_group, "goto");
804
gtk_action_set_sensitive (remove_act, count > 0);
805
gtk_action_set_sensitive (goto_act, count == 1);
807
menu = gtk_ui_manager_get_widget (plugin->priv->ui_manager, "/totem-chapters-popup");
809
gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
811
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
818
save_button_clicked_cb (GtkButton *button,
819
TotemChaptersPlugin *plugin)
821
TotemCmmlAsyncData *data;
823
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
825
if (plugin->priv->cancellable[1] != NULL) {
826
g_cancellable_cancel (plugin->priv->cancellable[1]);
827
g_object_unref (plugin->priv->cancellable[1]);
830
data = g_new0 (TotemCmmlAsyncData, 1);
831
data->file = plugin->priv->cmml_mrl;
832
data->list = get_chapters_list (plugin);
833
data->final = save_chapters_result_cb;
834
data->user_data = (gpointer) plugin;
835
/* cancellable object shouldn't be finalized during result func */
836
data->cancellable = g_cancellable_new ();
837
plugin->priv->cancellable[1] = data->cancellable;
838
g_object_add_weak_pointer (G_OBJECT (plugin->priv->cancellable[1]),
839
(gpointer *) &(plugin->priv->cancellable[1]));
841
if (G_UNLIKELY (totem_cmml_write_file_async (data) < 0)) {
842
totem_action_error (plugin->priv->totem, _("Error occurred while saving chapters"),
843
_("Please check you have permission to write to the folder containing the movie."));
846
gtk_widget_set_sensitive (plugin->priv->save_button, FALSE);
850
tree_view_row_activated_cb (GtkTreeView *tree_view,
852
GtkTreeViewColumn *column,
853
TotemChaptersPlugin *plugin)
860
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
861
g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
862
g_return_if_fail (path != NULL);
864
store = gtk_tree_view_get_model (tree_view);
865
seekable = totem_is_seekable (plugin->priv->totem);
867
g_warning ("chapters: unable to seek stream!");
871
gtk_tree_model_get_iter (store, &iter, path);
872
gtk_tree_model_get (store, &iter, CHAPTERS_TIME_PRIV_COLUMN, &_time, -1);
874
totem_action_seek_time (plugin->priv->totem, _time, TRUE);
878
tree_view_button_press_cb (GtkTreeView *tree_view,
879
GdkEventButton *event,
880
TotemChaptersPlugin *plugin)
882
g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
883
g_return_val_if_fail (event != NULL, FALSE);
885
if (event->type == GDK_BUTTON_PRESS && event->button == 3)
886
return show_popup_menu (plugin, event);
892
tree_view_key_press_cb (GtkTreeView *tree_view,
894
TotemChaptersPlugin *plugin)
896
GtkTreeSelection *selection;
898
g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
899
g_return_val_if_fail (event != NULL, FALSE);
901
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (plugin->priv->tree));
903
/* Special case some shortcuts */
904
if (event->state != 0) {
905
if ((event->state & GDK_CONTROL_MASK) &&
906
event->keyval == GDK_KEY_a) {
907
gtk_tree_selection_select_all (selection);
912
/* If we have modifiers, and either Ctrl, Mod1 (Alt), or any
913
* of Mod3 to Mod5 (Mod2 is num-lock...) are pressed, we
914
* let Gtk+ handle the key */
915
if (event->state != 0 &&
916
((event->state & GDK_CONTROL_MASK)
917
|| (event->state & GDK_MOD1_MASK)
918
|| (event->state & GDK_MOD3_MASK)
919
|| (event->state & GDK_MOD4_MASK)
920
|| (event->state & GDK_MOD5_MASK)))
923
if (event->keyval == GDK_KEY_Delete) {
924
if (gtk_tree_selection_count_selected_rows (selection) > 0)
925
remove_button_clicked_cb (GTK_BUTTON (plugin->priv->remove_button), plugin);
933
tree_view_popup_menu_cb (GtkTreeView *tree_view,
934
TotemChaptersPlugin *plugin)
936
g_return_val_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin), FALSE);
938
return show_popup_menu (plugin, NULL);
942
popup_remove_action_cb (GtkAction *action,
943
TotemChaptersPlugin *plugin)
945
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
947
remove_button_clicked_cb (GTK_BUTTON (plugin->priv->remove_button), plugin);
951
popup_goto_action_cb (GtkAction *action,
952
TotemChaptersPlugin *plugin)
954
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
956
goto_button_clicked_cb (GTK_BUTTON (plugin->priv->goto_button), plugin);
960
load_button_clicked_cb (GtkButton *button,
961
TotemChaptersPlugin *plugin)
963
GtkWindow *main_window;
966
GtkFileFilter *filter_supported, *filter_all;
967
gchar *filename, *mrl, *dir;
969
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
971
plugin->priv->was_played = totem_is_playing (plugin->priv->totem);
972
totem_action_pause (plugin->priv->totem);
974
mrl = totem_get_current_mrl (plugin->priv->totem);
975
main_window = totem_get_main_window (plugin->priv->totem);
976
dialog = gtk_file_chooser_dialog_new (_("Open Chapter File"), main_window, GTK_FILE_CHOOSER_ACTION_OPEN,
977
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
978
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
980
gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
982
cur = g_file_new_for_uri (mrl);
983
parent = g_file_get_parent (cur);
986
dir = g_file_get_uri (parent);
988
dir = g_strdup (G_DIR_SEPARATOR_S);
990
filter_supported = gtk_file_filter_new ();
991
filter_all = gtk_file_filter_new ();
993
gtk_file_filter_add_pattern (filter_supported, "*.cmml");
994
gtk_file_filter_add_pattern (filter_supported, "*.CMML");
995
gtk_file_filter_set_name (filter_supported, _("Supported files"));
997
gtk_file_filter_add_pattern (filter_all, "*");
998
gtk_file_filter_set_name (filter_all, _("All files"));
1000
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter_supported);
1001
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filter_all);
1003
gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog), dir);
1005
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
1006
filename = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
1008
load_chapters_from_file (filename, TRUE, plugin);
1013
if (plugin->priv->was_played)
1014
totem_action_play (plugin->priv->totem);
1016
gtk_widget_destroy (dialog);
1017
g_object_unref (main_window);
1018
g_object_unref (cur);
1019
g_object_unref (parent);
1025
continue_button_clicked_cb (GtkButton *button,
1026
TotemChaptersPlugin *plugin)
1028
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
1030
set_no_data_visible (FALSE, FALSE, plugin);
1034
goto_button_clicked_cb (GtkButton *button,
1035
TotemChaptersPlugin *plugin)
1038
GtkTreeModel *store;
1039
GtkTreeSelection *selection;
1042
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
1044
tree = GTK_TREE_VIEW (plugin->priv->tree);
1045
store = gtk_tree_view_get_model (tree);
1046
selection = gtk_tree_view_get_selection (tree);
1048
list = gtk_tree_selection_get_selected_rows (selection, &store);
1050
tree_view_row_activated_cb (tree, (GtkTreePath *) list->data, NULL, plugin);
1052
g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
1057
impl_activate (PeasActivatable *plugin)
1060
GtkWindow *main_window;
1061
GtkBuilder *builder;
1062
GtkWidget *main_box;
1063
GtkTreeSelection *selection;
1064
TotemChaptersPlugin *cplugin;
1065
GtkCellRenderer *renderer;
1066
GtkTreeViewColumn *column;
1069
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
1071
cplugin = TOTEM_CHAPTERS_PLUGIN (plugin);
1072
totem = g_object_get_data (G_OBJECT (plugin), "object");
1073
main_window = totem_get_main_window (totem);
1075
builder = totem_plugin_load_interface ("chapters", "chapters-list.ui", TRUE,
1076
main_window, cplugin);
1077
g_object_unref (main_window);
1079
if (builder == NULL)
1082
cplugin->priv->settings = g_settings_new (TOTEM_GSETTINGS_SCHEMA);
1083
cplugin->priv->autoload = g_settings_get_boolean (cplugin->priv->settings, "autoload-chapters");
1084
g_signal_connect (cplugin->priv->settings, "changed::autoload-chapters", (GCallback) autoload_changed_cb, cplugin);
1086
cplugin->priv->tree = GTK_WIDGET (gtk_builder_get_object (builder, "chapters_tree_view"));
1087
cplugin->priv->action_group = GTK_ACTION_GROUP (gtk_builder_get_object (builder, "chapters-action-group"));
1088
g_object_ref (cplugin->priv->action_group);
1089
cplugin->priv->ui_manager = GTK_UI_MANAGER (gtk_builder_get_object (builder, "totem-chapters-ui-manager"));
1090
g_object_ref (cplugin->priv->ui_manager);
1092
renderer = gtk_cell_renderer_pixbuf_new ();
1093
column = gtk_tree_view_column_new_with_attributes (_("Chapter Screenshot"), renderer, "pixbuf", CHAPTERS_PIXBUF_COLUMN, NULL);
1094
gtk_tree_view_append_column (GTK_TREE_VIEW (cplugin->priv->tree), column);
1096
renderer = gtk_cell_renderer_text_new ();
1098
g_object_set (renderer, "editable", TRUE, NULL);
1099
g_signal_connect (G_OBJECT (renderer), "editing-started",
1100
G_CALLBACK (prepare_chapter_edit), cplugin);
1101
g_signal_connect (G_OBJECT (renderer), "edited",
1102
G_CALLBACK (finish_chapter_edit), cplugin);
1104
column = gtk_tree_view_column_new_with_attributes (_("Chapter Title"), renderer,
1105
"markup", CHAPTERS_TITLE_COLUMN, NULL);
1106
gtk_tree_view_append_column (GTK_TREE_VIEW (cplugin->priv->tree), column);
1108
cplugin->priv->totem = g_object_ref (totem);
1109
/* for read operation */
1110
cplugin->priv->cancellable[0] = NULL;
1111
/* for write operation */
1112
cplugin->priv->cancellable[1] = NULL;
1113
cplugin->priv->edit_chapter = NULL;
1114
cplugin->priv->last_frame = NULL;
1115
cplugin->priv->cmml_mrl = NULL;
1116
cplugin->priv->last_time = 0;
1118
cplugin->priv->add_button = GTK_WIDGET (gtk_builder_get_object (builder, "add_button"));
1119
cplugin->priv->remove_button = GTK_WIDGET (gtk_builder_get_object (builder, "remove_button"));
1120
cplugin->priv->save_button = GTK_WIDGET (gtk_builder_get_object (builder, "save_button"));
1121
cplugin->priv->goto_button = GTK_WIDGET (gtk_builder_get_object (builder, "goto_button"));
1122
cplugin->priv->load_button = GTK_WIDGET (gtk_builder_get_object (builder, "load_button"));
1123
cplugin->priv->continue_button = GTK_WIDGET (gtk_builder_get_object (builder, "continue_button"));
1125
gtk_widget_hide (cplugin->priv->load_button);
1126
gtk_widget_hide (cplugin->priv->continue_button);
1128
cplugin->priv->list_box = GTK_WIDGET (gtk_builder_get_object (builder, "main_vbox"));
1129
cplugin->priv->load_box = GTK_WIDGET (gtk_builder_get_object (builder, "load_vbox"));
1131
main_box = gtk_vbox_new (FALSE, 6);
1132
gtk_box_pack_start (GTK_BOX (main_box), cplugin->priv->list_box, TRUE, TRUE, 0);
1133
gtk_box_pack_start (GTK_BOX (main_box), cplugin->priv->load_box, TRUE, TRUE, 0);
1135
set_no_data_visible (TRUE, FALSE, cplugin);
1137
totem_add_sidebar_page (totem, "chapters", _("Chapters"), main_box);
1139
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (cplugin->priv->tree));
1140
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
1142
g_signal_connect (G_OBJECT (totem),
1144
G_CALLBACK (totem_file_opened_async_cb),
1146
g_signal_connect (G_OBJECT (totem),
1148
G_CALLBACK (totem_file_closed_cb),
1150
g_signal_connect (G_OBJECT (selection),
1152
G_CALLBACK (chapter_selection_changed_cb),
1155
mrl = totem_get_current_mrl (cplugin->priv->totem);
1157
totem_file_opened_async_cb (cplugin->priv->totem, mrl, cplugin);
1159
g_object_unref (builder);
1164
impl_deactivate (PeasActivatable *plugin)
1167
TotemChaptersPlugin *cplugin;
1169
g_return_if_fail (TOTEM_IS_CHAPTERS_PLUGIN (plugin));
1171
totem = g_object_get_data (G_OBJECT (plugin), "object");
1172
cplugin = TOTEM_CHAPTERS_PLUGIN (plugin);
1174
/* If there are unsaved changes to the chapter data, ask the user if they'd like to save them. */
1175
if (gtk_widget_get_sensitive (cplugin->priv->save_button) == TRUE) {
1177
GtkWindow *main_window;
1179
main_window = totem_object_get_main_window (totem);
1180
dialog = gtk_message_dialog_new (main_window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1181
GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1182
_("Save changes to chapter list before closing?"));
1183
g_object_unref (main_window);
1185
gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1186
/* Translators: close Totem without saving changes to the chapter list of the current movie. */
1187
_("Close without Saving"), GTK_RESPONSE_CLOSE,
1188
/* Translators: save changes to the chapter list of the current movie before closing Totem. */
1189
_("Save"), GTK_RESPONSE_OK,
1191
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1192
_("If you don't save, changes to the chapter list will be lost."));
1194
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
1195
/* Save the chapter list before closing */
1196
save_button_clicked_cb (GTK_BUTTON (cplugin->priv->save_button), cplugin);
1198
/* Prevent it being cancelled further down in this function */
1199
cplugin->priv->cancellable[1] = NULL;
1202
gtk_widget_destroy (dialog);
1205
/* FIXME: do not cancel async operation if any */
1207
g_signal_handlers_disconnect_by_func (G_OBJECT (totem),
1208
totem_file_opened_async_cb,
1210
g_signal_handlers_disconnect_by_func (G_OBJECT (totem),
1211
totem_file_closed_cb,
1213
if (cplugin->priv->settings != NULL)
1214
g_object_unref (cplugin->priv->settings);
1216
if (G_UNLIKELY (cplugin->priv->last_frame != NULL))
1217
g_object_unref (G_OBJECT (cplugin->priv->last_frame));
1219
if (G_UNLIKELY (cplugin->priv->edit_chapter != NULL))
1220
gtk_widget_destroy (GTK_WIDGET (cplugin->priv->edit_chapter));
1222
if (G_LIKELY (cplugin->priv->action_group != NULL))
1223
g_object_unref (cplugin->priv->action_group);
1225
if (G_LIKELY (cplugin->priv->ui_manager != NULL))
1226
g_object_unref (cplugin->priv->ui_manager);
1228
if (G_LIKELY (cplugin->priv->cancellable[0] != NULL))
1229
g_cancellable_cancel (cplugin->priv->cancellable[0]);
1231
if (G_LIKELY (cplugin->priv->cancellable[1] != NULL))
1232
g_cancellable_cancel (cplugin->priv->cancellable[1]);
1235
g_object_unref (cplugin->priv->totem);
1236
g_free (cplugin->priv->cmml_mrl);
1238
totem_remove_sidebar_page (totem, "chapters");