eb7a088816189a14e6a89f7c61f20642b50ece20
[rugen] / src / page_clips.c
1 /*
2 * page_clips.c -- Clips Page Handling
3 * Copyright (C) 2002-2003 Charles Yates <charles.yates@pandora.be>
4 * Copyright (C) 2010 Dan Dennedy <dan@dennedy.org>
5 *
6 * This program 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 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program 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 this program; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30
31 #include "interface.h"
32 #include "support.h"
33 #include "dv1394app.h"
34 #include "page.h"
35
36 typedef struct
37 {
38 struct page_t parent;
39 dv1394app app;
40 GtkWidget *widget;
41 mvcp dv;
42 char *path;
43 int unit;
44 int generation;
45 int clip;
46
47 // TODO: This comes out later
48 int mode;
49 GtkWidget *modes[ 4 ];
50
51 struct page_t *parent_page;
52 }
53 *page_clips, page_clips_t;
54
55 static GtkWidget *this_page_get_widget( page_clips this );
56
57 static void list_clips( page_clips this, char *path )
58 {
59 GtkWidget *treeview = lookup_widget( this_page_get_widget( this ), "list_clips" );
60 GtkWidget *dirview = lookup_widget( this_page_get_widget( this ), "list_dir" );
61 GtkWidget *label = lookup_widget( this_page_get_widget( this ), "label_directory" );
62 if ( path != NULL )
63 {
64 mvcp_dir dir = mvcp_dir_init( this->dv, path );
65 GtkListStore *dir_store = NULL;
66 GtkListStore *clip_store = NULL;
67 GtkTreeIter iter;
68 int index;
69 mvcp_dir_entry_t entry;
70
71 free( this->path );
72 this->path = strdup( path );
73
74 gtk_label_set_text( GTK_LABEL( label ), this->path );
75
76 if ( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) == NULL )
77 {
78 GtkCellRenderer *renderer;
79 GtkTreeViewColumn *column;
80
81 clip_store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING );
82 gtk_tree_view_set_model( GTK_TREE_VIEW( treeview ), GTK_TREE_MODEL( clip_store ) );
83
84 renderer = gtk_cell_renderer_text_new( );
85 column = gtk_tree_view_column_new_with_attributes ( "Description", renderer, "text", 0, NULL);
86 gtk_tree_view_column_set_sort_column_id( column, 0 );
87 gtk_tree_view_append_column( GTK_TREE_VIEW( treeview ), column );
88
89 dir_store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING );
90 gtk_tree_view_set_model( GTK_TREE_VIEW( dirview ), GTK_TREE_MODEL( dir_store ) );
91
92 renderer = gtk_cell_renderer_text_new( );
93 column = gtk_tree_view_column_new_with_attributes ( "Description", renderer, "text", 0, NULL);
94 gtk_tree_view_column_set_sort_column_id( column, 0 );
95 gtk_tree_view_append_column( GTK_TREE_VIEW( dirview ), column );
96 }
97 else
98 {
99 clip_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) );
100 dir_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( dirview ) ) );
101 gtk_list_store_clear( clip_store );
102 gtk_list_store_clear( dir_store );
103 }
104
105 if ( strcmp( path, "/" ) )
106 {
107 gtk_list_store_append( clip_store, &iter );
108 gtk_list_store_set( clip_store, &iter, 0, "..", -1 );
109 gtk_list_store_append( dir_store, &iter );
110 gtk_list_store_set( dir_store, &iter, 0, "..", -1 );
111 }
112
113 for ( index = 0; index < mvcp_dir_count( dir ); index ++ )
114 {
115 mvcp_dir_get( dir, index, &entry );
116 if ( strchr( entry.name, '/' ) )
117 {
118 gtk_list_store_append( dir_store, &iter );
119 gtk_list_store_set( dir_store, &iter, 0, entry.name, -1 );
120 }
121 else
122 {
123 gtk_list_store_append( clip_store, &iter );
124 gtk_list_store_set( clip_store, &iter, 0, entry.name, -1 );
125 }
126 }
127
128 mvcp_dir_close( dir );
129 }
130 else
131 {
132 gtk_label_set_text( GTK_LABEL( label ), "Disconnected" );
133 if ( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) )
134 {
135 GtkListStore *list_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) );
136 gtk_list_store_clear( list_store );
137 list_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( dirview ) ) );
138 gtk_list_store_clear( list_store );
139 treeview = lookup_widget( this_page_get_widget( this ), "treeview1" );
140 gtk_list_store_clear( list_store );
141 }
142 }
143 }
144
145 static void list_queue( page_clips this, int clip )
146 {
147 GtkWidget *treeview = lookup_widget( this_page_get_widget( this ), "treeview1" );
148 mvcp_list list = mvcp_list_init( this->dv, dv1394app_get_selected_unit( this->app ) );
149 GtkListStore *list_store = NULL;
150 GtkTreeIter iter;
151 GtkTreePath *path;
152 int index;
153 mvcp_list_entry_t entry;
154
155 if ( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) == NULL )
156 {
157 GtkCellRenderer *renderer;
158 GtkTreeViewColumn *column;
159
160 list_store = gtk_list_store_new( 6,
161 G_TYPE_BOOLEAN,
162 G_TYPE_STRING,
163 G_TYPE_STRING,
164 G_TYPE_STRING,
165 G_TYPE_STRING,
166 G_TYPE_INT );
167 gtk_tree_view_set_model( GTK_TREE_VIEW( treeview ), GTK_TREE_MODEL( list_store ) );
168
169 renderer = gtk_cell_renderer_toggle_new( );
170 column = gtk_tree_view_column_new_with_attributes ( "", renderer, "active", 0, NULL);
171 gtk_tree_view_append_column( GTK_TREE_VIEW( treeview ), column );
172
173 renderer = gtk_cell_renderer_text_new( );
174 column = gtk_tree_view_column_new_with_attributes ( "Clip", renderer, "text", 1, NULL);
175 gtk_tree_view_append_column( GTK_TREE_VIEW( treeview ), column );
176
177 renderer = gtk_cell_renderer_text_new( );
178 column = gtk_tree_view_column_new_with_attributes ( "In", renderer, "text", 2, NULL);
179 gtk_tree_view_append_column( GTK_TREE_VIEW( treeview ), column );
180
181 renderer = gtk_cell_renderer_text_new( );
182 column = gtk_tree_view_column_new_with_attributes ( "Out", renderer, "text", 3, NULL);
183 gtk_tree_view_append_column( GTK_TREE_VIEW( treeview ), column );
184
185 renderer = gtk_cell_renderer_text_new( );
186 column = gtk_tree_view_column_new_with_attributes ( "Length", renderer, "text", 4, NULL);
187 gtk_tree_view_append_column( GTK_TREE_VIEW( treeview ), column );
188
189 }
190 else
191 {
192 list_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) );
193 gtk_list_store_clear( list_store );
194 }
195
196 this->generation = list->generation;
197
198 for ( index = 0; index < mvcp_list_count( list ); index ++ )
199 {
200 char tc1[12], tc2[12], tc3[12];
201 mvcp_list_get( list, index, &entry );
202 gtk_list_store_append( list_store, &iter );
203 gtk_list_store_set( list_store, &iter,
204 0, index == clip,
205 1, entry.full,
206 2, frames2tc( entry.in, entry.fps, tc1),
207 3, frames2tc( entry.out, entry.fps, tc2),
208 4, frames2tc( entry.size, entry.fps, tc3),
209 5, entry.clip,
210 -1 );
211 }
212
213 this->clip = clip;
214 if ( clip < mvcp_list_count( list ) )
215 {
216 path = gtk_tree_path_new_from_indices( this->clip, -1 );
217 gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW( treeview ), path, NULL, TRUE, 0.5, 0 );
218 gtk_tree_path_free( path );
219 }
220
221 mvcp_list_close( list );
222 }
223
224 static void list_active( page_clips this, int clip )
225 {
226 GtkWidget *treeview = lookup_widget( this_page_get_widget( this ), "treeview1" );
227 GtkListStore *list_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) );
228 GtkTreePath *path = gtk_tree_path_new_from_indices( this->clip, -1 );
229 GtkTreeIter iter;
230
231 gtk_tree_model_get_iter( GTK_TREE_MODEL (list_store), &iter, path );
232 gtk_tree_path_free( path );
233 gtk_list_store_set( list_store, &iter, 0, FALSE, -1 );
234
235 this->clip = clip;
236 path = gtk_tree_path_new_from_indices( this->clip, -1 );
237 gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW( treeview ), path, NULL, TRUE, 0.5, 0 );
238 gtk_tree_model_get_iter( GTK_TREE_MODEL (list_store), &iter, path );
239 gtk_tree_path_free( path );
240 gtk_list_store_set( list_store, &iter, 0, TRUE, -1 );
241 }
242
243 static gboolean on_ok( GtkWidget *dummy, gpointer data )
244 {
245 page_clips this = data;
246 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "list_clips" );
247 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
248 GtkTreeModel *model;
249 GtkTreeIter iter;
250 gchar *text;
251
252 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
253 {
254 gtk_tree_model_get( model, &iter, 0, &text, -1 );
255
256 if ( !strcmp( text, ".." ) )
257 {
258 char *temp = strdup( this->path );
259 temp[ strlen( temp ) - 1 ] = '\0';
260 *( strrchr( temp, '/' ) + 1 ) = '\0';
261 list_clips( this, temp );
262 free( temp );
263 }
264 else
265 {
266 char *temp = malloc( strlen( this->path ) + strlen( text ) + 1 );
267 strcpy( temp, this->path );
268 strcat( temp, text );
269 switch( this->mode )
270 {
271 case 0:
272 mvcp_unit_load_back( this->dv, dv1394app_get_selected_unit( this->app ), temp );
273 mvcp_unit_play( this->dv, dv1394app_get_selected_unit( this->app ) );
274 break;
275 case 1:
276 mvcp_unit_load( this->dv, dv1394app_get_selected_unit( this->app ), temp );
277 break;
278 case 2:
279 mvcp_unit_append( this->dv, dv1394app_get_selected_unit( this->app ), temp, -1, -1 );
280 break;
281 case 3:
282 {
283 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
284 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
285 GtkTreeModel *model;
286 GtkTreeIter iter;
287 int clip;
288
289 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
290 {
291 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
292 mvcp_unit_clip_insert( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, temp, -1, -1 );
293 }
294 break;
295 }
296 }
297
298 free( temp );
299 }
300
301 g_free( text );
302 }
303
304 return TRUE;
305 }
306
307 static gboolean on_dir( GtkWidget *dummy, gpointer data )
308 {
309 page_clips this = data;
310 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "list_dir" );
311 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
312 GtkTreeModel *model;
313 GtkTreeIter iter;
314 gchar *text;
315
316 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
317 {
318 gtk_tree_model_get( model, &iter, 0, &text, -1 );
319
320 if ( !strcmp( text, ".." ) )
321 {
322 char *temp = strdup( this->path );
323 temp[ strlen( temp ) - 1 ] = '\0';
324 *( strrchr( temp, '/' ) + 1 ) = '\0';
325 list_clips( this, temp );
326 free( temp );
327 }
328 else if ( text[ strlen( text ) - 1 ] == '/' )
329 {
330 char *temp = malloc( strlen( this->path ) + strlen( text ) + 1 );
331 strcpy( temp, this->path );
332 strcat( temp, text );
333 list_clips( this, temp );
334 free( temp );
335 }
336
337 g_free( text );
338 }
339
340 return TRUE;
341 }
342
343 static gboolean on_queue_item( GtkWidget *dummy, gpointer data )
344 {
345 page_clips this = data;
346 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
347 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
348 GtkTreeModel *model;
349 GtkTreeIter iter;
350 int clip;
351
352 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
353 {
354 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
355 mvcp_unit_clip_goto( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, 0 );
356 mvcp_unit_play( this->dv, dv1394app_get_selected_unit( this->app ) );
357 }
358
359 return TRUE;
360 }
361
362 static gboolean on_clip_selected( GtkWidget *widget, GdkEventButton *event, gpointer data )
363 {
364 if ( event->button==1 && event->type==GDK_2BUTTON_PRESS )
365 return on_ok( widget, data );
366 return FALSE;
367 }
368
369 static gboolean on_clip_key_press( GtkWidget *widget, GdkEventKey *event, gpointer data )
370 {
371 if ( event->keyval == GDK_Return )
372 return on_ok( widget, data );
373 return FALSE;
374 }
375
376 static gboolean on_dir_selected( GtkWidget *widget, GdkEventButton *event, gpointer data )
377 {
378 if ( event->button==1 && event->type==GDK_2BUTTON_PRESS )
379 return on_dir( widget, data );
380 return FALSE;
381 }
382
383 static gboolean on_dir_key_press( GtkWidget *widget, GdkEventKey *event, gpointer data )
384 {
385 if ( event->keyval == GDK_Return )
386 return on_dir( widget, data );
387 return FALSE;
388 }
389
390 static gboolean on_queue_selected( GtkWidget *widget, GdkEventButton *event, gpointer data )
391 {
392 if ( event->button==1 && event->type==GDK_2BUTTON_PRESS )
393 return on_queue_item( widget, data );
394 return FALSE;
395 }
396
397 static gboolean on_queue_key_press( GtkWidget *widget, GdkEventKey *event, gpointer data )
398 {
399 if ( event->keyval == GDK_Return )
400 return on_queue_item( widget, data );
401 return FALSE;
402 }
403
404 static gboolean on_home( GtkWidget *button, gpointer data )
405 {
406 page_clips this = data;
407 list_clips( this, "/" );
408 return TRUE;
409 }
410
411 static gboolean on_refresh( GtkWidget *button, gpointer data )
412 {
413 page_clips this = data;
414 char *temp = strdup( this->path );
415 list_clips( this, temp );
416 free( temp );
417
418 return TRUE;
419 }
420
421 static gboolean on_up( GtkWidget *dummy, gpointer data )
422 {
423 page_clips this = data;
424 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
425 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
426 GtkTreeModel *model;
427 GtkTreeIter iter;
428 int clip;
429
430 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
431 {
432 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
433 mvcp_unit_clip_move( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, mvcp_absolute, clip - 1 < 0 ? 0 : clip - 1 );
434 }
435 return TRUE;
436 }
437
438 static gboolean on_down( GtkWidget *dummy, gpointer data )
439 {
440 page_clips this = data;
441 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
442 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
443 GtkTreeModel *model;
444 GtkTreeIter iter;
445 int clip;
446
447 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
448 {
449 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
450 mvcp_unit_clip_move( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, mvcp_absolute, clip + 1 );
451 }
452 return TRUE;
453 }
454
455 static gboolean on_remove( GtkWidget *dummy, gpointer data )
456 {
457 page_clips this = data;
458 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
459 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
460 GtkTreeModel *model;
461 GtkTreeIter iter;
462 int clip;
463
464 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
465 {
466 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
467 mvcp_unit_clip_remove( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip );
468 }
469 return TRUE;
470 }
471
472 static gboolean on_clean( GtkWidget *dummy, gpointer data )
473 {
474 page_clips this = data;
475 mvcp_unit_clean( this->dv, dv1394app_get_selected_unit( this->app ) );
476 return TRUE;
477 }
478
479 void on_mode_change( GtkMenuItem *menuitem, gpointer data )
480 {
481 page_clips this = data;
482 int index = 0;
483
484 for ( index = 0; index < 4; index ++ )
485 if ( GTK_WIDGET( menuitem ) == this->modes[ index ] )
486 break;
487
488 this->mode = index;
489 }
490
491 static GtkWidget *this_page_get_widget( page_clips this )
492 {
493 if ( this->widget == NULL )
494 this->widget = this->parent_page->get_widget(this->parent_page);
495 return this->widget;
496 }
497
498 static void this_page_get_toolbar_info( page this, GtkIconSize size, GtkWidget **icon, char **label )
499 {
500 *icon = gtk_image_new_from_stock( "gtk-justify-fill", size );
501 *label = _("_Playlist");
502 }
503
504 static void this_page_on_connect( page_clips this )
505 {
506 this->dv = mvcp_init( dv1394app_get_parser( this->app ) );
507 list_clips( this, "/" );
508 }
509
510 static void this_page_on_unit_change( page_clips this, int unit )
511 {
512 if ( unit != this->unit )
513 this->unit = unit;
514 }
515
516 static void this_page_on_disconnect( page_clips this )
517 {
518 list_clips( this, NULL );
519 mvcp_close( this->dv );
520 }
521
522 static void this_page_show_status( page_clips this, mvcp_status status )
523 {
524 if ( status->status != unit_disconnected )
525 {
526 if ( this->generation != status->generation )
527 list_queue( this, status->clip_index );
528 else if ( this->clip != status->clip_index )
529 list_active( this, status->clip_index );
530 }
531 }
532
533 static void this_page_close( page_clips this )
534 {
535 if ( this != NULL )
536 free( this );
537 }
538
539 page page_clips_init( dv1394app app, struct page_t *parent_page)
540 {
541 page_clips this = calloc( 1, sizeof( page_clips_t ) );
542 GtkWidget *widget;
543 int index = 0;
544
545 this->parent_page = parent_page;
546 this->parent.get_widget = ( GtkWidget *(*)( page ) )this_page_get_widget;
547 this->parent.get_toolbar_info = this_page_get_toolbar_info;
548 this->parent.on_connect = ( void (*)( page ) )this_page_on_connect;
549 this->parent.on_unit_change = ( void (*)( page, int ) )this_page_on_unit_change;
550 this->parent.on_disconnect = ( void (*)( page ) )this_page_on_disconnect;
551 this->parent.show_status = ( void (*)( page, mvcp_status ) )this_page_show_status;
552 this->parent.close = ( void (*)( page ) )this_page_close;
553 this->app = app;
554 this->generation = -1;
555
556 widget = lookup_widget( this_page_get_widget( this ), "list_clips" );
557 g_signal_connect( G_OBJECT( widget ), "button-press-event", G_CALLBACK( on_clip_selected ), this );
558 g_signal_connect( G_OBJECT( widget ), "key-press-event", G_CALLBACK( on_clip_key_press ), this );
559 widget = lookup_widget( this_page_get_widget( this ), "list_dir" );
560 g_signal_connect( G_OBJECT( widget ), "button-press-event", G_CALLBACK( on_dir_selected ), this );
561 g_signal_connect( G_OBJECT( widget ), "key-press-event", G_CALLBACK( on_dir_key_press ), this );
562 widget = lookup_widget( this_page_get_widget( this ), "button_clips_refresh" );
563 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_refresh ), this );
564 widget = lookup_widget( this_page_get_widget( this ), "button_clips_home" );
565 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_home ), this );
566 widget = lookup_widget( this_page_get_widget( this ), "button_up" );
567 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_up ), this );
568 widget = lookup_widget( this_page_get_widget( this ), "button_down" );
569 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_down ), this );
570 widget = lookup_widget( this_page_get_widget( this ), "button_remove" );
571 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_remove ), this );
572 widget = lookup_widget( this_page_get_widget( this ), "button_clean" );
573 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_clean ), this );
574
575 widget = lookup_widget( this_page_get_widget( this ), "table4" );
576 gtk_widget_show( widget );
577
578 widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
579 g_signal_connect( G_OBJECT( widget ), "button-press-event", G_CALLBACK( on_queue_selected ), this );
580 g_signal_connect( G_OBJECT( widget ), "key-press-event", G_CALLBACK( on_queue_key_press ), this );
581
582 for ( index = 0; index < 4; index ++ )
583 {
584 char item[ 256 ];
585 sprintf( item, "mode_%d", index );
586 widget = lookup_widget( this_page_get_widget( this ), item );
587 gtk_signal_connect( GTK_OBJECT( widget ), "activate", GTK_SIGNAL_FUNC( on_mode_change ), this );
588 this->modes[ index ] = widget;
589 }
590
591 return ( page )this;
592 }