join clips and status control to same 'operate' page
[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 ( "In", 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 ( "Out", 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 ( "Length", 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 ( "Clip", renderer, "text", 4, NULL);
187 gtk_tree_view_append_column( GTK_TREE_VIEW( treeview ), column );
188 }
189 else
190 {
191 list_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) );
192 gtk_list_store_clear( list_store );
193 }
194
195 this->generation = list->generation;
196
197 for ( index = 0; index < mvcp_list_count( list ); index ++ )
198 {
199 char tc1[12], tc2[12], tc3[12];
200 mvcp_list_get( list, index, &entry );
201 gtk_list_store_append( list_store, &iter );
202 gtk_list_store_set( list_store, &iter,
203 0, index == clip,
204 1, frames2tc( entry.in, entry.fps, tc1),
205 2, frames2tc( entry.out, entry.fps, tc2),
206 3, frames2tc( entry.size, entry.fps, tc3),
207 4, entry.full,
208 5, entry.clip,
209 -1 );
210 }
211
212 this->clip = clip;
213 if ( clip < mvcp_list_count( list ) )
214 {
215 path = gtk_tree_path_new_from_indices( this->clip, -1 );
216 gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW( treeview ), path, NULL, TRUE, 0.5, 0 );
217 gtk_tree_path_free( path );
218 }
219
220 mvcp_list_close( list );
221 }
222
223 static void list_active( page_clips this, int clip )
224 {
225 GtkWidget *treeview = lookup_widget( this_page_get_widget( this ), "treeview1" );
226 GtkListStore *list_store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW( treeview ) ) );
227 GtkTreePath *path = gtk_tree_path_new_from_indices( this->clip, -1 );
228 GtkTreeIter iter;
229
230 gtk_tree_model_get_iter( GTK_TREE_MODEL (list_store), &iter, path );
231 gtk_tree_path_free( path );
232 gtk_list_store_set( list_store, &iter, 0, FALSE, -1 );
233
234 this->clip = clip;
235 path = gtk_tree_path_new_from_indices( this->clip, -1 );
236 gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW( treeview ), path, NULL, TRUE, 0.5, 0 );
237 gtk_tree_model_get_iter( GTK_TREE_MODEL (list_store), &iter, path );
238 gtk_tree_path_free( path );
239 gtk_list_store_set( list_store, &iter, 0, TRUE, -1 );
240 }
241
242 static gboolean on_ok( GtkWidget *dummy, gpointer data )
243 {
244 page_clips this = data;
245 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "list_clips" );
246 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
247 GtkTreeModel *model;
248 GtkTreeIter iter;
249 gchar *text;
250
251 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
252 {
253 gtk_tree_model_get( model, &iter, 0, &text, -1 );
254
255 if ( !strcmp( text, ".." ) )
256 {
257 char *temp = strdup( this->path );
258 temp[ strlen( temp ) - 1 ] = '\0';
259 *( strrchr( temp, '/' ) + 1 ) = '\0';
260 list_clips( this, temp );
261 free( temp );
262 }
263 else
264 {
265 char *temp = malloc( strlen( this->path ) + strlen( text ) + 1 );
266 strcpy( temp, this->path );
267 strcat( temp, text );
268 switch( this->mode )
269 {
270 case 0:
271 mvcp_unit_load_back( this->dv, dv1394app_get_selected_unit( this->app ), temp );
272 mvcp_unit_play( this->dv, dv1394app_get_selected_unit( this->app ) );
273 break;
274 case 1:
275 mvcp_unit_load( this->dv, dv1394app_get_selected_unit( this->app ), temp );
276 break;
277 case 2:
278 mvcp_unit_append( this->dv, dv1394app_get_selected_unit( this->app ), temp, -1, -1 );
279 break;
280 case 3:
281 {
282 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
283 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
284 GtkTreeModel *model;
285 GtkTreeIter iter;
286 int clip;
287
288 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
289 {
290 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
291 mvcp_unit_clip_insert( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, temp, -1, -1 );
292 }
293 break;
294 }
295 }
296
297 free( temp );
298 }
299
300 g_free( text );
301 }
302
303 return TRUE;
304 }
305
306 static gboolean on_dir( GtkWidget *dummy, gpointer data )
307 {
308 page_clips this = data;
309 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "list_dir" );
310 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
311 GtkTreeModel *model;
312 GtkTreeIter iter;
313 gchar *text;
314
315 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
316 {
317 gtk_tree_model_get( model, &iter, 0, &text, -1 );
318
319 if ( !strcmp( text, ".." ) )
320 {
321 char *temp = strdup( this->path );
322 temp[ strlen( temp ) - 1 ] = '\0';
323 *( strrchr( temp, '/' ) + 1 ) = '\0';
324 list_clips( this, temp );
325 free( temp );
326 }
327 else if ( text[ strlen( text ) - 1 ] == '/' )
328 {
329 char *temp = malloc( strlen( this->path ) + strlen( text ) + 1 );
330 strcpy( temp, this->path );
331 strcat( temp, text );
332 list_clips( this, temp );
333 free( temp );
334 }
335
336 g_free( text );
337 }
338
339 return TRUE;
340 }
341
342 static gboolean on_queue_item( GtkWidget *dummy, gpointer data )
343 {
344 page_clips this = data;
345 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
346 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
347 GtkTreeModel *model;
348 GtkTreeIter iter;
349 int clip;
350
351 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
352 {
353 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
354 mvcp_unit_clip_goto( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, 0 );
355 mvcp_unit_play( this->dv, dv1394app_get_selected_unit( this->app ) );
356 }
357
358 return TRUE;
359 }
360
361 static gboolean on_clip_selected( GtkWidget *widget, GdkEventButton *event, gpointer data )
362 {
363 if ( event->button==1 && event->type==GDK_2BUTTON_PRESS )
364 return on_ok( widget, data );
365 return FALSE;
366 }
367
368 static gboolean on_clip_key_press( GtkWidget *widget, GdkEventKey *event, gpointer data )
369 {
370 if ( event->keyval == GDK_Return )
371 return on_ok( widget, data );
372 return FALSE;
373 }
374
375 static gboolean on_dir_selected( GtkWidget *widget, GdkEventButton *event, gpointer data )
376 {
377 if ( event->button==1 && event->type==GDK_2BUTTON_PRESS )
378 return on_dir( widget, data );
379 return FALSE;
380 }
381
382 static gboolean on_dir_key_press( GtkWidget *widget, GdkEventKey *event, gpointer data )
383 {
384 if ( event->keyval == GDK_Return )
385 return on_dir( widget, data );
386 return FALSE;
387 }
388
389 static gboolean on_queue_selected( GtkWidget *widget, GdkEventButton *event, gpointer data )
390 {
391 if ( event->button==1 && event->type==GDK_2BUTTON_PRESS )
392 return on_queue_item( widget, data );
393 return FALSE;
394 }
395
396 static gboolean on_queue_key_press( GtkWidget *widget, GdkEventKey *event, gpointer data )
397 {
398 if ( event->keyval == GDK_Return )
399 return on_queue_item( widget, data );
400 return FALSE;
401 }
402
403 static gboolean on_home( GtkWidget *button, gpointer data )
404 {
405 page_clips this = data;
406 list_clips( this, "/" );
407 return TRUE;
408 }
409
410 static gboolean on_refresh( GtkWidget *button, gpointer data )
411 {
412 page_clips this = data;
413 char *temp = strdup( this->path );
414 list_clips( this, temp );
415 free( temp );
416
417 return TRUE;
418 }
419
420 static gboolean on_up( GtkWidget *dummy, gpointer data )
421 {
422 page_clips this = data;
423 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
424 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
425 GtkTreeModel *model;
426 GtkTreeIter iter;
427 int clip;
428
429 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
430 {
431 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
432 mvcp_unit_clip_move( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, mvcp_absolute, clip - 1 < 0 ? 0 : clip - 1 );
433 }
434 return TRUE;
435 }
436
437 static gboolean on_down( GtkWidget *dummy, gpointer data )
438 {
439 page_clips this = data;
440 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
441 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
442 GtkTreeModel *model;
443 GtkTreeIter iter;
444 int clip;
445
446 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
447 {
448 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
449 mvcp_unit_clip_move( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip, mvcp_absolute, clip + 1 );
450 }
451 return TRUE;
452 }
453
454 static gboolean on_remove( GtkWidget *dummy, gpointer data )
455 {
456 page_clips this = data;
457 GtkWidget *widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
458 GtkTreeSelection *select = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
459 GtkTreeModel *model;
460 GtkTreeIter iter;
461 int clip;
462
463 if ( gtk_tree_selection_get_selected( select, &model, &iter ) )
464 {
465 gtk_tree_model_get( model, &iter, 5, &clip, -1 );
466 mvcp_unit_clip_remove( this->dv, dv1394app_get_selected_unit( this->app ), mvcp_absolute, clip );
467 }
468 return TRUE;
469 }
470
471 static gboolean on_clean( GtkWidget *dummy, gpointer data )
472 {
473 page_clips this = data;
474 mvcp_unit_clean( this->dv, dv1394app_get_selected_unit( this->app ) );
475 return TRUE;
476 }
477
478 void on_mode_change( GtkMenuItem *menuitem, gpointer data )
479 {
480 page_clips this = data;
481 int index = 0;
482
483 for ( index = 0; index < 4; index ++ )
484 if ( GTK_WIDGET( menuitem ) == this->modes[ index ] )
485 break;
486
487 this->mode = index;
488 }
489
490 static GtkWidget *this_page_get_widget( page_clips this )
491 {
492 if ( this->widget == NULL )
493 this->widget = this->parent_page->get_widget(this->parent_page);
494 return this->widget;
495 }
496
497 static void this_page_get_toolbar_info( page this, GtkIconSize size, GtkWidget **icon, char **label )
498 {
499 *icon = gtk_image_new_from_stock( "gtk-justify-fill", size );
500 *label = _("_Playlist");
501 }
502
503 static void this_page_on_connect( page_clips this )
504 {
505 this->dv = mvcp_init( dv1394app_get_parser( this->app ) );
506 list_clips( this, "/" );
507 }
508
509 static void this_page_on_unit_change( page_clips this, int unit )
510 {
511 if ( unit != this->unit )
512 this->unit = unit;
513 }
514
515 static void this_page_on_disconnect( page_clips this )
516 {
517 list_clips( this, NULL );
518 mvcp_close( this->dv );
519 }
520
521 static void this_page_show_status( page_clips this, mvcp_status status )
522 {
523 if ( status->status != unit_disconnected )
524 {
525 if ( this->generation != status->generation )
526 list_queue( this, status->clip_index );
527 else if ( this->clip != status->clip_index )
528 list_active( this, status->clip_index );
529 }
530 }
531
532 static void this_page_close( page_clips this )
533 {
534 if ( this != NULL )
535 free( this );
536 }
537
538 page page_clips_init( dv1394app app, struct page_t *parent_page)
539 {
540 page_clips this = calloc( 1, sizeof( page_clips_t ) );
541 GtkWidget *widget;
542 int index = 0;
543
544 this->parent_page = parent_page;
545 this->parent.get_widget = ( GtkWidget *(*)( page ) )this_page_get_widget;
546 this->parent.get_toolbar_info = this_page_get_toolbar_info;
547 this->parent.on_connect = ( void (*)( page ) )this_page_on_connect;
548 this->parent.on_unit_change = ( void (*)( page, int ) )this_page_on_unit_change;
549 this->parent.on_disconnect = ( void (*)( page ) )this_page_on_disconnect;
550 this->parent.show_status = ( void (*)( page, mvcp_status ) )this_page_show_status;
551 this->parent.close = ( void (*)( page ) )this_page_close;
552 this->app = app;
553 this->generation = -1;
554
555 widget = lookup_widget( this_page_get_widget( this ), "list_clips" );
556 g_signal_connect( G_OBJECT( widget ), "button-press-event", G_CALLBACK( on_clip_selected ), this );
557 g_signal_connect( G_OBJECT( widget ), "key-press-event", G_CALLBACK( on_clip_key_press ), this );
558 widget = lookup_widget( this_page_get_widget( this ), "list_dir" );
559 g_signal_connect( G_OBJECT( widget ), "button-press-event", G_CALLBACK( on_dir_selected ), this );
560 g_signal_connect( G_OBJECT( widget ), "key-press-event", G_CALLBACK( on_dir_key_press ), this );
561 widget = lookup_widget( this_page_get_widget( this ), "button_clips_refresh" );
562 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_refresh ), this );
563 widget = lookup_widget( this_page_get_widget( this ), "button_clips_home" );
564 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_home ), this );
565 widget = lookup_widget( this_page_get_widget( this ), "button_up" );
566 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_up ), this );
567 widget = lookup_widget( this_page_get_widget( this ), "button_down" );
568 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_down ), this );
569 widget = lookup_widget( this_page_get_widget( this ), "button_remove" );
570 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_remove ), this );
571 widget = lookup_widget( this_page_get_widget( this ), "button_clean" );
572 gtk_signal_connect( GTK_OBJECT( widget ), "clicked", GTK_SIGNAL_FUNC( on_clean ), this );
573
574 widget = lookup_widget( this_page_get_widget( this ), "table4" );
575 gtk_widget_show( widget );
576
577 widget = lookup_widget( this_page_get_widget( this ), "treeview1" );
578 g_signal_connect( G_OBJECT( widget ), "button-press-event", G_CALLBACK( on_queue_selected ), this );
579 g_signal_connect( G_OBJECT( widget ), "key-press-event", G_CALLBACK( on_queue_key_press ), this );
580
581 for ( index = 0; index < 4; index ++ )
582 {
583 char item[ 256 ];
584 sprintf( item, "mode_%d", index );
585 widget = lookup_widget( this_page_get_widget( this ), item );
586 gtk_signal_connect( GTK_OBJECT( widget ), "activate", GTK_SIGNAL_FUNC( on_mode_change ), this );
587 this->modes[ index ] = widget;
588 }
589
590 return ( page )this;
591 }