1 // gnash-view.cpp: Gtk view widget for gnash
3 // Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 #include "gnash-view.h"
21 #include "gtk_canvas.h"
24 #include <gdk/gdkkeysyms.h>
25 #include <gtk/gtkbutton.h>
30 #include "movie_definition.h"
31 #include "MovieFactory.h"
32 #include "movie_root.h" // for Abstract callbacks
34 #include "sound_handler.h"
35 #include "MediaHandler.h"
36 #include "RunResources.h" // for passing handlers and other data to the core.
37 #include "VirtualClock.h"
38 #include "SystemClock.h"
39 #include "DisplayObject.h"
41 #include "Global_as.h"
42 #include "NamingPolicy.h"
43 #include "StreamProvider.h"
44 #include "GnashFactory.h"
59 std::shared_ptr
<gnash::media::MediaHandler
> media_handler
;
60 std::shared_ptr
<gnash::sound::sound_handler
> sound_handler
;
62 /// Handlers (for sound etc) for a libcore run.
64 /// This must be kept alive for the entire lifetime of the movie_root
65 /// (currently: of the Gui).
66 std::unique_ptr
<gnash::RunResources
> run_info
;
68 boost::intrusive_ptr
<gnash::movie_definition
> movie_definition
;
70 std::unique_ptr
<gnash::movie_root
> stage
;
71 std::unique_ptr
<gnash::SystemClock
> system_clock
;
72 std::unique_ptr
<gnash::InterruptableVirtualClock
> virtual_clock
;
75 G_DEFINE_TYPE(GnashView
, gnash_view
, GTK_TYPE_BIN
)
77 static GObjectClass
*parent_class
= nullptr;
79 static void gnash_view_class_init(GnashViewClass
*gnash_view_class
);
80 static void gnash_view_init(GnashView
*view
);
81 static void gnash_view_size_allocate (GtkWidget
*widget
, GtkAllocation
*allocation
);
82 static void gnash_view_size_request (GtkWidget
*widget
, GtkRequisition
*requisition
);
83 static void gnash_view_set_property (GObject
*object
, guint prop_id
, const GValue
*value
, GParamSpec
*pspec
);
84 static void gnash_view_get_property (GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
);
85 static void gnash_view_realize_cb(GtkWidget
*widget
, gpointer user_data
);
87 static gboolean
key_press_event_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
);
88 static gboolean
key_release_event_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
);
89 static gboolean
button_press_event_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
);
90 static gboolean
button_release_event_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
);
91 static gboolean
motion_notify_event_cb(GtkWidget
*widget
, GdkEventMotion
*event
, gpointer data
);
93 static gnash::key::code
gdk_to_gnash_key(guint key
);
94 static gboolean
gnash_view_advance_movie(GnashView
*view
);
95 static void gnash_view_display(GnashView
*view
);
96 static void gnash_view_load_movie(GnashView
*view
, const gchar
*path
);
101 return GTK_WIDGET(g_object_new (GNASH_TYPE_VIEW
, nullptr));
105 gnash_view_call (GnashView
*view
, const gchar
*func_name
, const gchar
*input_data
)
107 gnash::VM
& vm
= view
->stage
->getVM();
110 gnash::as_value func
= getMember(*getObject(view
->movie
), getURI(vm
, func_name
));
112 if( !func
.is_function() ) {
116 gnash::as_value result
;
118 result
= callMethod(getObject(view
->movie
),
119 getURI(vm
, func_name
), gnash::as_value(input_data
));
121 result
= callMethod(getObject(view
->movie
), getURI(vm
, func_name
));
123 if( !result
.is_string() ) {
127 return result
.to_string().c_str();
131 gnash_view_class_init(GnashViewClass
*gnash_view_class
)
133 GNASH_REPORT_FUNCTION
;
135 parent_class
= (GObjectClass
*) g_type_class_peek_parent(gnash_view_class
);
137 GObjectClass
*g_object_class
= G_OBJECT_CLASS (gnash_view_class
);
138 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS (gnash_view_class
);
140 widget_class
->size_allocate
= gnash_view_size_allocate
;
141 widget_class
->size_request
= gnash_view_size_request
;
143 g_object_class
->get_property
= gnash_view_get_property
;
144 g_object_class
->set_property
= gnash_view_set_property
;
146 g_object_class_install_property (g_object_class
,
148 g_param_spec_string ("uri",
150 "URI to the SWF movie to display",
152 (GParamFlags
)G_PARAM_READWRITE
));
156 gnash_view_set_property (GObject
*object
,
161 GnashView
*view
= GNASH_VIEW (object
);
166 if(view
->movie_definition
.get() != nullptr) {
167 g_warning("Cannot change the movie URI once the view has been initialized.");
170 view
->uri
= g_strdup(g_value_get_string (value
));
173 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
178 gnash_view_get_property (GObject
*object
,
183 GnashView
*view
= GNASH_VIEW (object
);
188 g_value_set_string (value
, view
->uri
);
191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
196 gnash_view_init(GnashView
*view
)
198 GNASH_REPORT_FUNCTION
;
201 view
->advance_timer
= 0;
203 g_signal_connect (GTK_WIDGET(view
), "realize",
204 G_CALLBACK (gnash_view_realize_cb
), NULL
);
206 // Initializations that can happen before realization come here. The rest
207 // come after realize, in gnash_view_realize_cb.
208 gnash::LogFile
& dbglogfile
= gnash::LogFile::getDefaultInstance();
209 dbglogfile
.setVerbosity(3);
211 // Use the default media handler.
212 // TODO: allow setting this.
213 view
->media_handler
.reset(gnash::media::MediaFactory::instance().get(""));
218 view
->sound_handler
.reset(gnash::sound::create_sound_handler_sdl(
219 view
->media_handler
.get()));
220 } catch (gnash::SoundException
& ex
) {
221 gnash::log_error(_("Could not create sound handler: %s."
222 " Will continue without sound."), ex
.what());
224 gnash::log_error(_("Sound requested but no sound support compiled in"));
227 view
->canvas
= GNASH_CANVAS(gnash_canvas_new());
229 gnash_canvas_setup(view
->canvas
, nullstr
, nullstr
, 0, nullptr);
230 gtk_container_add (GTK_CONTAINER (view
), GTK_WIDGET(view
->canvas
));
231 gtk_widget_show (GTK_WIDGET(view
->canvas
));
233 gtk_widget_add_events(GTK_WIDGET(view
->canvas
), GDK_BUTTON_PRESS_MASK
234 | GDK_BUTTON_RELEASE_MASK
235 | GDK_KEY_RELEASE_MASK
237 | GDK_POINTER_MOTION_MASK
);
239 g_signal_connect_object(GTK_WIDGET(view
->canvas
), "key-press-event",
240 G_CALLBACK(key_press_event_cb
), view
, (GConnectFlags
)0);
241 g_signal_connect_object(GTK_WIDGET(view
->canvas
), "key-release-event",
242 G_CALLBACK(key_release_event_cb
), view
, (GConnectFlags
)0);
243 g_signal_connect_object(GTK_WIDGET(view
->canvas
), "button-press-event",
244 G_CALLBACK(button_press_event_cb
), view
, (GConnectFlags
)0);
245 g_signal_connect_object(GTK_WIDGET(view
->canvas
), "button-release-event",
246 G_CALLBACK(button_release_event_cb
), view
, (GConnectFlags
)0);
247 g_signal_connect_object(GTK_WIDGET(view
->canvas
), "motion-notify-event",
248 G_CALLBACK(motion_notify_event_cb
), view
, (GConnectFlags
)0);
252 gnash_view_size_allocate (GtkWidget
*widget
, GtkAllocation
*allocation
)
254 GnashView
*view
= GNASH_VIEW(widget
);
255 widget
->allocation
= *allocation
;
256 gtk_widget_size_allocate (GTK_BIN(widget
)->child
, allocation
);
258 if( view
->stage
.get() != nullptr) {
259 view
->stage
->setDimensions(allocation
->width
, allocation
->height
);
261 std::shared_ptr
<gnash::Renderer
> renderer
= gnash_canvas_get_renderer(view
->canvas
);
262 float xscale
= allocation
->width
/ view
->movie_definition
->get_width_pixels();
263 float yscale
= allocation
->height
/ view
->movie_definition
->get_height_pixels();
264 renderer
->set_scale(xscale
, yscale
);
269 gnash_view_size_request (GtkWidget
*widget
, GtkRequisition
*requisition
)
271 GnashView
*view
= GNASH_VIEW(widget
);
272 if( view
->movie_definition
.get() == nullptr ) {
273 requisition
->width
= 0;
274 requisition
->height
= 0;
276 requisition
->width
= view
->movie_definition
->get_width_pixels();
277 requisition
->height
= view
->movie_definition
->get_height_pixels();
282 gnash_view_realize_cb(GtkWidget
*widget
, gpointer
/*user_data*/)
284 GNASH_REPORT_FUNCTION
;
285 GnashView
*view
= GNASH_VIEW(widget
);
287 // Some initializations need to happen after the widget has been realized.
288 if(view
->movie_definition
.get() == nullptr) {
289 gtk_widget_realize(GTK_WIDGET(view
->canvas
));
290 gnash_view_load_movie(view
, view
->uri
);
295 key_press_event_cb(GtkWidget */
*widget*/
, GdkEventKey
*event
, gpointer data
)
297 GNASH_REPORT_FUNCTION
;
298 GnashView
*view
= GNASH_VIEW(data
);
299 if (view
->stage
.get() == nullptr)
302 gnash::key::code c
= gdk_to_gnash_key(event
->keyval
);
304 if (c
!= gnash::key::INVALID
) {
305 if( view
->stage
->keyEvent(c
, true) )
306 gnash_view_display(view
);
314 key_release_event_cb(GtkWidget */
*widget*/
, GdkEventKey
*event
, gpointer data
)
316 GNASH_REPORT_FUNCTION
;
317 GnashView
*view
= GNASH_VIEW(data
);
318 if (view
->stage
.get() == nullptr)
321 gnash::key::code c
= gdk_to_gnash_key(event
->keyval
);
323 if (c
!= gnash::key::INVALID
) {
324 if( view
->stage
->keyEvent(c
, false) )
325 gnash_view_display(view
);
333 button_press_event_cb(GtkWidget */
*widget*/
, GdkEventButton
*event
, gpointer data
)
335 GNASH_REPORT_FUNCTION
;
336 GnashView
*view
= GNASH_VIEW(data
);
337 if (view
->stage
.get() == nullptr)
340 /// Double- and triple-clicks should not send an extra event!
341 /// Flash has no built-in double click.
342 if (event
->type
!= GDK_BUTTON_PRESS
) return FALSE
;
344 gtk_widget_grab_focus(GTK_WIDGET(view
->canvas
));
346 view
->stage
->mouseClick(true);
352 button_release_event_cb(GtkWidget
* /*widget*/, GdkEventButton
* /*event*/,
355 GNASH_REPORT_FUNCTION
;
356 GnashView
*view
= GNASH_VIEW(data
);
357 if (view
->stage
.get() == nullptr)
360 view
->stage
->mouseClick(false);
366 motion_notify_event_cb(GtkWidget */
*widget*/
, GdkEventMotion
*event
, gpointer data
)
368 //GNASH_REPORT_FUNCTION;
370 GtkWidget
*widget
= GTK_WIDGET(data
);
371 GnashView
*view
= GNASH_VIEW(data
);
372 float xscale
= widget
->allocation
.width
/ view
->movie_definition
->get_width_pixels();
373 float yscale
= widget
->allocation
.height
/ view
->movie_definition
->get_height_pixels();
375 // A stage pseudopixel is user pixel / _xscale wide
376 std::int32_t x
= event
->x
/ xscale
;
378 // A stage pseudopixel is user pixel / _yscale high
379 std::int32_t y
= event
->y
/ yscale
;
381 if ( view
->stage
->mouseMoved(x
, y
) )
383 // any action triggered by the
384 // event required screen refresh
385 gnash_view_display(view
);
388 gnash::DisplayObject
* activeEntity
= view
->stage
->getActiveEntityUnderPointer();
391 if ( activeEntity
->isSelectableTextField() )
393 GdkCursor
*gdkcursor
= gdk_cursor_new(GDK_XTERM
);
394 gdk_window_set_cursor (widget
->window
, nullptr);
395 gdk_cursor_unref(gdkcursor
);
397 else if ( activeEntity
->allowHandCursor() )
399 GdkCursor
*gdkcursor
= gdk_cursor_new(GDK_HAND2
);
400 gdk_window_set_cursor (widget
->window
, nullptr);
401 gdk_cursor_unref(gdkcursor
);
405 gdk_window_set_cursor (widget
->window
, nullptr);
410 gdk_window_set_cursor (widget
->window
, nullptr);
417 gnash_view_load_movie(GnashView
*view
, const gchar
*uri
)
422 // The RunResources should be populated before parsing.
423 view
->run_info
.reset(new gnash::RunResources());
424 view
->run_info
->setSoundHandler(view
->sound_handler
);
426 std::unique_ptr
<gnash::NamingPolicy
> np(new gnash::IncrementalRename(url
));
427 std::shared_ptr
<gnash::StreamProvider
> sp(
428 new gnash::StreamProvider(url
, url
, std::move(np
)));
429 view
->run_info
->setStreamProvider(sp
);
431 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
433 if ( url
.protocol() == "file" ) {
434 const std::string
& str_path(url
.path());
436 size_t lastSlash
= str_path
.find_last_of('/');
437 std::string dir
= str_path
.substr(0, lastSlash
+ 1);
438 rcfile
.addLocalSandboxPath(dir
);
439 gnash::log_debug(_("%s appended to local sandboxes"), dir
.c_str());
441 rcfile
.addLocalSandboxPath(str_path
);
442 gnash::log_debug(_("%s appended to local sandboxes"), str_path
.c_str());
445 // Load the actual movie.
446 view
->movie_definition
= gnash::MovieFactory::makeMovie(url
,
447 *view
->run_info
, url
.str().c_str(), false);
449 g_return_if_fail(view
->movie_definition
.get() != nullptr);
451 // NOTE: it's important that _systemClock is constructed
452 // before and destroyed after _virtualClock !
453 view
->system_clock
.reset(new gnash::SystemClock());
454 view
->virtual_clock
.reset(new gnash::InterruptableVirtualClock(*view
->system_clock
));
455 view
->stage
.reset(new gnash::movie_root(*view
->virtual_clock
, *view
->run_info
));
457 view
->movie_definition
->completeLoad();
459 view
->advance_timer
= g_timeout_add_full(G_PRIORITY_LOW
, 10,
460 (GSourceFunc
)gnash_view_advance_movie
, view
, nullptr);
462 gtk_widget_queue_resize (GTK_WIDGET(view
));
464 //view->movie.reset (view->movie_definition->createMovie());
466 std::map
<std::string
, std::string
> variables
;
467 gnash::URL::parse_querystring(url
.querystring(), variables
);
469 gnash::Movie
* m
= view
->stage
->init(view
->movie_definition
.get(), variables
);
472 view
->stage
->set_background_alpha(1.0f
);
474 // @todo since we registered the sound handler, shouldn't we know
475 // already what it is ?!
476 gnash::sound::sound_handler
* s
= view
->stage
->runResources().soundHandler();
477 if ( s
) s
->unpause();
479 gnash::log_debug("Starting virtual clock");
480 view
->virtual_clock
->resume();
482 gnash_view_advance_movie(view
);
486 gnash_view_advance_movie(GnashView
*view
)
488 view
->stage
->advance();
490 gnash_view_display(view
);
496 gnash_view_display(GnashView
*view
)
498 gnash::InvalidatedRanges changed_ranges
;
499 changed_ranges
.setWorld();
501 std::shared_ptr
<gnash::Renderer
> renderer
= gnash_canvas_get_renderer(view
->canvas
);
502 renderer
->set_invalidated_regions(changed_ranges
);
503 gdk_window_invalidate_rect(GTK_WIDGET(view
->canvas
)->window
, nullptr, false);
505 gnash_canvas_before_rendering(view
->canvas
, view
->stage
.get());
506 view
->stage
->display();
508 gdk_window_process_updates(GTK_WIDGET(view
->canvas
)->window
, false);
511 static gnash::key::code
512 gdk_to_gnash_key(guint key
)
514 gnash::key::code
c(gnash::key::INVALID
);
516 // ascii 32-126 in one range:
517 if (key
>= GDK_space
&& key
<= GDK_asciitilde
) {
518 c
= (gnash::key::code
) ((key
- GDK_space
) + gnash::key::SPACE
);
522 else if (key
>= GDK_F1
&& key
<= GDK_F15
) {
523 c
= (gnash::key::code
) ((key
- GDK_F1
) + gnash::key::F1
);
527 else if (key
>= GDK_KP_0
&& key
<= GDK_KP_9
) {
528 c
= (gnash::key::code
) ((key
- GDK_KP_0
) + gnash::key::KP_0
);
532 else if (key
>= GDK_nobreakspace
&& key
<= GDK_ydiaeresis
) {
533 c
= (gnash::key::code
) ((key
- GDK_nobreakspace
) +
534 gnash::key::NOBREAKSPACE
);
537 // non-character keys don't correlate, so use a look-up table.
543 { GDK_BackSpace
, gnash::key::BACKSPACE
},
544 { GDK_Tab
, gnash::key::TAB
},
545 { GDK_Clear
, gnash::key::CLEAR
},
546 { GDK_Return
, gnash::key::ENTER
},
548 { GDK_Shift_L
, gnash::key::SHIFT
},
549 { GDK_Shift_R
, gnash::key::SHIFT
},
550 { GDK_Control_L
, gnash::key::CONTROL
},
551 { GDK_Control_R
, gnash::key::CONTROL
},
552 { GDK_Alt_L
, gnash::key::ALT
},
553 { GDK_Alt_R
, gnash::key::ALT
},
554 { GDK_Caps_Lock
, gnash::key::CAPSLOCK
},
556 { GDK_Escape
, gnash::key::ESCAPE
},
558 { GDK_Page_Down
, gnash::key::PGDN
},
559 { GDK_Page_Up
, gnash::key::PGUP
},
560 { GDK_Home
, gnash::key::HOME
},
561 { GDK_End
, gnash::key::END
},
562 { GDK_Left
, gnash::key::LEFT
},
563 { GDK_Up
, gnash::key::UP
},
564 { GDK_Right
, gnash::key::RIGHT
},
565 { GDK_Down
, gnash::key::DOWN
},
566 { GDK_Insert
, gnash::key::INSERT
},
567 { GDK_Delete
, gnash::key::DELETEKEY
},
569 { GDK_Help
, gnash::key::HELP
},
570 { GDK_Num_Lock
, gnash::key::NUM_LOCK
},
572 { GDK_VoidSymbol
, gnash::key::INVALID
}
575 for (int i
= 0; table
[i
].gdk
!= GDK_VoidSymbol
; i
++) {
576 if (key
== table
[i
].gdk
) {
587 static int gdk_to_gnash_modifier(int state
);
589 gdk_to_gnash_modifier(int state
)
591 int modifier
= gnash::key::GNASH_MOD_NONE
;
593 if (state
& GDK_SHIFT_MASK
) {
594 modifier
= modifier
| gnash::key::GNASH_MOD_SHIFT
;
596 if (state
& GDK_CONTROL_MASK
) {
597 modifier
= modifier
| gnash::key::GNASH_MOD_CONTROL
;
599 if (state
& GDK_MOD1_MASK
) {
600 modifier
= modifier
| gnash::key::GNASH_MOD_ALT
;