1 /*
2  * Copyright (c) 2017-2022 gnome-mpv
3  *
4  * This file is part of Celluloid.
5  *
6  * Celluloid is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Celluloid is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Celluloid.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <glib/gi18n.h>
21 #include <gdk/gdk.h>
22 
23 #ifdef GDK_WINDOWING_WAYLAND
24 #include <gdk/wayland/gdkwayland.h>
25 #endif
26 
27 #include "celluloid-view.h"
28 #include "celluloid-file-chooser.h"
29 #include "celluloid-open-location-dialog.h"
30 #include "celluloid-preferences-dialog.h"
31 #include "celluloid-shortcuts-window.h"
32 #include "celluloid-authors.h"
33 #include "celluloid-marshal.h"
34 #include "celluloid-menu.h"
35 #include "celluloid-common.h"
36 #include "celluloid-def.h"
37 
38 enum
39 {
40 	PROP_0,
41 	PROP_PLAYLIST_COUNT,
42 	PROP_PAUSE,
43 	PROP_IDLE_ACTIVE,
44 	PROP_VOLUME,
45 	PROP_VOLUME_MAX,
46 	PROP_DURATION,
47 	PROP_PLAYLIST_POS,
48 	PROP_TRACK_LIST,
49 	PROP_DISC_LIST,
50 	PROP_SKIP_ENABLED,
51 	PROP_LOOP,
52 	PROP_SHUFFLE,
53 	PROP_BORDER,
54 	PROP_FULLSCREEN,
55 	PROP_MEDIA_TITLE,
56 	PROP_DISPLAY_FPS,
57 	PROP_SEARCHING,
58 	N_PROPERTIES
59 };
60 
61 struct _CelluloidView
62 {
63 	CelluloidMainWindow parent;
64 	gboolean disposed;
65 	gboolean has_dialog;
66 
67 	/* Properties */
68 	gint playlist_count;
69 	gboolean pause;
70 	gboolean idle_active;
71 	gdouble volume;
72 	gdouble volume_max;
73 	gdouble duration;
74 	gint playlist_pos;
75 	GPtrArray *track_list;
76 	GPtrArray *disc_list;
77 	gboolean skip_enabled;
78 	gboolean control_box_enabled;
79 	gboolean loop;
80 	gboolean shuffle;
81 	gboolean border;
82 	gboolean fullscreen;
83 	gchar *media_title;
84 	gdouble display_fps;
85 	gboolean searching;
86 };
87 
88 struct _CelluloidViewClass
89 {
90 	CelluloidMainWindowClass parent_class;
91 };
92 
93 G_DEFINE_TYPE(CelluloidView, celluloid_view, CELLULOID_TYPE_MAIN_WINDOW)
94 
95 static void
96 constructed(GObject *object);
97 
98 static void
99 dispose(GObject *object);
100 
101 static void
102 set_property(	GObject *object,
103 		guint property_id,
104 		const GValue *value,
105 		GParamSpec *pspec );
106 
107 static void
108 get_property(	GObject *object,
109 		guint property_id,
110 		GValue *value,
111 		GParamSpec *pspec );
112 
113 static void
114 load_css(CelluloidView *view);
115 
116 static void
117 load_settings(CelluloidView *view);
118 
119 static void
120 save_playlist(CelluloidView *view, GFile *file, GError **error);
121 
122 static void
123 update_title(CelluloidView *view);
124 
125 static void
126 show_message_dialog(	CelluloidView *view,
127 			GtkMessageType type,
128 			const gchar *title,
129 			const gchar *prefix,
130 			const gchar *msg );
131 
132 static void
133 show_open_track_dialog(CelluloidView  *view, TrackType type);
134 
135 /* Dialog responses */
136 static void
137 message_dialog_response_handler(	GtkDialog *dialog,
138 					gint response_id,
139 					gpointer data);
140 
141 static void
142 open_dialog_response_handler(GtkDialog *dialog, gint response_id, gpointer data);
143 
144 static void
145 open_location_dialog_response_handler(	GtkDialog *dialog,
146 					gint response_id,
147 					gpointer data );
148 
149 static void
150 open_track_dialog_response_handler(	GtkDialog *dialog,
151 					gint response_id,
152 					gpointer data );
153 
154 static void
155 preferences_dialog_response_handler(	GtkDialog *dialog,
156 					gint response_id,
157 					gpointer data );
158 
159 static void
160 save_playlist_response_handler(	GtkDialog *dialog,
161 				gint response_id,
162 				gpointer data );
163 
164 static void
165 realize_handler(GtkWidget *widget, gpointer data);
166 
167 static void
168 resize_handler(	CelluloidVideoArea *video_area,
169 		gint width,
170 		gint height,
171 		gpointer data );
172 
173 static void
174 render_handler(CelluloidVideoArea *area, gpointer data);
175 
176 static gboolean
177 drop_handler(	GtkDropTargetAsync *self,
178 		GValue *value,
179 		gdouble x,
180 		gdouble y,
181 		gpointer data );
182 
183 static gboolean
184 notify_fullscreened_handler(GObject *gobject, GParamSpec *pspec, gpointer data);
185 
186 static gboolean
187 close_request_handler(GtkWidget *widget, gpointer data);
188 
189 static void
190 playlist_row_activated_handler(	CelluloidPlaylistWidget *playlist,
191 				gint64 pos,
192 				gpointer data );
193 
194 static void
195 playlist_row_inserted_handler(	CelluloidPlaylistWidget *widget,
196 				gint pos,
197 				gpointer data );
198 
199 static void
200 playlist_row_deleted_handler(	CelluloidPlaylistWidget *widget,
201 				gint pos,
202 				gpointer data );
203 
204 static void
205 playlist_row_reordered_handler(	CelluloidPlaylistWidget *widget,
206 				gint src,
207 				gint dest,
208 				gpointer data );
209 
210 static void
constructed(GObject * object)211 constructed(GObject *object)
212 {
213 	G_OBJECT_CLASS(celluloid_view_parent_class)->constructed(object);
214 
215 	CelluloidView *view =
216 		CELLULOID_VIEW(object);
217 	CelluloidMainWindow *wnd =
218 		CELLULOID_MAIN_WINDOW(view);
219 	CelluloidVideoArea *video_area =
220 		celluloid_main_window_get_video_area(wnd);
221 	CelluloidPlaylistWidget *playlist =
222 		celluloid_main_window_get_playlist(wnd);
223 	CelluloidControlBox *control_box =
224 		celluloid_main_window_get_control_box(wnd);
225 
226 	g_object_bind_property(	view, "duration",
227 				control_box, "duration",
228 				G_BINDING_DEFAULT );
229 	g_object_bind_property(	view, "pause",
230 				control_box, "pause",
231 				G_BINDING_DEFAULT );
232 	g_object_bind_property(	view, "skip-enabled",
233 				control_box, "skip-enabled",
234 				G_BINDING_DEFAULT );
235 	g_object_bind_property(	view, "loop",
236 				control_box, "loop",
237 				G_BINDING_BIDIRECTIONAL );
238 	g_object_bind_property(	view, "shuffle",
239 				control_box, "shuffle",
240 				G_BINDING_BIDIRECTIONAL );
241 	g_object_bind_property(	view, "volume",
242 				control_box, "volume",
243 				G_BINDING_BIDIRECTIONAL );
244 	g_object_bind_property(	view, "volume-max",
245 				control_box, "volume-max",
246 				G_BINDING_DEFAULT );
247 	g_object_bind_property(	playlist, "playlist-count",
248 				view, "playlist-count",
249 				G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE );
250 	g_object_bind_property(	playlist, "searching",
251 				view, "searching",
252 				G_BINDING_BIDIRECTIONAL );
253 
254 	celluloid_main_window_load_state(wnd);
255 	load_settings(view);
256 
257 	g_signal_connect(	video_area,
258 				"resize",
259 				G_CALLBACK(resize_handler),
260 				view );
261 	g_signal_connect(	video_area,
262 				"render",
263 				G_CALLBACK(render_handler),
264 				view );
265 
266 	GType types[] = {G_TYPE_STRING, GDK_TYPE_FILE_LIST};
267 
268 	GtkDropTarget *video_area_drop_target =
269 		gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY);
270 
271 	gtk_drop_target_set_gtypes
272 		(video_area_drop_target, types, G_N_ELEMENTS(types));
273 	gtk_widget_add_controller
274 		(	GTK_WIDGET(video_area),
275 			GTK_EVENT_CONTROLLER(video_area_drop_target) );
276 	g_signal_connect(	video_area_drop_target,
277 				"drop",
278 				G_CALLBACK(drop_handler),
279 				view );
280 
281 	GtkDropTarget *playlist_drop_target =
282 		gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY);
283 	gtk_drop_target_set_gtypes
284 		(playlist_drop_target, types, G_N_ELEMENTS(types));
285 	gtk_widget_add_controller
286 		(	GTK_WIDGET(playlist),
287 			GTK_EVENT_CONTROLLER(playlist_drop_target) );
288 	g_signal_connect(	playlist_drop_target,
289 				"drop",
290 				G_CALLBACK(drop_handler),
291 				view );
292 
293 	g_signal_connect(	wnd,
294 				"notify::fullscreened",
295 				G_CALLBACK(notify_fullscreened_handler),
296 				view );
297 	g_signal_connect(	wnd,
298 				"close-request",
299 				G_CALLBACK(close_request_handler),
300 				view );
301 	g_signal_connect(	playlist,
302 				"row-activated",
303 				G_CALLBACK(playlist_row_activated_handler),
304 				view );
305 	g_signal_connect(	playlist,
306 				"row-inserted",
307 				G_CALLBACK(playlist_row_inserted_handler),
308 				view );
309 	g_signal_connect(	playlist,
310 				"row-deleted",
311 				G_CALLBACK(playlist_row_deleted_handler),
312 				view );
313 	g_signal_connect(	playlist,
314 				"rows-reordered",
315 				G_CALLBACK(playlist_row_reordered_handler),
316 				view );
317 }
318 
319 static void
dispose(GObject * object)320 dispose(GObject *object)
321 {
322 	CelluloidView *view = CELLULOID_VIEW(object);
323 
324 	if(!view->disposed)
325 	{
326 		celluloid_main_window_save_state(CELLULOID_MAIN_WINDOW(object));
327 		view->disposed = TRUE;
328 	}
329 
330 	G_OBJECT_CLASS(celluloid_view_parent_class)->dispose(object);
331 }
332 
333 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)334 set_property(	GObject *object,
335 		guint property_id,
336 		const GValue *value,
337 		GParamSpec *pspec )
338 {
339 	CelluloidView *self =
340 		CELLULOID_VIEW(object);
341 	CelluloidMainWindow *wnd =
342 		CELLULOID_MAIN_WINDOW(self);
343 	CelluloidControlBox* control_box =
344 		celluloid_main_window_get_control_box(wnd);
345 
346 	switch(property_id)
347 	{
348 		case PROP_PLAYLIST_COUNT:
349 		self->playlist_count = g_value_get_int(value);
350 
351 		update_title(self);
352 
353 		g_object_set(	control_box,
354 				"enabled",
355 				self->playlist_count > 0,
356 				NULL );
357 
358 		if(self->playlist_count <= 0)
359 		{
360 			celluloid_view_reset(self);
361 		}
362 		break;
363 
364 		case PROP_PAUSE:
365 		self->pause = g_value_get_boolean(value);
366 		break;
367 
368 		case PROP_IDLE_ACTIVE:
369 		self->idle_active = g_value_get_boolean(value);
370 		update_title(self);
371 		break;
372 
373 		case PROP_VOLUME:
374 		self->volume = g_value_get_double(value);
375 		break;
376 
377 		case PROP_VOLUME_MAX:
378 		self->volume_max = g_value_get_double(value);
379 		break;
380 
381 		case PROP_DURATION:
382 		self->duration = g_value_get_double(value);
383 		break;
384 
385 		case PROP_PLAYLIST_POS:
386 		self->playlist_pos = g_value_get_int(value);
387 
388 		CelluloidPlaylistWidget *playlist =
389 			celluloid_main_window_get_playlist(wnd);
390 
391 		celluloid_playlist_widget_set_indicator_pos
392 			(playlist, self->playlist_pos);
393 		break;
394 
395 		case PROP_TRACK_LIST:
396 		self->track_list = g_value_get_pointer(value);
397 		celluloid_main_window_update_track_list(wnd, self->track_list);
398 		break;
399 
400 		case PROP_DISC_LIST:
401 		self->disc_list = g_value_get_pointer(value);
402 		celluloid_main_window_update_disc_list(wnd, self->disc_list);
403 		break;
404 
405 		case PROP_SKIP_ENABLED:
406 		self->skip_enabled = g_value_get_boolean(value);
407 		break;
408 
409 		case PROP_LOOP:
410 		self->loop = g_value_get_boolean(value);
411 		break;
412 
413 		case PROP_SHUFFLE:
414 		self->shuffle = g_value_get_boolean(value);
415 		break;
416 
417 		case PROP_BORDER:
418 		self->border = g_value_get_boolean(value);
419 		{
420 			GtkWidget *titlebar =
421 				gtk_window_get_titlebar(GTK_WINDOW(wnd));
422 
423 			if(titlebar)
424 			{
425 				gtk_widget_set_visible(titlebar, self->border);
426 			}
427 
428 			gtk_window_set_decorated(GTK_WINDOW(wnd), self->border);
429 		}
430 		break;
431 
432 		case PROP_FULLSCREEN:
433 		self->fullscreen = g_value_get_boolean(value);
434 		celluloid_main_window_set_fullscreen(wnd, self->fullscreen);
435 		break;
436 
437 		case PROP_MEDIA_TITLE:
438 		g_free(self->media_title);
439 		self->media_title = g_value_dup_string(value);
440 		update_title(self);
441 		break;
442 
443 		case PROP_DISPLAY_FPS:
444 		self->display_fps = g_value_get_double(value);
445 		break;
446 
447 		case PROP_SEARCHING:
448 		// We do not display playlist in fullscreen mode so refuse to
449 		// enter search mode here if we're in fullscreen mode.
450 		self->searching =	!self->fullscreen &&
451 					g_value_get_boolean(value);
452 
453 		// Show playlist if we're about to enter search mode and it
454 		// isn't already visible.
455 		if(self->searching && !celluloid_view_get_playlist_visible(self))
456 		{
457 			celluloid_view_set_playlist_visible(self, TRUE);
458 		}
459 		break;
460 
461 		default:
462 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
463 		break;
464 	}
465 }
466 
467 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)468 get_property(	GObject *object,
469 		guint property_id,
470 		GValue *value,
471 		GParamSpec *pspec )
472 {
473 	CelluloidView *self = CELLULOID_VIEW(object);
474 
475 	switch(property_id)
476 	{
477 		case PROP_PLAYLIST_COUNT:
478 		g_value_set_int(value, self->playlist_count);
479 		break;
480 
481 		case PROP_PAUSE:
482 		g_value_set_boolean(value, self->pause);
483 		break;
484 
485 		case PROP_IDLE_ACTIVE:
486 		g_value_set_boolean(value, self->idle_active);
487 		break;
488 
489 		case PROP_VOLUME:
490 		g_value_set_double(value, self->volume);
491 		break;
492 
493 		case PROP_VOLUME_MAX:
494 		g_value_set_double(value, self->volume_max);
495 		break;
496 
497 		case PROP_DURATION:
498 		g_value_set_double(value, self->duration);
499 		break;
500 
501 		case PROP_PLAYLIST_POS:
502 		g_value_set_int(value, self->playlist_pos);
503 		break;
504 
505 		case PROP_TRACK_LIST:
506 		g_value_set_pointer(value, self->track_list);
507 		break;
508 
509 		case PROP_DISC_LIST:
510 		g_value_set_pointer(value, self->disc_list);
511 		break;
512 
513 		case PROP_SKIP_ENABLED:
514 		g_value_set_boolean(value, self->skip_enabled);
515 		break;
516 
517 		case PROP_LOOP:
518 		g_value_set_boolean(value, self->loop);
519 		break;
520 
521 		case PROP_SHUFFLE:
522 		g_value_set_boolean(value, self->shuffle);
523 		break;
524 
525 		case PROP_BORDER:
526 		g_value_set_boolean(value, self->border);
527 		break;
528 
529 		case PROP_FULLSCREEN:
530 		g_value_set_boolean(value, self->fullscreen);
531 		break;
532 
533 		case PROP_MEDIA_TITLE:
534 		g_value_set_static_string(value, self->media_title);
535 		break;
536 
537 		case PROP_DISPLAY_FPS:
538 		g_value_set_double(value, self->display_fps);
539 		break;
540 
541 		case PROP_SEARCHING:
542 		g_value_set_boolean(value, self->searching);
543 		break;
544 
545 		default:
546 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
547 		break;
548 	}
549 }
550 
551 static void
load_css(CelluloidView * view)552 load_css(CelluloidView *view)
553 {
554 	const gchar *style =
555 		"celluloid-video-area { background-color: black; }"
556 		".dialog-action-area { margin: -12px 12px 12px 0px; }"
557 		".dialog-action-area > button { margin-left: 12px; }";
558 	GtkCssProvider *style_provider =
559 		gtk_css_provider_new();
560 
561 	gtk_css_provider_load_from_data(style_provider, style, -1);
562 
563 	GdkSurface *surface = gtk_widget_get_surface(view);
564 
565 	if(surface)
566 	{
567 		GdkDisplay *display = gdk_surface_get_display(surface);
568 
569 		gtk_style_context_add_provider_for_display
570 			(	display,
571 				GTK_STYLE_PROVIDER(style_provider),
572 				GTK_STYLE_PROVIDER_PRIORITY_APPLICATION );
573 
574 		g_object_unref(style_provider);
575 	}
576 	else
577 	{
578 		g_warning("Failed to load CSS: Surface not available");
579 	}
580 }
581 
582 static void
load_settings(CelluloidView * view)583 load_settings(CelluloidView *view)
584 {
585 	GSettings *settings =
586 		g_settings_new(CONFIG_ROOT);
587 	CelluloidMainWindow *wnd =
588 		CELLULOID_MAIN_WINDOW(view);
589 	CelluloidControlBox *control_box =
590 		celluloid_main_window_get_control_box(wnd);
591 
592 	gboolean csd_enable;
593 	gboolean dark_theme_enable;
594 
595 	csd_enable =	g_settings_get_boolean (settings, "csd-enable");
596 	dark_theme_enable =	g_settings_get_boolean
597 				(settings, "dark-theme-enable");
598 
599 	g_object_set(	control_box,
600 			"show-fullscreen-button", !csd_enable,
601 			"skip-enabled", FALSE,
602 			NULL );
603 	g_object_set(	gtk_settings_get_default(),
604 			"gtk-application-prefer-dark-theme", dark_theme_enable,
605 			NULL );
606 
607 	g_object_unref(settings);
608 }
609 
610 static void
save_playlist(CelluloidView * view,GFile * file,GError ** error)611 save_playlist(CelluloidView *view, GFile *file, GError **error)
612 {
613 	CelluloidMainWindow *wnd = CELLULOID_MAIN_WINDOW(view);
614 	CelluloidPlaylistWidget *wgt = celluloid_main_window_get_playlist(wnd);
615 	GPtrArray *playlist = celluloid_playlist_widget_get_contents(wgt);
616 	gboolean rc = TRUE;
617 	GOutputStream *dest_stream = NULL;
618 
619 	if(file)
620 	{
621 		GFileOutputStream *file_stream;
622 
623 		file_stream = g_file_replace(	file,
624 						NULL,
625 						FALSE,
626 						G_FILE_CREATE_NONE,
627 						NULL,
628 						error );
629 		dest_stream = G_OUTPUT_STREAM(file_stream);
630 		rc = !!dest_stream;
631 	}
632 
633 	for(guint i = 0; rc && i < playlist->len; i++)
634 	{
635 		gsize written;
636 
637 		CelluloidPlaylistEntry *entry = g_ptr_array_index(playlist, i);
638 
639 		rc = g_output_stream_printf(	dest_stream,
640 						&written,
641 						NULL,
642 						NULL,
643 						"%s\n",
644 						entry->filename);
645 	}
646 
647 	if(dest_stream)
648 	{
649 		g_output_stream_close(dest_stream, NULL, error);
650 	}
651 
652 	g_ptr_array_free(playlist, TRUE);
653 }
654 
655 static void
update_title(CelluloidView * view)656 update_title(CelluloidView *view)
657 {
658 	const gboolean use_media_title =
659 		view->media_title &&
660 		!view->idle_active &&
661 		view->playlist_count > 0;
662 	const gchar *title_source =
663 		use_media_title ?
664 		view->media_title :
665 		g_get_application_name();
666 	gchar *title =
667 		sanitize_utf8(title_source, TRUE);
668 
669 	gtk_window_set_title(GTK_WINDOW(view), title);
670 	g_free(title);
671 }
672 
673 void
show_message_dialog(CelluloidView * view,GtkMessageType type,const gchar * title,const gchar * prefix,const gchar * msg)674 show_message_dialog(	CelluloidView *view,
675 			GtkMessageType type,
676 			const gchar *title,
677 			const gchar *prefix,
678 			const gchar *msg )
679 {
680 	GtkWidget *dialog = NULL;
681 
682 	if(view->has_dialog)
683 	{
684 		g_info("Error dialog suppressed because one is already being shown");
685 
686 		if(prefix)
687 		{
688 			g_info("Content: *%s* [%s] %s", title, prefix, msg);
689 		}
690 		else
691 		{
692 			g_info("Content: *%s* %s", title, msg);
693 		}
694 	}
695 	else if(prefix)
696 	{
697 		gchar *prefix_escaped = g_markup_printf_escaped("%s", prefix);
698 		gchar *msg_escaped = g_markup_printf_escaped("%s", msg);
699 
700 		dialog =	gtk_message_dialog_new_with_markup
701 				(	GTK_WINDOW(view),
702 					GTK_DIALOG_DESTROY_WITH_PARENT,
703 					type,
704 					GTK_BUTTONS_OK,
705 					"<b>[%s]</b> %s",
706 					prefix_escaped,
707 					msg_escaped );
708 
709 		g_free(prefix_escaped);
710 		g_free(msg_escaped);
711 	}
712 	else
713 	{
714 		dialog =	gtk_message_dialog_new
715 				(	GTK_WINDOW(view),
716 					GTK_DIALOG_DESTROY_WITH_PARENT,
717 					type,
718 					GTK_BUTTONS_OK,
719 					"%s",
720 					msg );
721 	}
722 
723 	if(dialog)
724 	{
725 		GtkMessageDialog *message_dialog =
726 			GTK_MESSAGE_DIALOG(dialog);
727 		GtkWidget *message_area =
728 			gtk_message_dialog_get_message_area(message_dialog);
729 		GtkWidget *first_child =
730 			gtk_widget_get_first_child(message_area);
731 
732 		view->has_dialog = TRUE;
733 
734 		for(	GtkWidget *child = first_child;
735 			child;
736 			child = gtk_widget_get_next_sibling(child) )
737 		{
738 			if(GTK_IS_LABEL(child))
739 			{
740 				gtk_label_set_wrap_mode
741 					(	GTK_LABEL(child),
742 						PANGO_WRAP_WORD_CHAR );
743 			}
744 		}
745 
746 		g_signal_connect
747 			(	dialog,
748 				"response",
749 				G_CALLBACK(message_dialog_response_handler),
750 				view );
751 
752 		gtk_window_set_title(GTK_WINDOW(dialog), title);
753 		gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
754 		gtk_widget_show(dialog);
755 	}
756 }
757 
758 static void
show_open_track_dialog(CelluloidView * view,TrackType type)759 show_open_track_dialog(CelluloidView  *view, TrackType type)
760 {
761 	const gchar *title = NULL;
762 
763 	switch(type)
764 	{
765 		case TRACK_TYPE_AUDIO:
766 		title = _("Load Audio Track…");
767 		break;
768 
769 		case TRACK_TYPE_VIDEO:
770 		title = _("Load Video Track…");
771 		break;
772 
773 		case TRACK_TYPE_SUBTITLE:
774 		title = _("Load Subtitle Track…");
775 		break;
776 
777 		default:
778 		g_assert_not_reached();
779 		break;
780 	}
781 
782 	CelluloidFileChooser *chooser =	celluloid_file_chooser_new
783 					(	title,
784 						GTK_WINDOW(view),
785 						GTK_FILE_CHOOSER_ACTION_OPEN );
786 
787 	g_object_set_data(	G_OBJECT(chooser),
788 				"track-type",
789 				(gpointer)type );
790 
791 	g_signal_connect(	chooser,
792 				"response",
793 				G_CALLBACK(open_track_dialog_response_handler),
794 				view );
795 
796 	celluloid_file_chooser_set_default_filters
797 		(	chooser,
798 			type == TRACK_TYPE_AUDIO,
799 			type == TRACK_TYPE_VIDEO,
800 			FALSE,
801 			type == TRACK_TYPE_SUBTITLE );
802 
803 	celluloid_file_chooser_show(chooser);
804 }
805 
806 static void
message_dialog_response_handler(GtkDialog * dialog,gint response_id,gpointer data)807 message_dialog_response_handler(	GtkDialog *dialog,
808 					gint response_id,
809 					gpointer data)
810 {
811 	CELLULOID_VIEW(data)->has_dialog = FALSE;
812 
813 	gtk_window_destroy(GTK_WINDOW(dialog));
814 }
815 
816 static void
open_dialog_response_handler(GtkDialog * dialog,gint response_id,gpointer data)817 open_dialog_response_handler(GtkDialog *dialog, gint response_id, gpointer data)
818 {
819 	GPtrArray *args = data;
820 	CelluloidView *view = g_ptr_array_index(args, 0);
821 	gboolean *append = g_ptr_array_index(args, 1);
822 
823 	if(response_id == GTK_RESPONSE_ACCEPT)
824 	{
825 		GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
826 		GListModel *files = gtk_file_chooser_get_files(chooser);
827 
828 		if(files)
829 		{
830 			g_signal_emit_by_name(view, "file-open", files, *append);
831 			g_object_unref(files);
832 		}
833 	}
834 
835 	celluloid_file_chooser_destroy(CELLULOID_FILE_CHOOSER(dialog));
836 
837 	g_free(append);
838 	g_ptr_array_free(args, TRUE);
839 }
840 
841 static void
open_location_dialog_response_handler(GtkDialog * dialog,gint response_id,gpointer data)842 open_location_dialog_response_handler(	GtkDialog *dialog,
843 					gint response_id,
844 					gpointer data )
845 {
846 	if(response_id == GTK_RESPONSE_ACCEPT)
847 	{
848 		CelluloidOpenLocationDialog *location_dialog =
849 			CELLULOID_OPEN_LOCATION_DIALOG(dialog);
850 		const gchar *uri =
851 			celluloid_open_location_dialog_get_string
852 			(location_dialog);
853 
854 		GPtrArray *args = data;
855 		CelluloidView *view = g_ptr_array_index(args, 0);
856 		gboolean *append = g_ptr_array_index(args, 1);
857 
858 		GFile *file = g_file_new_for_uri(uri);
859 		GListStore *list = g_list_store_new(G_TYPE_OBJECT);
860 
861 		g_list_store_append(list, file);
862 
863 		g_signal_emit_by_name(view, "file-open", list, *append);
864 
865 		g_object_unref(list);
866 		g_free(append);
867 		g_ptr_array_free(args, TRUE);
868 	}
869 
870 	gtk_window_close(GTK_WINDOW(dialog));
871 }
872 
873 static void
open_track_dialog_response_handler(GtkDialog * dialog,gint response_id,gpointer data)874 open_track_dialog_response_handler(	GtkDialog *dialog,
875 					gint response_id,
876 					gpointer data )
877 {
878 	if(response_id == GTK_RESPONSE_ACCEPT)
879 	{
880 		GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
881 		GListModel *files = gtk_file_chooser_get_files(chooser);
882 		const gchar *name = NULL;
883 
884 		TrackType type =
885 			(TrackType)
886 			g_object_get_data(G_OBJECT(dialog), "track-type");
887 
888 		switch(type)
889 		{
890 			case TRACK_TYPE_AUDIO:
891 			name = "audio-track-load";
892 			break;
893 
894 			case TRACK_TYPE_VIDEO:
895 			name = "video-track-load";
896 			break;
897 
898 			case TRACK_TYPE_SUBTITLE:
899 			name = "subtitle-track-load";
900 			break;
901 
902 			default:
903 			g_assert_not_reached();
904 			break;
905 		}
906 
907 		CelluloidView *view = CELLULOID_VIEW(data);
908 		const guint n_items = g_list_model_get_n_items(files);
909 
910 		for(guint i = 0; i < n_items; i++)
911 		{
912 			GFile *file = g_list_model_get_item(files, i);
913 			gchar *uri = g_file_get_uri(file);
914 
915 			g_signal_emit_by_name(view, name, uri);
916 			g_free(uri);
917 		}
918 
919 		g_object_unref(files);
920 	}
921 
922 	celluloid_file_chooser_destroy(CELLULOID_FILE_CHOOSER(dialog));
923 }
924 
925 static void
preferences_dialog_response_handler(GtkDialog * dialog,gint response_id,gpointer data)926 preferences_dialog_response_handler(	GtkDialog *dialog,
927 					gint response_id,
928 					gpointer data )
929 {
930 	if(response_id == GTK_RESPONSE_ACCEPT)
931 	{
932 		CelluloidMainWindow *wnd;
933 		GSettings *settings;
934 		gboolean csd_enable;
935 
936 		wnd = CELLULOID_MAIN_WINDOW(data);
937 		settings = g_settings_new(CONFIG_ROOT);
938 		csd_enable = g_settings_get_boolean(settings, "csd-enable");
939 
940 		if(celluloid_main_window_get_csd_enabled(wnd) != csd_enable)
941 		{
942 			show_message_dialog(	CELLULOID_VIEW(data),
943 						GTK_MESSAGE_INFO,
944 						g_get_application_name(),
945 						NULL,
946 						_("Enabling or disabling "
947 						"client-side decorations "
948 						"requires restarting to "
949 						"take effect.") );
950 		}
951 
952 		gtk_widget_queue_draw(GTK_WIDGET(wnd));
953 		g_signal_emit_by_name(data, "preferences-updated");
954 
955 		g_object_unref(settings);
956 	}
957 
958 	gtk_window_close(GTK_WINDOW(dialog));
959 }
960 
961 static void
save_playlist_response_handler(GtkDialog * dialog,gint response_id,gpointer data)962 save_playlist_response_handler(	GtkDialog *dialog,
963 				gint response_id,
964 				gpointer data )
965 {
966 	CelluloidView *view = CELLULOID_VIEW(data);
967 	GFile *dest_file = NULL;
968 	GError *error = NULL;
969 
970 	if(response_id == GTK_RESPONSE_ACCEPT)
971 	{
972 		/* There should be only one file selected. */
973 		dest_file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog));
974 	}
975 
976 	celluloid_file_chooser_destroy(CELLULOID_FILE_CHOOSER(dialog));
977 
978 	if(dest_file)
979 	{
980 		save_playlist(view, dest_file, &error);
981 		g_object_unref(dest_file);
982 	}
983 
984 	if(error)
985 	{
986 		show_message_dialog(	view,
987 					GTK_MESSAGE_ERROR,
988 					_("Error"),
989 					NULL,
990 					error->message );
991 
992 		g_error_free(error);
993 	}
994 }
995 
996 static void
realize_handler(GtkWidget * widget,gpointer data)997 realize_handler(GtkWidget *widget, gpointer data)
998 {
999 	load_css(CELLULOID_VIEW(widget));
1000 
1001 	g_signal_emit_by_name(CELLULOID_VIEW(widget), "ready");
1002 }
1003 
1004 static void
resize_handler(CelluloidVideoArea * video_area,gint width,gint height,gpointer data)1005 resize_handler(	CelluloidVideoArea *video_area,
1006 		gint width,
1007 		gint height,
1008 		gpointer data )
1009 {
1010 	g_signal_emit_by_name(data, "video-area-resize", width, height);
1011 }
1012 
1013 static void
render_handler(CelluloidVideoArea * area,gpointer data)1014 render_handler(CelluloidVideoArea *area, gpointer data)
1015 {
1016 	g_signal_emit_by_name(data, "render");
1017 }
1018 
1019 static gboolean
drop_handler(GtkDropTargetAsync * self,GValue * value,gdouble x,gdouble y,gpointer data)1020 drop_handler(	GtkDropTargetAsync *self,
1021 		GValue *value,
1022 		gdouble x,
1023 		gdouble y,
1024 		gpointer data )
1025 {
1026 	CelluloidView *view = CELLULOID_VIEW(data);
1027 	GtkEventController *controller = GTK_EVENT_CONTROLLER(self);
1028 	GtkWidget *source = gtk_event_controller_get_widget(controller);
1029 	const gboolean append = CELLULOID_IS_PLAYLIST_WIDGET(source);
1030 	GListStore *files = NULL;
1031 
1032 	if(G_VALUE_HOLDS_STRING(value))
1033 	{
1034 		const gchar *string = g_value_get_string(value);
1035 		gchar **uris = g_uri_list_extract_uris(string);
1036 
1037 		if(uris)
1038 		{
1039 			files = g_list_store_new(G_TYPE_FILE);
1040 
1041 			for(gint i = 0; uris[i]; i++)
1042 			{
1043 				GFile *file = g_file_new_for_uri(uris[i]);
1044 
1045 				g_list_store_append(files, file);
1046 				g_object_unref(file);
1047 			}
1048 
1049 			g_strfreev(uris);
1050 		}
1051 	}
1052 	else if(G_VALUE_HOLDS(value, GDK_TYPE_FILE_LIST))
1053 	{
1054 		const GSList *slist = g_value_get_boxed(value);
1055 
1056 		files = g_list_store_new(G_TYPE_FILE);
1057 
1058 		for(const GSList *cur = slist; cur; cur = cur->next)
1059 		{
1060 			g_list_store_append(files, G_FILE(cur->data));
1061 		}
1062 	}
1063 
1064 	if(files)
1065 	{
1066 		g_signal_emit_by_name(view, "file-open", files, append);
1067 		g_object_unref(files);
1068 	}
1069 
1070 	return TRUE;
1071 }
1072 
1073 static gboolean
notify_fullscreened_handler(GObject * gobject,GParamSpec * pspec,gpointer data)1074 notify_fullscreened_handler(GObject *gobject, GParamSpec *pspec, gpointer data)
1075 {
1076 	CelluloidView *view = CELLULOID_VIEW(data);
1077 
1078 	view->fullscreen = gtk_window_is_fullscreen(GTK_WINDOW(view));
1079 	g_object_notify(data, "fullscreen");
1080 
1081 	return FALSE;
1082 }
1083 
1084 static gboolean
close_request_handler(GtkWidget * widget,gpointer data)1085 close_request_handler(GtkWidget *widget, gpointer data)
1086 {
1087 	if(!celluloid_main_window_get_fullscreen(CELLULOID_MAIN_WINDOW(widget)))
1088 	{
1089 		celluloid_main_window_save_state(CELLULOID_MAIN_WINDOW(widget));
1090 	}
1091 
1092 	return FALSE;
1093 }
1094 
1095 static void
playlist_row_activated_handler(CelluloidPlaylistWidget * playlist,gint64 pos,gpointer data)1096 playlist_row_activated_handler(	CelluloidPlaylistWidget *playlist,
1097 				gint64 pos,
1098 				gpointer data )
1099 {
1100 	g_signal_emit_by_name(data, "playlist-item-activated", pos);
1101 }
1102 
1103 static void
playlist_row_inserted_handler(CelluloidPlaylistWidget * widget,gint pos,gpointer data)1104 playlist_row_inserted_handler(	CelluloidPlaylistWidget *widget,
1105 				gint pos,
1106 				gpointer data )
1107 {
1108 	g_signal_emit_by_name(data, "playlist-item-inserted", pos);
1109 }
1110 
1111 static void
playlist_row_deleted_handler(CelluloidPlaylistWidget * widget,gint pos,gpointer data)1112 playlist_row_deleted_handler(	CelluloidPlaylistWidget *widget,
1113 				gint pos,
1114 				gpointer data )
1115 {
1116 	if(celluloid_playlist_widget_empty(widget))
1117 	{
1118 		celluloid_view_reset(data);
1119 	}
1120 
1121 	g_signal_emit_by_name(data, "playlist-item-deleted", pos);
1122 }
1123 
1124 static void
playlist_row_reordered_handler(CelluloidPlaylistWidget * widget,gint src,gint dest,gpointer data)1125 playlist_row_reordered_handler(	CelluloidPlaylistWidget *widget,
1126 				gint src,
1127 				gint dest,
1128 				gpointer data )
1129 {
1130 	g_signal_emit_by_name(data, "playlist-reordered", src, dest);
1131 }
1132 
1133 static void
celluloid_view_class_init(CelluloidViewClass * klass)1134 celluloid_view_class_init(CelluloidViewClass *klass)
1135 {
1136 	GObjectClass *object_class = G_OBJECT_CLASS(klass);
1137 	GParamSpec *pspec = NULL;
1138 
1139 	object_class->constructed = constructed;
1140 	object_class->dispose = dispose;
1141 	object_class->set_property = set_property;
1142 	object_class->get_property = get_property;
1143 
1144 	pspec = g_param_spec_int
1145 		(	"playlist-count",
1146 			"Playlist count",
1147 			"The number of items in playlist",
1148 			0,
1149 			G_MAXINT,
1150 			0,
1151 			G_PARAM_READWRITE );
1152 	g_object_class_install_property(object_class, PROP_PLAYLIST_COUNT, pspec);
1153 
1154 	pspec = g_param_spec_boolean
1155 		(	"pause",
1156 			"Pause",
1157 			"Whether or not the player is paused",
1158 			FALSE,
1159 			G_PARAM_READWRITE );
1160 	g_object_class_install_property(object_class, PROP_PAUSE, pspec);
1161 
1162 	pspec = g_param_spec_boolean
1163 		(	"idle-active",
1164 			"Idle active",
1165 			"Whether or not the player is idle",
1166 			TRUE,
1167 			G_PARAM_READWRITE );
1168 	g_object_class_install_property(object_class, PROP_IDLE_ACTIVE, pspec);
1169 
1170 	pspec = g_param_spec_double
1171 		(	"volume",
1172 			"Volume",
1173 			"The volume the volume button is set to",
1174 			0.0,
1175 			G_MAXDOUBLE,
1176 			0.0,
1177 			G_PARAM_READWRITE );
1178 	g_object_class_install_property(object_class, PROP_VOLUME, pspec);
1179 
1180 	pspec = g_param_spec_double
1181 		(	"volume-max",
1182 			"Volume Max",
1183 			"The maximum amplification level",
1184 			0.0,
1185 			G_MAXDOUBLE,
1186 			100.0,
1187 			G_PARAM_READWRITE );
1188 	g_object_class_install_property(object_class, PROP_VOLUME_MAX, pspec);
1189 
1190 	pspec = g_param_spec_double
1191 		(	"duration",
1192 			"Duration",
1193 			"The duration the seek bar is using",
1194 			0.0,
1195 			G_MAXDOUBLE,
1196 			0.0,
1197 			G_PARAM_READWRITE );
1198 	g_object_class_install_property(object_class, PROP_DURATION, pspec);
1199 
1200 	pspec = g_param_spec_int
1201 		(	"playlist-pos",
1202 			"Playlist position",
1203 			"The position of the current item in playlist",
1204 			-1,
1205 			G_MAXINT,
1206 			-1,
1207 			G_PARAM_READWRITE );
1208 	g_object_class_install_property(object_class, PROP_PLAYLIST_POS, pspec);
1209 
1210 	pspec = g_param_spec_pointer
1211 		(	"track-list",
1212 			"Track list",
1213 			"The list of tracks in the playing file",
1214 			G_PARAM_READWRITE );
1215 	g_object_class_install_property(object_class, PROP_TRACK_LIST, pspec);
1216 
1217 	pspec = g_param_spec_pointer
1218 		(	"disc-list",
1219 			"Disc list",
1220 			"The list of mounted discs",
1221 			G_PARAM_READWRITE );
1222 	g_object_class_install_property(object_class, PROP_DISC_LIST, pspec);
1223 
1224 	pspec = g_param_spec_boolean
1225 		(	"skip-enabled",
1226 			"Skip enabled",
1227 			"Whether or not skip buttons are shown",
1228 			FALSE,
1229 			G_PARAM_READWRITE );
1230 	g_object_class_install_property(object_class, PROP_SKIP_ENABLED, pspec);
1231 
1232 	pspec = g_param_spec_boolean
1233 		(	"loop",
1234 			"Loop",
1235 			"Whether or not the the loop button is active",
1236 			FALSE,
1237 			G_PARAM_READWRITE );
1238 	g_object_class_install_property(object_class, PROP_LOOP, pspec);
1239 
1240 	pspec = g_param_spec_boolean
1241 		(	"shuffle",
1242 			"Shuffle",
1243 			"Whether or not the the shuffle button is active",
1244 			FALSE,
1245 			G_PARAM_READWRITE );
1246 	g_object_class_install_property(object_class, PROP_SHUFFLE, pspec);
1247 
1248 	pspec = g_param_spec_boolean
1249 		(	"border",
1250 			"Border",
1251 			"Whether or not the main window should have decorations",
1252 			FALSE,
1253 			G_PARAM_READWRITE );
1254 	g_object_class_install_property(object_class, PROP_BORDER, pspec);
1255 
1256 	pspec = g_param_spec_boolean
1257 		(	"fullscreen",
1258 			"Fullscreen",
1259 			"Whether or not the player is current in fullscreen mode",
1260 			FALSE,
1261 			G_PARAM_READWRITE );
1262 	g_object_class_install_property(object_class, PROP_FULLSCREEN, pspec);
1263 
1264 	pspec = g_param_spec_string
1265 		(	"media-title",
1266 			"Media Title",
1267 			"The title of the media being played",
1268 			NULL,
1269 			G_PARAM_READWRITE );
1270 	g_object_class_install_property(object_class, PROP_MEDIA_TITLE, pspec);
1271 
1272 	pspec = g_param_spec_boolean
1273 		(	"searching",
1274 			"Searching",
1275 			"Whether or not the user is searching the playlist",
1276 			FALSE,
1277 			G_PARAM_READWRITE );
1278 	g_object_class_install_property(object_class, PROP_SEARCHING, pspec);
1279 
1280 	g_signal_new(	"video-area-resize",
1281 			G_TYPE_FROM_CLASS(klass),
1282 			G_SIGNAL_RUN_FIRST|G_SIGNAL_DETAILED,
1283 			0,
1284 			NULL,
1285 			NULL,
1286 			g_cclosure_gen_marshal_VOID__INT_INT,
1287 			G_TYPE_NONE,
1288 			2,
1289 			G_TYPE_INT,
1290 			G_TYPE_INT );
1291 
1292 	/* Controls-related signals */
1293 	g_signal_new(	"volume-changed",
1294 			G_TYPE_FROM_CLASS(klass),
1295 			G_SIGNAL_RUN_FIRST,
1296 			0,
1297 			NULL,
1298 			NULL,
1299 			g_cclosure_marshal_VOID__DOUBLE,
1300 			G_TYPE_NONE,
1301 			1,
1302 			G_TYPE_DOUBLE );
1303 	g_signal_new(	"ready",
1304 			G_TYPE_FROM_CLASS(klass),
1305 			G_SIGNAL_RUN_FIRST,
1306 			0,
1307 			NULL,
1308 			NULL,
1309 			g_cclosure_marshal_VOID__VOID,
1310 			G_TYPE_NONE,
1311 			0 );
1312 	g_signal_new(	"render",
1313 			G_TYPE_FROM_CLASS(klass),
1314 			G_SIGNAL_RUN_FIRST,
1315 			0,
1316 			NULL,
1317 			NULL,
1318 			g_cclosure_marshal_VOID__VOID,
1319 			G_TYPE_NONE,
1320 			0 );
1321 	g_signal_new(	"preferences-updated",
1322 			G_TYPE_FROM_CLASS(klass),
1323 			G_SIGNAL_RUN_FIRST,
1324 			0,
1325 			NULL,
1326 			NULL,
1327 			g_cclosure_marshal_VOID__VOID,
1328 			G_TYPE_NONE,
1329 			0 );
1330 	g_signal_new(	"audio-track-load",
1331 			G_TYPE_FROM_CLASS(klass),
1332 			G_SIGNAL_RUN_FIRST,
1333 			0,
1334 			NULL,
1335 			NULL,
1336 			g_cclosure_marshal_VOID__STRING,
1337 			G_TYPE_NONE,
1338 			1,
1339 			G_TYPE_STRING );
1340 	g_signal_new(	"video-track-load",
1341 			G_TYPE_FROM_CLASS(klass),
1342 			G_SIGNAL_RUN_FIRST,
1343 			0,
1344 			NULL,
1345 			NULL,
1346 			g_cclosure_marshal_VOID__STRING,
1347 			G_TYPE_NONE,
1348 			1,
1349 			G_TYPE_STRING );
1350 	g_signal_new(	"subtitle-track-load",
1351 			G_TYPE_FROM_CLASS(klass),
1352 			G_SIGNAL_RUN_FIRST,
1353 			0,
1354 			NULL,
1355 			NULL,
1356 			g_cclosure_marshal_VOID__STRING,
1357 			G_TYPE_NONE,
1358 			1,
1359 			G_TYPE_STRING );
1360 	g_signal_new(	"file-open",
1361 			G_TYPE_FROM_CLASS(klass),
1362 			G_SIGNAL_RUN_FIRST,
1363 			0,
1364 			NULL,
1365 			NULL,
1366 			g_cclosure_gen_marshal_VOID__POINTER_BOOLEAN,
1367 			G_TYPE_NONE,
1368 			2,
1369 			G_TYPE_POINTER,
1370 			G_TYPE_BOOLEAN );
1371 	g_signal_new(	"playlist-item-activated",
1372 			G_TYPE_FROM_CLASS(klass),
1373 			G_SIGNAL_RUN_FIRST,
1374 			0,
1375 			NULL,
1376 			NULL,
1377 			g_cclosure_marshal_VOID__INT,
1378 			G_TYPE_NONE,
1379 			1,
1380 			G_TYPE_INT );
1381 	g_signal_new(	"playlist-item-inserted",
1382 			G_TYPE_FROM_CLASS(klass),
1383 			G_SIGNAL_RUN_FIRST,
1384 			0,
1385 			NULL,
1386 			NULL,
1387 			g_cclosure_marshal_VOID__INT,
1388 			G_TYPE_NONE,
1389 			1,
1390 			G_TYPE_INT );
1391 	g_signal_new(	"playlist-item-deleted",
1392 			G_TYPE_FROM_CLASS(klass),
1393 			G_SIGNAL_RUN_FIRST,
1394 			0,
1395 			NULL,
1396 			NULL,
1397 			g_cclosure_marshal_VOID__INT,
1398 			G_TYPE_NONE,
1399 			1,
1400 			G_TYPE_INT );
1401 	g_signal_new(	"playlist-reordered",
1402 			G_TYPE_FROM_CLASS(klass),
1403 			G_SIGNAL_RUN_FIRST,
1404 			0,
1405 			NULL,
1406 			NULL,
1407 			g_cclosure_gen_marshal_VOID__INT_INT,
1408 			G_TYPE_NONE,
1409 			2,
1410 			G_TYPE_INT,
1411 			G_TYPE_INT );
1412 }
1413 
1414 static void
celluloid_view_init(CelluloidView * view)1415 celluloid_view_init(CelluloidView *view)
1416 {
1417 	view->disposed = FALSE;
1418 	view->has_dialog = FALSE;
1419 	view->playlist_count = 0;
1420 	view->pause = FALSE;
1421 	view->idle_active = FALSE;
1422 	view->volume = 0.0;
1423 	view->volume_max = 100.0;
1424 	view->duration = 0.0;
1425 	view->playlist_pos = 0;
1426 	view->disc_list = NULL;
1427 	view->skip_enabled = FALSE;
1428 	view->loop = FALSE;
1429 	view->shuffle = FALSE;
1430 	view->border = FALSE;
1431 	view->fullscreen = FALSE;
1432 	view->media_title = NULL;
1433 	view->display_fps = 0;
1434 	view->searching = FALSE;
1435 
1436 	g_signal_connect(view, "realize", G_CALLBACK(realize_handler), NULL);
1437 }
1438 
1439 CelluloidView *
celluloid_view_new(CelluloidApplication * app,gboolean always_floating)1440 celluloid_view_new(CelluloidApplication *app, gboolean always_floating)
1441 {
1442 	GObject *obj = g_object_new(	celluloid_view_get_type(),
1443 					"application",
1444 					app,
1445 					"always-use-floating-controls",
1446 					always_floating,
1447 					NULL );
1448 	CelluloidView *view = CELLULOID_VIEW(obj);
1449 	GtkApplication *gtk_app = GTK_APPLICATION(app);
1450 	GSettings *settings = g_settings_new(CONFIG_ROOT);
1451 
1452 	if(g_settings_get_boolean(settings, "csd-enable"))
1453 	{
1454 		celluloid_main_window_enable_csd(CELLULOID_MAIN_WINDOW(view));
1455 	}
1456 	else
1457 	{
1458 		GMenu *full_menu = g_menu_new();
1459 
1460 		celluloid_menu_build_full(full_menu, NULL, NULL);
1461 		gtk_application_set_menubar(gtk_app, G_MENU_MODEL(full_menu));
1462 	}
1463 
1464 	g_object_unref(settings);
1465 
1466 	return view;
1467 }
1468 
1469 CelluloidMainWindow *
celluloid_view_get_main_window(CelluloidView * view)1470 celluloid_view_get_main_window(CelluloidView *view)
1471 {
1472 	return CELLULOID_MAIN_WINDOW(view);
1473 }
1474 
1475 void
celluloid_view_show_open_dialog(CelluloidView * view,gboolean folder,gboolean append)1476 celluloid_view_show_open_dialog(	CelluloidView *view,
1477 					gboolean folder,
1478 					gboolean append )
1479 {
1480 	CelluloidFileChooser *chooser;
1481 	GPtrArray *args;
1482 	const gchar *title;
1483 	GtkFileChooserAction action;
1484 	gboolean *append_buf;
1485 
1486 	if(folder)
1487 	{
1488 		title = append ? _("Add Folder to Playlist") : _("Open Folder");
1489 		action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
1490 	}
1491 	else
1492 	{
1493 		title = append ? _("Add File to Playlist") : _("Open File");
1494 		action = GTK_FILE_CHOOSER_ACTION_OPEN;
1495 	}
1496 
1497 	chooser = celluloid_file_chooser_new(title, GTK_WINDOW(view), action);
1498 	args = g_ptr_array_new();
1499 	append_buf = g_malloc(sizeof(gboolean));
1500 	*append_buf = append;
1501 
1502 	g_ptr_array_add(args, view);
1503 	g_ptr_array_add(args, append_buf);
1504 
1505 	g_signal_connect(	chooser,
1506 				"response",
1507 				G_CALLBACK(open_dialog_response_handler),
1508 				args );
1509 
1510 	if(!folder)
1511 	{
1512 		celluloid_file_chooser_set_default_filters
1513 			(chooser, TRUE, TRUE, TRUE, TRUE);
1514 	}
1515 
1516 	celluloid_file_chooser_show(chooser);
1517 }
1518 
1519 void
celluloid_view_show_open_location_dialog(CelluloidView * view,gboolean append)1520 celluloid_view_show_open_location_dialog(CelluloidView *view, gboolean append)
1521 {
1522 	GtkWidget *dlg;
1523 	GPtrArray *args;
1524 	gboolean *append_buf;
1525 
1526 	dlg =	celluloid_open_location_dialog_new
1527 		(	GTK_WINDOW(view),
1528 			append?
1529 			_("Add Location to Playlist"):
1530 			_("Open Location") );
1531 	args = g_ptr_array_sized_new(2);
1532 	append_buf = g_malloc(sizeof(gboolean));
1533 	*append_buf = append;
1534 
1535 	g_ptr_array_add(args, view);
1536 	g_ptr_array_add(args, append_buf);
1537 
1538 	g_signal_connect
1539 		(	dlg,
1540 			"response",
1541 			G_CALLBACK(open_location_dialog_response_handler),
1542 			args );
1543 
1544 	gtk_widget_show(dlg);
1545 }
1546 
1547 void
celluloid_view_show_open_audio_track_dialog(CelluloidView * view)1548 celluloid_view_show_open_audio_track_dialog(CelluloidView *view)
1549 {
1550 	show_open_track_dialog(view, TRACK_TYPE_AUDIO);
1551 }
1552 
1553 void
celluloid_view_show_open_video_track_dialog(CelluloidView * view)1554 celluloid_view_show_open_video_track_dialog(CelluloidView *view)
1555 {
1556 	show_open_track_dialog(view, TRACK_TYPE_VIDEO);
1557 }
1558 
1559 void
celluloid_view_show_open_subtitle_track_dialog(CelluloidView * view)1560 celluloid_view_show_open_subtitle_track_dialog(CelluloidView *view)
1561 {
1562 	show_open_track_dialog(view, TRACK_TYPE_SUBTITLE);
1563 }
1564 
1565 void
celluloid_view_show_save_playlist_dialog(CelluloidView * view)1566 celluloid_view_show_save_playlist_dialog(CelluloidView *view)
1567 {
1568 	CelluloidFileChooser *file_chooser;
1569 	GtkFileChooser *gtk_chooser;
1570 
1571 	file_chooser =	celluloid_file_chooser_new
1572 			(	_("Save Playlist"),
1573 				GTK_WINDOW(view),
1574 				GTK_FILE_CHOOSER_ACTION_SAVE );
1575 	gtk_chooser = GTK_FILE_CHOOSER(file_chooser);
1576 
1577 	gtk_file_chooser_set_current_name(gtk_chooser, "playlist.m3u");
1578 
1579 	g_signal_connect(	file_chooser,
1580 				"response",
1581 				G_CALLBACK(save_playlist_response_handler),
1582 				view );
1583 
1584 	gtk_native_dialog_show(GTK_NATIVE_DIALOG(file_chooser));
1585 }
1586 
1587 void
celluloid_view_show_preferences_dialog(CelluloidView * view)1588 celluloid_view_show_preferences_dialog(CelluloidView *view)
1589 {
1590 	GtkWidget *dialog = celluloid_preferences_dialog_new(GTK_WINDOW(view));
1591 
1592 	g_signal_connect_after(	dialog,
1593 				"response",
1594 				G_CALLBACK(preferences_dialog_response_handler),
1595 				view );
1596 
1597 	gtk_widget_show(dialog);
1598 }
1599 
1600 void
celluloid_view_show_shortcuts_dialog(CelluloidView * view)1601 celluloid_view_show_shortcuts_dialog(CelluloidView *view)
1602 {
1603 	GtkWidget *wnd = celluloid_shortcuts_window_new(GTK_WINDOW(view));
1604 
1605 	gtk_widget_show(wnd);
1606 }
1607 
1608 void
celluloid_view_show_about_dialog(CelluloidView * view)1609 celluloid_view_show_about_dialog(CelluloidView *view)
1610 {
1611 	const gchar *const authors[] = AUTHORS;
1612 
1613 	gtk_show_about_dialog(	GTK_WINDOW(view),
1614 				"logo-icon-name",
1615 				ICON_NAME,
1616 				"version",
1617 				VERSION,
1618 				"comments",
1619 				_("A GTK frontend for MPV"),
1620 				"website",
1621 				"https://github.com/celluloid-player/celluloid",
1622 				"license-type",
1623 				GTK_LICENSE_GPL_3_0,
1624 				"copyright",
1625 				"\u00A9 2014-2021 The Celluloid authors",
1626 				"authors",
1627 				authors,
1628 				"translator-credits",
1629 				_("translator-credits"),
1630 				NULL );
1631 }
1632 
1633 void
celluloid_view_show_message_dialog(CelluloidView * view,GtkMessageType type,const gchar * title,const gchar * prefix,const gchar * msg)1634 celluloid_view_show_message_dialog(	CelluloidView *view,
1635 					GtkMessageType type,
1636 					const gchar *title,
1637 					const gchar *prefix,
1638 					const gchar *msg )
1639 {
1640 	show_message_dialog(view, type, title, prefix, msg);
1641 }
1642 
1643 void
celluloid_view_present(CelluloidView * view)1644 celluloid_view_present(CelluloidView *view)
1645 {
1646 	gtk_window_present(GTK_WINDOW(view));
1647 }
1648 
1649 void
celluloid_view_quit(CelluloidView * view)1650 celluloid_view_quit(CelluloidView *view)
1651 {
1652 	gtk_window_close(GTK_WINDOW(view));
1653 }
1654 
1655 void
celluloid_view_reset(CelluloidView * view)1656 celluloid_view_reset(CelluloidView *view)
1657 {
1658 	celluloid_main_window_reset(CELLULOID_MAIN_WINDOW(view));
1659 }
1660 
1661 void
celluloid_view_queue_render(CelluloidView * view)1662 celluloid_view_queue_render(CelluloidView *view)
1663 {
1664 	CelluloidMainWindow *wnd = CELLULOID_MAIN_WINDOW(view);
1665 	CelluloidVideoArea *area = celluloid_main_window_get_video_area(wnd);
1666 
1667 	celluloid_video_area_queue_render(area);
1668 }
1669 
1670 void
celluloid_view_make_gl_context_current(CelluloidView * view)1671 celluloid_view_make_gl_context_current(CelluloidView *view)
1672 {
1673 	CelluloidMainWindow *wnd =
1674 		CELLULOID_MAIN_WINDOW(view);
1675 	CelluloidVideoArea *video_area =
1676 		celluloid_main_window_get_video_area(wnd);
1677 	GtkGLArea *gl_area =
1678 		celluloid_video_area_get_gl_area(video_area);
1679 
1680 	gtk_widget_realize(GTK_WIDGET(gl_area));
1681 	gtk_gl_area_make_current(gl_area);
1682 }
1683 
1684 void
celluloid_view_set_use_opengl_cb(CelluloidView * view,gboolean use_opengl_cb)1685 celluloid_view_set_use_opengl_cb(CelluloidView *view, gboolean use_opengl_cb)
1686 {
1687 	CelluloidMainWindow *wnd = CELLULOID_MAIN_WINDOW(view);
1688 	CelluloidVideoArea *area = celluloid_main_window_get_video_area(wnd);
1689 
1690 	celluloid_video_area_set_use_opengl(area, use_opengl_cb);
1691 }
1692 
1693 gint
celluloid_view_get_scale_factor(CelluloidView * view)1694 celluloid_view_get_scale_factor(CelluloidView *view)
1695 {
1696 	GdkSurface *surface = gtk_widget_get_surface(view);
1697 
1698 	return gdk_surface_get_scale_factor(surface);
1699 }
1700 
1701 void
celluloid_view_get_video_area_geometry(CelluloidView * view,gint * width,gint * height)1702 celluloid_view_get_video_area_geometry(	CelluloidView *view,
1703 					gint *width,
1704 					gint *height )
1705 {
1706 	CelluloidMainWindow *wnd = CELLULOID_MAIN_WINDOW(view);
1707 	CelluloidVideoArea *area = celluloid_main_window_get_video_area(wnd);
1708 	GtkAllocation allocation;
1709 
1710 	gtk_widget_get_allocation(GTK_WIDGET(area), &allocation);
1711 
1712 	*width = allocation.width;
1713 	*height = allocation.height;
1714 }
1715 
1716 void
celluloid_view_move(CelluloidView * view,gboolean flip_x,gboolean flip_y,GValue * x,GValue * y)1717 celluloid_view_move(	CelluloidView *view,
1718 			gboolean flip_x,
1719 			gboolean flip_y,
1720 			GValue *x,
1721 			GValue *y )
1722 {
1723 #ifdef GDK_WINDOWING_WAYLAND
1724 	GdkDisplay *display = gdk_display_get_default();
1725 
1726 	if (GDK_IS_WAYLAND_DISPLAY(display))
1727 	{
1728 		g_warning("%s: Not supported on Wayland", __func__);
1729 		return;
1730 	}
1731 #endif
1732 
1733 	g_warning("%s: Not implemented", __func__);
1734 }
1735 
1736 void
celluloid_view_resize_video_area(CelluloidView * view,gint width,gint height)1737 celluloid_view_resize_video_area(CelluloidView *view, gint width, gint height)
1738 {
1739 	celluloid_main_window_resize_video_area
1740 		(CELLULOID_MAIN_WINDOW(view), width, height);
1741 }
1742 
1743 void
celluloid_view_set_fullscreen(CelluloidView * view,gboolean fullscreen)1744 celluloid_view_set_fullscreen(CelluloidView *view, gboolean fullscreen)
1745 {
1746 	g_object_set(view, "fullscreen", fullscreen, NULL);
1747 	celluloid_main_window_set_fullscreen
1748 		(CELLULOID_MAIN_WINDOW(view), fullscreen);
1749 }
1750 
1751 void
celluloid_view_set_time_position(CelluloidView * view,gdouble position)1752 celluloid_view_set_time_position(CelluloidView *view, gdouble position)
1753 {
1754 	CelluloidControlBox *control_box;
1755 
1756 	control_box =	celluloid_main_window_get_control_box
1757 			(CELLULOID_MAIN_WINDOW(view));
1758 	g_object_set(control_box, "time-position", position, NULL);
1759 }
1760 
1761 void
celluloid_view_update_playlist(CelluloidView * view,GPtrArray * playlist)1762 celluloid_view_update_playlist(CelluloidView *view, GPtrArray *playlist)
1763 {
1764 	CelluloidMainWindow *wnd = CELLULOID_MAIN_WINDOW(view);
1765 	CelluloidPlaylistWidget *wgt = celluloid_main_window_get_playlist(wnd);
1766 
1767 	celluloid_playlist_widget_update_contents(wgt, playlist);
1768 }
1769 
1770 void
celluloid_view_set_playlist_pos(CelluloidView * view,gint64 pos)1771 celluloid_view_set_playlist_pos(CelluloidView *view, gint64 pos)
1772 {
1773 	g_object_set(view, "playlist-pos", (gint)pos, NULL);
1774 }
1775 
1776 void
celluloid_view_set_playlist_visible(CelluloidView * view,gboolean visible)1777 celluloid_view_set_playlist_visible(CelluloidView *view, gboolean visible)
1778 {
1779 	celluloid_main_window_set_playlist_visible
1780 		(CELLULOID_MAIN_WINDOW(view), visible);
1781 }
1782 
1783 gboolean
celluloid_view_get_playlist_visible(CelluloidView * view)1784 celluloid_view_get_playlist_visible(CelluloidView *view)
1785 {
1786 	return 	celluloid_main_window_get_playlist_visible
1787 		(CELLULOID_MAIN_WINDOW(view));
1788 }
1789 
1790 void
celluloid_view_set_controls_visible(CelluloidView * view,gboolean visible)1791 celluloid_view_set_controls_visible(CelluloidView *view, gboolean visible)
1792 {
1793 	celluloid_main_window_set_controls_visible
1794 		(CELLULOID_MAIN_WINDOW(view), visible);
1795 }
1796 
1797 gboolean
celluloid_view_get_controls_visible(CelluloidView * view)1798 celluloid_view_get_controls_visible(CelluloidView *view)
1799 {
1800 	return	celluloid_main_window_get_controls_visible
1801 		(CELLULOID_MAIN_WINDOW(view));
1802 }
1803 
1804 void
celluloid_view_set_main_menu_visible(CelluloidView * view,gboolean visible)1805 celluloid_view_set_main_menu_visible(CelluloidView *view, gboolean visible)
1806 {
1807 	gboolean csd =
1808 		celluloid_main_window_get_csd_enabled
1809 		(CELLULOID_MAIN_WINDOW(view));
1810 
1811 	if(csd)
1812 	{
1813 		GtkWidget *header_bar = gtk_window_get_titlebar(GTK_WINDOW(view));
1814 
1815 		celluloid_header_bar_set_menu_button_popup_visible
1816 			(CELLULOID_HEADER_BAR(header_bar), visible);
1817 	}
1818 	else
1819 	{
1820 		gtk_application_window_set_show_menubar
1821 			(GTK_APPLICATION_WINDOW(view), visible);
1822 	}
1823 }
1824 
1825 gboolean
celluloid_view_get_main_menu_visible(CelluloidView * view)1826 celluloid_view_get_main_menu_visible(CelluloidView *view)
1827 {
1828 	gboolean csd =
1829 		celluloid_main_window_get_csd_enabled
1830 		(CELLULOID_MAIN_WINDOW(view));
1831 
1832 	gboolean result = FALSE;
1833 
1834 	if(csd)
1835 	{
1836 		GtkWidget *header_bar = gtk_window_get_titlebar(GTK_WINDOW(view));
1837 
1838 		result =
1839 			celluloid_header_bar_get_menu_button_popup_visible
1840 			(CELLULOID_HEADER_BAR(header_bar));
1841 	}
1842 	else
1843 	{
1844 		result =
1845 			gtk_application_window_get_show_menubar
1846 			(GTK_APPLICATION_WINDOW(view));
1847 	}
1848 
1849 	return result;
1850 }
1851