big merge from master, fix rpm creation, drop fetching swfdec
[gnash.git] / gui / pythonmod / gnash-view.cpp
blobf58b1c6a1bb5718088b67241b3af2760fe24f173
1 // gnash-view.cpp: Gtk view widget for gnash
2 //
3 // Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
4 //
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.
9 //
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"
23 #include <gdk/gdk.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <gtk/gtkbutton.h>
27 #include "log.h"
29 #include "VM.h"
30 #include "movie_definition.h"
31 #include "MovieFactory.h"
32 #include "movie_root.h" // for Abstract callbacks
33 #include "Renderer.h"
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 "smart_ptr.h"
40 #include "DisplayObject.h"
41 #include "Global_as.h"
42 #include "NamingPolicy.h"
43 #include "StreamProvider.h"
45 enum
47 PROP_0,
48 PROP_URI
51 struct _GnashView {
52 GtkBin base_instance;
54 GnashCanvas *canvas;
55 const gchar *uri;
56 guint advance_timer;
58 boost::shared_ptr<gnash::media::MediaHandler> media_handler;
59 boost::shared_ptr<gnash::sound::sound_handler> sound_handler;
61 /// Handlers (for sound etc) for a libcore run.
63 /// This must be kept alive for the entire lifetime of the movie_root
64 /// (currently: of the Gui).
65 std::auto_ptr<gnash::RunResources> run_info;
67 boost::intrusive_ptr<gnash::movie_definition> movie_definition;
68 gnash::Movie* movie;
69 std::auto_ptr<gnash::movie_root> stage;
70 std::auto_ptr<gnash::SystemClock> system_clock;
71 std::auto_ptr<gnash::InterruptableVirtualClock> virtual_clock;
74 G_DEFINE_TYPE(GnashView, gnash_view, GTK_TYPE_BIN)
76 static GObjectClass *parent_class = NULL;
78 static void gnash_view_class_init(GnashViewClass *gnash_view_class);
79 static void gnash_view_init(GnashView *view);
80 static void gnash_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
81 static void gnash_view_size_request (GtkWidget *widget, GtkRequisition *requisition);
82 static void gnash_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
83 static void gnash_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
84 static void gnash_view_realize_cb(GtkWidget *widget, gpointer user_data);
86 static gboolean key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data);
87 static gboolean key_release_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data);
88 static gboolean button_press_event_cb(GtkWidget *widget, GdkEventButton *event, gpointer data);
89 static gboolean button_release_event_cb(GtkWidget *widget, GdkEventButton *event, gpointer data);
90 static gboolean motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event, gpointer data);
92 static gnash::key::code gdk_to_gnash_key(guint key);
93 static int gdk_to_gnash_modifier(int state);
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);
98 GtkWidget *
99 gnash_view_new (void)
101 return GTK_WIDGET(g_object_new (GNASH_TYPE_VIEW, NULL));
104 const gchar *
105 gnash_view_call (GnashView *view, const gchar *func_name, const gchar *input_data)
107 gnash::VM& vm = view->stage->getVM();
108 gnash::as_value obj;
110 gnash::as_value func = getMember(*getObject(view->movie), getURI(vm, func_name));
112 if( !func.is_function() ) {
113 return NULL;
116 gnash::as_value result;
117 if( input_data ) {
118 result = callMethod(getObject(view->movie),
119 getURI(vm, func_name), gnash::as_value(input_data));
120 } else {
121 result = callMethod(getObject(view->movie), getURI(vm, func_name));
123 if( !result.is_string() ) {
124 return NULL;
127 return result.to_string().c_str();
130 static void
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,
147 PROP_URI,
148 g_param_spec_string ("uri",
149 "URI to movie",
150 "URI to the SWF movie to display",
151 NULL,
152 (GParamFlags)G_PARAM_READWRITE));
155 static void
156 gnash_view_set_property (GObject *object,
157 guint prop_id,
158 const GValue *value,
159 GParamSpec *pspec)
161 GnashView *view = GNASH_VIEW (object);
163 switch (prop_id)
165 case PROP_URI:
166 if(view->movie_definition.get() != NULL) {
167 g_warning("Cannot change the movie URI once the view has been initialized.");
168 return;
170 view->uri = g_strdup(g_value_get_string (value));
171 break;
172 default:
173 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
177 static void
178 gnash_view_get_property (GObject *object,
179 guint prop_id,
180 GValue *value,
181 GParamSpec *pspec)
183 GnashView *view = GNASH_VIEW (object);
185 switch (prop_id)
187 case PROP_URI:
188 g_value_set_string (value, view->uri);
189 break;
190 default:
191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
195 static void
196 gnash_view_init(GnashView *view)
198 GNASH_REPORT_FUNCTION;
200 view->uri = NULL;
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(""));
215 // Init sound
216 #ifdef SOUND_SDL
217 try {
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 w/out sound."), ex.what());
224 gnash::log_error(_("Sound requested but no sound support compiled in"));
225 #endif
227 view->canvas = GNASH_CANVAS(gnash_canvas_new());
228 std::string nullstr;
229 gnash_canvas_setup(view->canvas, nullstr, nullstr, 0, NULL);
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
236 | GDK_KEY_PRESS_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);
251 static void
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() != NULL) {
259 view->stage->setDimensions(allocation->width, allocation->height);
261 boost::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);
268 static void
269 gnash_view_size_request (GtkWidget *widget, GtkRequisition *requisition)
271 GnashView *view = GNASH_VIEW(widget);
272 if( view->movie_definition.get() == NULL ) {
273 requisition->width = 0;
274 requisition->height = 0;
275 } else {
276 requisition->width = view->movie_definition->get_width_pixels();
277 requisition->height = view->movie_definition->get_height_pixels();
281 static void
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() == NULL) {
289 gtk_widget_realize(GTK_WIDGET(view->canvas));
290 gnash_view_load_movie(view, view->uri);
294 static gboolean
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() == NULL)
300 return FALSE;
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);
307 return TRUE;
310 return FALSE;
313 static gboolean
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() == NULL)
319 return FALSE;
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);
326 return TRUE;
329 return FALSE;
332 static gboolean
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() == NULL)
338 return FALSE;
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);
348 return TRUE;
351 static gboolean
352 button_release_event_cb(GtkWidget* /*widget*/, GdkEventButton* /*event*/,
353 gpointer data)
355 GNASH_REPORT_FUNCTION;
356 GnashView *view = GNASH_VIEW(data);
357 if (view->stage.get() == NULL)
358 return FALSE;
360 view->stage->mouseClick(false);
362 return TRUE;
365 static gboolean
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 boost::int32_t x = event->x / xscale;
378 // A stage pseudopixel is user pixel / _yscale high
379 boost::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();
389 if ( activeEntity )
391 if ( activeEntity->isSelectableTextField() )
393 GdkCursor *gdkcursor = gdk_cursor_new(GDK_XTERM);
394 gdk_window_set_cursor (widget->window, NULL);
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, NULL);
401 gdk_cursor_unref(gdkcursor);
403 else
405 gdk_window_set_cursor (widget->window, NULL);
408 else
410 gdk_window_set_cursor (widget->window, NULL);
413 return TRUE;
416 static void
417 gnash_view_load_movie(GnashView *view, const gchar *uri)
420 gnash::URL url(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::auto_ptr<gnash::NamingPolicy> np(new gnash::IncrementalRename(url));
427 boost::shared_ptr<gnash::StreamProvider> sp(
428 new gnash::StreamProvider(url, url, 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() != NULL);
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->movie_definition, *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, NULL);
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);
470 view->movie = m;
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);
485 static gboolean
486 gnash_view_advance_movie(GnashView *view)
488 view->stage->advance();
490 gnash_view_display(view);
492 return TRUE;
495 static void
496 gnash_view_display(GnashView *view)
498 gnash::InvalidatedRanges changed_ranges;
499 changed_ranges.setWorld();
501 boost::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, NULL, 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);
521 // Function keys:
522 else if (key >= GDK_F1 && key <= GDK_F15) {
523 c = (gnash::key::code) ((key - GDK_F1) + gnash::key::F1);
526 // Keypad:
527 else if (key >= GDK_KP_0 && key <= GDK_KP_9) {
528 c = (gnash::key::code) ((key - GDK_KP_0) + gnash::key::KP_0);
531 // Extended ascii:
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.
538 else {
539 struct {
540 guint gdk;
541 gnash::key::code gs;
542 } 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) {
577 c = table[i].gs;
578 break;
583 return c;
586 static int
587 gdk_to_gnash_modifier(int state)
589 int modifier = gnash::key::GNASH_MOD_NONE;
591 if (state & GDK_SHIFT_MASK) {
592 modifier = modifier | gnash::key::GNASH_MOD_SHIFT;
594 if (state & GDK_CONTROL_MASK) {
595 modifier = modifier | gnash::key::GNASH_MOD_CONTROL;
597 if (state & GDK_MOD1_MASK) {
598 modifier = modifier | gnash::key::GNASH_MOD_ALT;
601 return modifier;