wavs saved as wavs, pumped up version
[maemo-recorder] / src / maemo-recorder-ui.c
1 /* vim: set sts=4 sw=4 et: */
2 /*
3  * maemo-recorder-ui.c
4  *
5  * Copyright (C) 2006 Nokia Corporation
6  *
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 as
10  * published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  *
21  */
22
23
24 #include <gst/gst.h>
25 #include <glib/gi18n-lib.h>
26 #include <libgnomevfs/gnome-vfs.h>
27 #include <locale.h>
28 #include <hildon-widgets/hildon-program.h>
29 #include <hildon-widgets/hildon-note.h>
30 #include <hildon-widgets/hildon-banner.h>
31 #include <hildon-widgets/hildon-defines.h>
32 #include <hildon-widgets/hildon-file-system-model.h>
33 #include <hildon-widgets/hildon-file-chooser-dialog.h>
34 #include <string.h>
35 #include <sys/time.h>
36
37 #include "maemo-recorder.h"
38 #include "maemo-recorder-ui.h"
39 #include "maemo-recorder-file.h"
40 #include "settings.h"
41
42 #define DEFAULT_REC_BLOCKSIZE "160"
43
44 #define STOP_DELAY 500
45 #define REC_UPDATE_INTERVAL 750
46 #define PLAY_UPDATE_INTERVAL 200
47
48 /* MACROs */
49
50 #define GST_TIME_MINS(t) \
51         (guint) ((((GstClockTime)(t)) / (GST_SECOND * 60)) % 60)
52 #define GST_TIME_SECS(t) \
53         (guint) ((((GstClockTime)(t)) / GST_SECOND) % 60)
54 #define GST_TIME_TO_SECS(t) \
55         (gdouble) (((gdouble)(t)) / (gdouble) GST_SECOND) /* GST_SECOND should be 1e9 */
56 #define GST_TIME_MSECS(t) \
57         (guint) (((GstClockTime)(t)) % GST_SECOND)
58
59 #define RECORDER_APP_TITLE "Maemo Recorder"
60 #define RECORDER_MSG_READY _("Ready")
61 #define RECORDER_MSG_STOPPED _("Stopped")
62 #define RECORDER_MSG_PAUSED _("Paused")
63 #define RECORDER_MSG_PLAYING  _("Playing")
64 #define RECORDER_MSG_RECORDING _("Recording")
65 #define RECORDER_FILE_UNTITLED _("Untitled")
66
67 /* general enumerations */
68     
69 typedef enum
70 {
71     DTX_OFF = 0,
72     DTX_ON = 1
73 } DTX;
74
75 /* menu codes */
76 typedef enum 
77 {
78     MENU_FILE_NEW = 1,
79     MENU_FILE_OPEN,
80     MENU_FILE_SAVE,
81     MENU_FILE_SAVE_AS,
82     MENU_FILE_REC,
83     MENU_FILE_PLAY,
84     MENU_FILE_STOP,
85     MENU_FILE_QUIT
86 } MenuActionCode;
87
88 typedef enum 
89 {
90     PIPELINE_PLAY = 1,
91     PIPELINE_PLAY_MP3,
92     PIPELINE_REC
93 } PipeLineType;
94
95 /* function prototypes */
96
97 static gboolean cbBus (GstBus *bus, 
98                GstMessage *message, 
99                gpointer data);
100
101 static void pipelineStateChanged (GstElement *element,
102                   GstState old,
103                   GstState new,
104                   GstState pending,
105                   AppData *data);
106
107 static void seekToTime(GstElement *pipeline, gdouble secs);
108 static gboolean seekToZero(AppData *data, GstElement *pipeline);
109 static void setLength(AppData *data, gdouble secs);
110 static gboolean cbStopPlayback(AppData *data);
111 static void cbStop(GtkWidget* widget, AppData *data);
112 static void cbPlay(GtkWidget* widget, AppData *data);
113 static void cbRec(GtkWidget* widget, AppData *data);
114 static void cbNew(GtkWidget* widget, AppData *data);
115 static void cbOpen(GtkWidget* widget, AppData *data);
116 /*static void cbSave(GtkWidget* widget, AppData *data);*/
117 static void cbSaveAs(GtkWidget* widget, AppData *data);
118 static void cbItemClose(GtkWidget *widget, gpointer data);
119 static void cbUserSeek(GtkAdjustment *adjustment, gpointer data);
120 static gchar* cbFormatSeekbarValue(GtkScale *scale, gdouble value);
121 static GtkWidget* createToolBar(AppData *data);
122 static void createMenu( AppData *data );
123 static gboolean createPipeline(AppData *app, PipeLineType type);
124 static void openPlayPipeline( AppData *data );
125 static gboolean destroyPipeline(AppData *data, PipeLineType type);
126 static gboolean destroyPipelines(AppData *data);
127 static void cbItemGroupChanged(gpointer data);
128 static gboolean cbUpdateRecLength(AppData *data);
129 static void cbDestroy(GtkWidget* widget, GdkEvent *event, gpointer data);
130 static gboolean openURI(gpointer user_data);
131 static gboolean closeFile(AppData *data);
132 static gdouble guessMediaLength(AppData *data);
133 static GstCaps *createCapsFilter(AudioFormat format);
134
135 static void new_pad_cb (GstElement *wavparse, GstPad *new_pad, gpointer data)
136 {
137     GstElement *sink;
138     AppData* app = (AppData*) data; 
139
140     ULOG_INFO("new pad");
141  
142     sink = gst_element_factory_make ("dsppcmsink", "sink");
143  
144     gst_bin_add (GST_BIN (app->playPipeline), sink);
145  
146     if (!gst_element_link (wavparse, sink))
147       g_error ("link(wavparse, sink) failed!");
148     gst_element_sync_state_with_parent(sink); 
149  
150 }
151
152 static gboolean createPipeline(AppData *app, PipeLineType type)
153 {
154     GstElement *src = NULL;
155     GstElement *sink = NULL;
156     GstElement *filter = NULL;
157     GstElement *queue = NULL;
158     GstElement *pipeline = NULL;
159     GstElement *parse = NULL;
160     GstCaps *caps = NULL;
161
162     g_assert(NULL != app);
163
164     pipeline = gst_pipeline_new("pipeline");
165
166     gst_bus_add_watch(gst_pipeline_get_bus (GST_PIPELINE (pipeline)),
167                    cbBus, app);
168
169     /* create elements */
170     switch (type)
171     {
172         case PIPELINE_PLAY_MP3:
173             ULOG_INFO("mp3 playback - queue");
174             src = gst_element_factory_make ("gnomevfssrc", "source");
175             queue = gst_element_factory_make ("queue", "queue");
176             sink = gst_element_factory_make ("dspmp3sink", "sink");
177
178             g_object_set(G_OBJECT (queue), 
179                 "min-threshold-bytes", 131072,
180                 NULL );
181             g_object_set(G_OBJECT(src), 
182                     "location", app->openFileName, 
183                     "blocksize", 65536,
184                     NULL);
185             break;
186
187         case PIPELINE_PLAY:
188             src = gst_element_factory_make ("filesrc", "source");
189             /* we need also a filter to modify caps */
190             filter = gst_element_factory_make("capsfilter", "filter");
191             switch (app->filter)
192             {
193                 case FORMAT_ILBC:
194                     ULOG_INFO("using ilbc sink");
195                     sink = gst_element_factory_make ("dspilbcsink", "sink");
196                     break;
197
198                 case FORMAT_PCMA:
199                 case FORMAT_PCMU:
200                 case FORMAT_PCM:
201                     ULOG_INFO("using pcm sink");
202                     sink = gst_element_factory_make ("dsppcmsink", "sink");
203                     break;
204
205                 case FORMAT_WAV:
206                     ULOG_INFO("using wavparse & pcm sink");
207                     parse = gst_element_factory_make ("wavparse", "parse");
208                     break;
209                     
210                 default:
211                     break;  
212             }
213
214             g_object_set(G_OBJECT(src), 
215                     "location", app->openFileName, 
216                     NULL);
217
218             caps = createCapsFilter(app->filter);
219             g_object_set(G_OBJECT(filter), 
220                   "caps", caps,
221                   NULL);
222             break;
223
224         case PIPELINE_REC:
225             switch (app->filter)  
226             {    
227                 case FORMAT_ILBC:
228                     ULOG_INFO("using ilbc source");
229                     src = gst_element_factory_make("dspilbcsrc", "source");
230                     g_object_set(G_OBJECT(src),
231                         "dtx", DTX_OFF,
232                         NULL);
233                     break;
234         
235                 case FORMAT_PCMA:
236                 case FORMAT_PCMU:
237                 case FORMAT_PCM:
238                     ULOG_INFO("using pcm source");
239                     src = gst_element_factory_make("dsppcmsrc", "source");
240                     g_object_set(G_OBJECT (src), 
241                           "blocksize", DEFAULT_REC_BLOCKSIZE, 
242                           "dtx", DTX_OFF,
243                         NULL);
244                     break;
245             
246                 case FORMAT_WAV:
247                     ULOG_INFO("using pcm source & wavenc");
248                     src = gst_element_factory_make("dsppcmsrc", "source");
249                     g_object_set(G_OBJECT (src),
250                             "blocksize", DEFAULT_REC_BLOCKSIZE,
251                             "dtx", DTX_OFF,
252                             NULL);
253                     parse = gst_element_factory_make("wavenc", "enc");
254                     break;   
255             
256                 default:
257                     ULOG_WARN("Unknown filter type!");
258                     break; 
259             }
260
261             filter = gst_element_factory_make("capsfilter", "filter");
262             caps = createCapsFilter(app->filter);
263             g_object_set(G_OBJECT(filter), 
264                   "caps", caps,
265                   NULL);
266
267             sink = gst_element_factory_make("filesink", "sink");
268
269             g_object_set(G_OBJECT(sink), 
270                 "location", app->saveFileName,
271                 NULL);
272             break;
273
274         default:
275             ULOG_ERR("Invalid pipeline type!");
276             gst_object_unref(pipeline);
277             return FALSE;
278     }
279
280     if (!src || !pipeline)
281     {
282         ULOG_ERR("Could not create GstElement!");
283         return FALSE;
284     }
285
286     if (!sink && app->filter != FORMAT_WAV)
287     {
288         ULOG_ERR("Could not create GstElement!");
289         return FALSE;        
290     }
291         
292     ULOG_INFO("Create pipeline");
293      
294     /* add to pipeline and link */
295     switch (type)
296     {
297         case PIPELINE_REC:
298             switch (app->filter)
299             {
300                 case FORMAT_ILBC:
301                 case FORMAT_PCM:
302                 case FORMAT_PCMA:
303                     if (!filter)
304                     {
305                        ULOG_ERR("Could not create filter GstElement!");
306                        return FALSE;
307                     }
308                     gst_bin_add_many(GST_BIN(pipeline), src, filter, sink, NULL);
309
310                     if (!gst_element_link_many (src, filter, sink, NULL))
311                     {
312                         ULOG_ERR("gst_element_link failed for src, filter and sink!");
313                         return FALSE;
314                     }
315                     break;
316
317                 case FORMAT_WAV:
318                     gst_bin_add_many(GST_BIN(pipeline), src, parse, sink, NULL);
319                     if (!gst_element_link_many (src, parse, sink, NULL))
320                     {
321                         ULOG_ERR("gst_element_link failed for src, parse and sink!");
322                     }
323                     break;
324          
325                 default:
326                     break;
327           
328             }
329             break; 
330            
331         case PIPELINE_PLAY:
332             switch (app->filter)
333             {
334                 case FORMAT_ILBC:
335                 case FORMAT_PCM:
336                 case FORMAT_PCMA:
337                     if (!filter)
338                     {
339                        ULOG_ERR("Could not create filter GstElement!");
340                        return FALSE;
341                     }
342                     gst_bin_add_many(GST_BIN(pipeline), src, filter, sink, NULL);
343
344                     if (!gst_element_link_many (src, filter, sink, NULL))
345                     {
346                         ULOG_ERR("gst_element_link failed for src, filter and sink!");
347                         return FALSE;
348                     }
349
350                     break;
351
352                 case FORMAT_WAV:
353                     gst_bin_add_many(GST_BIN(pipeline), src, parse, NULL);
354                     if (!gst_element_link_many (src, parse, NULL))
355                     {
356                         ULOG_ERR("gst_element_link failed for src, parse and sink!");
357                         return FALSE;
358                     }
359                     app->playPipeline = pipeline;
360                     g_signal_connect(parse, "pad_added",
361                                  G_CALLBACK(new_pad_cb), app);
362                     break;
363
364                 default:
365                     break;
366             }
367
368             break;
369      
370         case PIPELINE_PLAY_MP3:
371         default:
372             gst_bin_add_many(GST_BIN(pipeline), src, queue, sink, NULL);
373  
374             if(!gst_element_link_many(src, queue, sink, NULL))
375             {
376                  ULOG_ERR("gst_element_link failed for src and sink!");
377                  return FALSE;
378             }
379
380             break;
381     }
382
383     /* set application data */
384     if (type == PIPELINE_REC)
385     {
386         app->recPipeline = pipeline;
387     }
388     else
389     {
390         app->playPipeline = pipeline;
391         app->playPipelineType = type;
392     }
393      
394     if (caps)
395     {
396         gst_caps_unref(caps);
397         caps = NULL;
398     }
399
400     return TRUE;
401 }
402
403 static gboolean destroyPipelines(AppData *data)
404 {
405     gboolean ret = FALSE;
406
407     /* ugly hack with pipeline types, but works though */
408     ret != destroyPipeline(data, PIPELINE_REC);
409     ret != destroyPipeline(data, PIPELINE_PLAY);
410     return ret;
411 }
412
413 static gboolean destroyPipeline(AppData *data, PipeLineType type)
414 {
415     GstState state;
416     GstState pending;
417     GstElement *pipeline = NULL;
418
419     ULOG_INFO("%s() - Stopping playback/recording", G_STRFUNC);
420
421     switch (type)
422     {
423         case PIPELINE_REC:
424             pipeline = data->recPipeline;
425             /*
426             data->recPipeline = NULL;
427             */
428             break;
429
430         default:
431             pipeline = data->playPipeline;
432             /*
433             data->playPipeline = NULL;
434             */
435             break;
436     }
437     
438     if (!GST_IS_ELEMENT(pipeline))
439         return TRUE;
440
441     /* this unallocates everything */
442     gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_NULL);
443
444     /* for some reason the state does not update manually */
445     gst_element_get_state(pipeline, &state, 
446             &pending, GST_CLOCK_TIME_NONE);
447     pipelineStateChanged(pipeline,
448            state,
449            state,
450            pending,
451            data);
452
453     /*
454     gst_object_unref(pipeline);
455     */
456
457     return TRUE;
458 }
459
460
461 /* callbacks */
462
463 static void pipelineStateChanged (GstElement *element,
464                   GstState old,
465                   GstState new,
466                   GstState pending,
467                   AppData * data)
468 {
469     g_assert(NULL != data);
470      
471     switch (new)
472     {
473         case GST_STATE_PLAYING:
474           if(APPSTATE_RECORDING == getAppState(data))
475           {
476                gchar *tmp = g_strdup_printf("%s...", RECORDER_MSG_RECORDING);
477                hildon_banner_show_information(GTK_WIDGET(data->mainView), NULL, tmp);
478                g_free(tmp);
479                tmp = NULL;
480                ULOG_INFO("%s() - Recording", G_STRFUNC);
481                gtk_entry_set_text(GTK_ENTRY(data->mainViewData.stateEntry), 
482                       RECORDER_MSG_RECORDING);
483
484                gtk_widget_set_state(data->buttonRec, GTK_STATE_ACTIVE);
485
486                if (data->recUpdateId == 0 && gettimeofday(&data->recStartTv, NULL) == 0)
487                {
488                    data->recUpdateId = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, REC_UPDATE_INTERVAL, (GSourceFunc) cbUpdateRecLength, data, NULL);
489                }
490           }
491           else
492           {
493                gchar *tmp = g_strdup_printf("%s...", RECORDER_MSG_PLAYING);
494                hildon_banner_show_information(GTK_WIDGET(data->mainView), NULL, tmp);
495                g_free(tmp);
496                tmp = NULL;
497                ULOG_INFO("%s() - Playing", G_STRFUNC);
498                gtk_entry_set_text(GTK_ENTRY(data->mainViewData.stateEntry), 
499                       RECORDER_MSG_PLAYING);  
500                gtk_widget_set_state(data->buttonPlay, GTK_STATE_ACTIVE);
501                /*
502                gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(data->buttonPlay), GTK_STOCK_MEDIA_PAUSE);
503                */
504           }
505
506           break;
507
508         case GST_STATE_READY:
509             /* hildon_banner_show_information(GTK_WIDGET(data->mainView), NULL, "Ready..."); */
510             ULOG_INFO("%s() - Ready", G_STRFUNC);
511             gtk_entry_set_text(GTK_ENTRY(data->mainViewData.stateEntry), 
512                      RECORDER_MSG_READY);  
513             break;
514
515         case GST_STATE_PAUSED:
516           {
517               gint64 pos = 0;
518               GstFormat fmt = GST_FORMAT_TIME;
519               ULOG_INFO("%s() - Paused", G_STRFUNC);
520
521               /* if pipeline pos == 0 => stopped, else => paused */
522               if (GST_IS_ELEMENT(data->playPipeline) && gst_element_query_position(data->playPipeline, &fmt, &pos) && pos != 0)
523               {
524                   hildon_banner_show_information(GTK_WIDGET(data->mainView), NULL, RECORDER_MSG_PAUSED);       
525                   gtk_entry_set_text(GTK_ENTRY(data->mainViewData.stateEntry), 
526                          RECORDER_MSG_PAUSED);
527                   /*
528                   gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(data->buttonPlay), GTK_STOCK_MEDIA_PLAY);
529                   gtk_widget_set_state(data->buttonPlay, GTK_STATE_NORMAL);
530                   */
531               }
532               else
533               {
534                   hildon_banner_show_information(GTK_WIDGET(data->mainView), NULL, RECORDER_MSG_STOPPED);       
535                   gtk_entry_set_text(GTK_ENTRY(data->mainViewData.stateEntry), 
536                          RECORDER_MSG_STOPPED);  
537                   gtk_widget_set_state(data->buttonPlay, GTK_STATE_NORMAL);
538                   gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(data->buttonPlay), GTK_STOCK_MEDIA_PLAY);
539               }
540               gtk_widget_set_state(data->buttonRec, GTK_STATE_NORMAL);
541           }
542           break;
543           
544         case GST_STATE_NULL:
545           ULOG_INFO("%s() - Null", G_STRFUNC);
546           hildon_banner_show_information(GTK_WIDGET(data->mainView), NULL, RECORDER_MSG_STOPPED);
547           gtk_entry_set_text(GTK_ENTRY(data->mainViewData.stateEntry), 
548                      RECORDER_MSG_READY);  
549           gtk_widget_set_state(data->buttonPlay, GTK_STATE_NORMAL);
550           gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(data->buttonPlay), GTK_STOCK_MEDIA_PLAY);
551           gtk_widget_set_state(data->buttonRec, GTK_STATE_NORMAL);
552           
553           break;
554
555         default:
556           ULOG_WARN("%s() - default case", G_STRFUNC);
557           break;
558     }
559 }
560
561
562 static gboolean cbBus(GstBus *bus,
563                GstMessage *message,
564                gpointer   data)
565 {
566     AppData *app = (AppData*)data;
567
568     switch (GST_MESSAGE_TYPE(message)) 
569     {
570         case GST_MESSAGE_WARNING:
571         {
572             GError *err;
573             gchar *debug;
574
575             gst_message_parse_error (message, &err, &debug);
576             ULOG_WARN("%s() - Warning: %s", G_STRFUNC, err->message);
577             g_error_free (err);
578             g_free (debug);
579             break;
580         }
581
582         case GST_MESSAGE_ERROR: 
583         {
584             GError *err;
585             gchar *debug;
586
587             gst_message_parse_error (message, &err, &debug);
588             ULOG_ERR("%s() - Error: %s", G_STRFUNC, err->message);
589             g_error_free (err);
590             g_free (debug);
591             /* break; */
592             /* flow through to eos */
593         }
594
595         case GST_MESSAGE_EOS:
596         {
597             ULOG_INFO("%s() - eos", G_STRFUNC);
598
599              switch(getAppState(app))
600              {
601                  case APPSTATE_PLAYING:
602                     /* stop playback after a short break*/
603                     g_timeout_add(STOP_DELAY, (GSourceFunc)cbStopPlayback, data);
604                     break;
605
606                  case APPSTATE_RECORDING:
607                      gst_element_set_state(GST_ELEMENT(app->recPipeline), 
608                            GST_STATE_PAUSED);
609                      destroyPipeline(app, PIPELINE_REC);
610                      app->saved = FALSE;
611                      break;
612
613                  case APPSTATE_READY:
614                  default:
615                     /* destroyPipelines(app); */
616                      break;
617              }
618              break;
619         }
620
621         case GST_MESSAGE_STATE_CHANGED: 
622         {
623             GstState old;
624             GstState new;
625             GstState pending;
626
627             gst_message_parse_state_changed(message, &old, &new, &pending);
628
629             pipelineStateChanged(NULL, old, new, pending, app);
630
631             break;
632         }
633
634         default:
635             /* unhandled message */
636             ULOG_WARN("%s() - Unhandled message, type = %d", G_STRFUNC, message->type);
637             break;
638     }
639
640     /* remove message from the queue */
641     return TRUE;
642 }
643
644 static void cbDestroy(GtkWidget* widget, GdkEvent *event, gpointer data)
645 {     
646     AppData* app;
647
648     g_assert(data);
649
650     app = (AppData *) data;
651
652     if (!closeFile(app))
653         return;
654
655     destroyPipelines(app);
656     if (app->playPipeline)
657         gst_object_unref(GST_OBJECT(app->playPipeline));
658
659     if (app->recPipeline)
660         gst_object_unref(GST_OBJECT(app->recPipeline));
661
662     gtk_main_quit();
663 }
664
665 static gboolean cbCheckPosition (AppData *data)
666 {
667     GstFormat fmt = GST_FORMAT_TIME;
668     gint64 pos = 0, len = 0;
669     static gboolean lengthSet = FALSE;
670
671     g_assert(NULL != data);
672
673     /* get length */
674     if(!lengthSet && gst_element_query_duration(data->playPipeline, &fmt, &len))
675     {
676         if (len > 0)
677         {
678             gdouble size = 0;
679             size = GST_TIME_TO_SECS(len);
680             setLength(data, size); /* sets lengthEntry and adjustment */
681             lengthSet = TRUE;
682         }
683     }
684
685     /* calculate position */
686     if (gst_element_query_position(data->playPipeline, &fmt, &pos))
687     {
688         gdouble time = GST_TIME_TO_SECS(pos);
689
690         ULOG_DEBUG("pos = %lld, time = %f", 
691              pos,
692            time 
693            );
694
695         gtk_adjustment_set_value( 
696            GTK_ADJUSTMENT(data->mainViewData.adjustment),
697            time);
698         gtk_adjustment_value_changed(GTK_ADJUSTMENT(data->mainViewData.adjustment));
699     }
700
701     if (APPSTATE_PLAYING == getAppState(data))
702     {
703         return TRUE;
704     }
705
706     return FALSE;
707 }
708
709 static void cbNew(GtkWidget* widget, AppData *data) 
710 {
711     g_assert(NULL != data);
712
713     if (!closeFile(data))
714         return;
715
716     /* remove pipelines if existing */
717     destroyPipelines(data);
718     ULOG_DEBUG_F("cbNew");
719     /* destroy tmp file */
720
721     /* clear filenames */
722     g_free(data->openFileName);
723     data->openFileName = NULL;
724     g_free(data->saveFileName);
725     data->saveFileName = NULL;
726 /*    data->filter = FORMAT_NONE;*/
727     data->file_format = FORMAT_NONE;
728
729     /* update display */
730     gtk_entry_set_text(GTK_ENTRY(data->mainViewData.fileNameEntry), 
731         RECORDER_FILE_UNTITLED);
732     setLength(data, 0.0);
733     /* update the display + scale */
734     gtk_adjustment_set_value(GTK_ADJUSTMENT(data->mainViewData.adjustment),
735               0);
736     gtk_widget_set_sensitive(data->buttonSave, FALSE);
737     gtk_widget_set_sensitive(data->buttonSaveAs, FALSE);
738     data->saved = TRUE;
739
740     gtk_window_set_title(GTK_WINDOW(data->mainView), RECORDER_FILE_UNTITLED);
741
742     ULOG_DEBUG_F("cbNew end");
743 }
744
745 static void cbOpen(GtkWidget* widget, AppData *data) 
746 {
747     GtkWidget* dialog = NULL;
748     gchar *tmpfile = NULL;
749     gchar *selected = NULL;
750     AudioFormat format;
751     gchar *basename;
752     gdouble len = -1.0;
753 #if 0
754     GtkFileFilter *filter;
755 #endif
756
757     ULOG_DEBUG_F("begin");
758     g_assert(NULL != data);
759
760     if (!closeFile(data))
761         return;
762
763 #if 0
764     /* create filter */
765     filter = gtk_file_filter_new();
766     gtk_file_filter_add_mime_type(filter, "audio/x-mp3");
767 #endif
768
769     g_assert(GTK_IS_WINDOW(data->mainView));
770     /* create dialog */
771     dialog = hildon_file_chooser_dialog_new_with_properties(
772               GTK_WINDOW(data->mainView), 
773               "action", GTK_FILE_CHOOSER_ACTION_OPEN,
774               "file-system-model", NULL,
775               "local-only", TRUE,
776               NULL
777               );
778
779     /* show it */
780     gtk_widget_show_all(dialog);
781
782     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) 
783     {
784         selected = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
785     }
786
787     ULOG_DEBUG("%s() - dialog finished", G_STRFUNC);
788
789     if (dialog != NULL && GTK_IS_WIDGET(dialog))
790     {
791         gtk_widget_destroy(dialog);
792         /*
793         ULOG_DEBUG("%s() - dialog destroyed", G_STRFUNC);
794         */
795     }
796
797     if (NULL == selected) /* no file selected */
798         return;
799
800     ULOG_INFO("%s() - selected filename = '%s'", G_STRFUNC, selected);
801
802     if (openFile(selected, &format, &tmpfile))
803     {
804         ULOG_INFO("%s() - openFile() succeeded, format: %d, tmpfile %s", G_STRFUNC, format, tmpfile);
805
806         g_assert(tmpfile);
807
808         /* update filenames */
809         basename = g_path_get_basename(selected);
810
811         gtk_entry_set_text(GTK_ENTRY(data->mainViewData.fileNameEntry), selected);
812         gtk_window_set_title(GTK_WINDOW(data->mainView), basename);
813         g_free(basename);
814
815         g_free(data->openFileName);
816         data->openFileName = tmpfile;
817         data->file_format = format;
818         data->filter = format;
819         g_free(data->saveFileName);
820         data->saveFileName = NULL;
821         gtk_widget_set_sensitive(data->buttonSaveAs, TRUE);
822
823         len = guessMediaLength(data);
824         if (len > 0.0)
825             setLength(data, len);
826         else
827             setLength(data, 0.0);
828
829         data->saved = TRUE;
830     }
831     else
832     {
833         ULOG_WARN("%s() - openFile() failed", G_STRFUNC);
834         hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Could not open file!"));
835     }
836
837     g_free(selected);
838
839     ULOG_DEBUG_F("end");
840 }
841
842 static gboolean
843 openURI(gpointer user_data)
844 {
845     AppData *data;
846     GnomeVFSURI *uri;
847     gchar *selected = NULL;
848     AudioFormat format;
849     gchar *tmpfile = NULL;
850     gchar *basename = NULL;
851     gdouble len = -1.0;
852
853     g_assert(user_data);
854     data = (AppData *) user_data;
855
856     if (NULL == data->mimeURI)
857         return FALSE;
858
859     uri = gnome_vfs_uri_new(data->mimeURI);
860     selected = g_strdup(gnome_vfs_uri_get_path(uri));
861
862     gnome_vfs_uri_unref(uri);
863     uri = NULL;
864
865     g_free(data->mimeURI);
866     data->mimeURI = NULL;
867
868     /* TODO: the following is duplicated in cbOpen(), move to a tryOpen() function ? */
869
870     if (NULL == selected)
871         return FALSE;
872
873     ULOG_INFO("%s() - selected filename = '%s'", G_STRFUNC, selected);
874
875     if (openFile(selected, &format, &tmpfile))
876     {
877         ULOG_INFO("%s: openFile() succeeded, format: %d, tmpfile %s", G_STRFUNC, format, tmpfile);
878
879         g_assert(tmpfile);
880
881         /* update filenames */
882         basename = g_path_get_basename(selected);
883         
884         gtk_entry_set_text(GTK_ENTRY(data->mainViewData.fileNameEntry), selected);
885         gtk_window_set_title(GTK_WINDOW(data->mainView), basename);
886         g_free(basename);
887
888         g_free(data->openFileName);
889         data->openFileName = tmpfile;
890         data->file_format = format;
891         data->filter = format;
892         g_free(data->saveFileName);
893         data->saveFileName = NULL;
894         gtk_widget_set_sensitive(data->buttonSaveAs, TRUE);
895         
896         len = guessMediaLength(data);
897         if (len > 0.0)
898             setLength(data, len);
899         else
900             setLength(data, 0.0);
901
902         data->saved = TRUE;
903     }
904     else
905     {
906         ULOG_WARN("%s() - openFile() failed", G_STRFUNC);
907         hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Could not open file!"));
908     }
909
910     g_free(selected);
911
912     return FALSE;
913 }
914
915 static void openPlayPipeline( AppData *data )
916 {
917
918     GstFormat fmt = GST_FORMAT_TIME;
919     gint64 len;
920     gdouble size = 0;
921     /* create pipelines */
922     /* check file type */
923     switch (data->file_format)
924     {
925         case FORMAT_PCMA:
926         case FORMAT_PCMU:
927         case FORMAT_PCM:
928         case FORMAT_ILBC:
929         case FORMAT_WAV:
930             destroyPipelines(data);
931             data->filter = data->file_format;
932             createPipeline(data, PIPELINE_PLAY);
933             break;
934
935         case FORMAT_MP3:
936             destroyPipelines(data);
937             data->filter = data->file_format;
938             createPipeline(data, PIPELINE_PLAY_MP3);
939             break;
940
941         case FORMAT_NONE:
942         default:
943             ULOG_WARN("%s() - unknown file_format", G_STRFUNC);
944             hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Unknown filetype!"));
945             return;
946             break;
947     }
948
949     if (!GST_IS_ELEMENT(data->playPipeline))
950     {
951         hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Could not create pipeline!"));
952         return;
953     }
954
955     gst_element_set_state(GST_ELEMENT(data->playPipeline), GST_STATE_READY);
956     gst_element_get_state(GST_ELEMENT(data->playPipeline), NULL, NULL, GST_CLOCK_TIME_NONE /* or ns */);
957
958     /* calculate length */
959     if (gst_element_query_duration (data->playPipeline, &fmt, &len))
960     {
961         if (len > 0)
962         {
963             size = GST_TIME_TO_SECS(len);
964             ULOG_INFO("playSize: len:%lld size:%f", len, size);
965             setLength(data, size);
966         }
967     }
968     /*
969     else 
970     {
971         ULOG_INFO("playSize else");
972         setLength(data, 0.0);
973     }
974     */
975 }
976
977 /* returns whether the action can proceed or should be aborted */
978 static gboolean 
979 closeFile(AppData *data)
980 {
981     GtkWidget *note;
982     gint i;
983
984     if (data->saved)
985         return TRUE;
986
987     note = hildon_note_new_confirmation_add_buttons(GTK_WINDOW(data->mainView), _("Save recording?"),
988             _("Yes"), GTK_RESPONSE_YES,
989             _("No"), GTK_RESPONSE_NO,
990             _("Cancel"), GTK_RESPONSE_CANCEL,
991             NULL);
992
993     i = gtk_dialog_run(GTK_DIALOG(note));
994     gtk_widget_destroy(note);
995
996     switch (i)
997     {
998         case GTK_RESPONSE_CANCEL:
999             return FALSE;
1000
1001         case GTK_RESPONSE_NO:
1002             return TRUE;
1003
1004         case GTK_RESPONSE_YES:
1005         {
1006             cbSaveAs(NULL, data);
1007             return data->saved;
1008         }
1009         default:
1010             ULOG_WARN("%s(): unknown response from dialog: %d", G_STRFUNC, i);
1011     }
1012     return FALSE;
1013 }
1014      
1015 #if 0
1016 static void cbSave(GtkWidget* widget, AppData *data) 
1017 {
1018      GtkWidget* dialog = NULL;
1019      const gchar *current;
1020      gchar *selected = NULL;
1021
1022      g_assert(data);
1023
1024      ULOG_DEBUG("%s() - begin", G_STRFUNC);
1025
1026      current = gtk_entry_get_text(GTK_ENTRY(data->mainViewData.fileNameEntry));
1027      if (NULL == current || strcmp(current, RECORDER_FILE_UNTITLED) == 0) 
1028      {
1029        hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Nothing to save"));
1030        return;
1031      }
1032
1033      /* if saveFileName does not exist run saveas */
1034      if (NULL == data->saveFileName)
1035      {
1036       /* create dialog */
1037       dialog = GTK_WIDGET(hildon_file_chooser_dialog_new(
1038                    GTK_WINDOW(data->mainView), 
1039                    GTK_FILE_CHOOSER_ACTION_SAVE));
1040       
1041       /* show it */
1042       gtk_widget_show_all(dialog);
1043       
1044       if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) 
1045       {
1046            selected = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1047       }
1048      
1049       ULOG_DEBUG("%s() - dialog finished", G_STRFUNC);
1050       
1051       gtk_widget_destroy(dialog);
1052       
1053       if (NULL != selected)
1054       {
1055            ULOG_INFO("%s() - selected filename = '%s'", G_STRFUNC,
1056             selected);
1057            g_free(data->saveFileName);
1058            data->saveFileName = g_strdup_printf("%s%s", selected, getExtension(data->file_format));
1059            g_free(selected);
1060            selected = NULL;
1061       }
1062       else
1063       {
1064            return;
1065       }
1066      }
1067
1068      /* save the file */
1069      if (doSave(gtk_entry_get_text(GTK_ENTRY(data->mainViewData.fileNameEntry)), data->saveFileName, data->file_format))
1070      {
1071       gchar *basename = g_path_get_basename(data->saveFileName);
1072       ULOG_INFO("%s() - file succesfully saved!", G_STRFUNC);
1073       g_free(data->openFileName);
1074       data->openFileName = g_strdup(data->saveFileName);
1075             
1076       gtk_entry_set_text(GTK_ENTRY(data->mainViewData.fileNameEntry), 
1077                  data->saveFileName);
1078       gtk_widget_set_sensitive(data->buttonSave, FALSE);
1079       gtk_window_set_title(GTK_WINDOW(data->mainView), basename);
1080       g_free(basename);
1081      }
1082      
1083      ULOG_DEBUG("%s() - end", G_STRFUNC);
1084 }
1085 #endif
1086
1087 static void cbSettings(GtkWidget* widget, AppData *data)
1088 {
1089     settings_edit( widget, data );
1090 }
1091
1092 static void cbSaveAs(GtkWidget* widget, AppData *data) 
1093 {
1094     GtkWidget* dialog = NULL;
1095     const gchar *current;
1096     gchar *selected = NULL;
1097
1098     g_assert(NULL != data);
1099
1100     ULOG_DEBUG("%s() - begin", G_STRFUNC);
1101
1102     current = gtk_entry_get_text(GTK_ENTRY(data->mainViewData.fileNameEntry));
1103     if (NULL == current || strcmp(current, RECORDER_FILE_UNTITLED) == 0) 
1104     {
1105         hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Nothing to save"));
1106         return;
1107     }
1108
1109     /* create dialog */
1110     dialog = GTK_WIDGET(hildon_file_chooser_dialog_new(
1111               GTK_WINDOW(data->mainView), 
1112               GTK_FILE_CHOOSER_ACTION_SAVE));
1113
1114     gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), 
1115             get_default_dir() );
1116
1117     /* show it */
1118     gtk_widget_show_all(dialog);
1119
1120     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) 
1121     {
1122         selected = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1123     }
1124
1125     ULOG_DEBUG("%s() - dialog finished", G_STRFUNC);
1126     gtk_widget_destroy(dialog);
1127
1128     if (NULL == selected)
1129         return;
1130
1131     ULOG_INFO("%s() - selected filename = '%s'", G_STRFUNC, selected);
1132
1133     g_free(data->saveFileName);
1134     data->saveFileName = NULL;
1135
1136     if (saveFile(selected, data->openFileName, data->file_format, &(data->saveFileName)))
1137     {
1138         gchar *basename;
1139         const gchar *ext;
1140
1141         g_assert(data->saveFileName);
1142
1143         /* set new title that has the file name */
1144         basename = g_path_get_basename(data->saveFileName);
1145         ULOG_DEBUG("%s() - file '%s' succesfully saved!", G_STRFUNC, data->saveFileName);
1146
1147         gtk_window_set_title(GTK_WINDOW(data->mainView), basename);
1148
1149         /* Houston, we have a kludge:
1150          * for AU files we need to keep the old tmpfile for playback
1151          * for RAW/iLBC files, we can remove the tmpfile and point openFileName to the saved file
1152          */
1153         ext = getExtension(data->file_format);
1154         if (strcmp(ext, EXTENSION_AU) != 0)
1155         {
1156             g_free(data->openFileName);
1157             data->openFileName = g_strdup(data->saveFileName);
1158         }
1159
1160         gtk_entry_set_text(GTK_ENTRY(data->mainViewData.fileNameEntry), 
1161                  data->saveFileName);
1162         
1163         g_free(basename);
1164         data->saved = TRUE;
1165     }
1166     else
1167     {
1168         hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Saving file failed!"));
1169     }
1170
1171     g_free(selected);
1172     selected = NULL;
1173
1174     ULOG_DEBUG("%s() - end", G_STRFUNC);
1175 }
1176
1177 static void cbRec(GtkWidget* widget, AppData *data) 
1178 {
1179     g_assert(NULL != data);
1180
1181     ULOG_DEBUG("%s() - begin", G_STRFUNC);     
1182
1183     if (APPSTATE_READY != getAppState(data))
1184     {
1185         ULOG_WARN("%s() - state different than READY -> return", G_STRFUNC);
1186         return;
1187     }
1188
1189     if (!closeFile(data))
1190      return;
1191
1192     /* clear filenames, use tmp file */
1193     g_free(data->openFileName);
1194     data->openFileName = NULL;
1195
1196     g_free(data->saveFileName);
1197     data->saveFileName = NULL;     
1198
1199     switch (data->filter)
1200     {
1201         case FORMAT_PCM:
1202              data->saveFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_FILE);
1203              data->openFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_FILE);
1204              break;
1205
1206          case FORMAT_PCMA:
1207              data->saveFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_PCMA_FILE);
1208              data->openFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_PCMA_FILE);
1209              break;
1210              
1211          case FORMAT_PCMU:
1212              data->saveFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_PCMU_FILE);
1213              data->openFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_PCMU_FILE);
1214              break;
1215
1216          case FORMAT_WAV:
1217              data->saveFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_WAV_FILE);
1218              data->openFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_WAV_FILE);
1219              break;
1220
1221          case FORMAT_ILBC:
1222          default:
1223              data->saveFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_ILBC_FILE);
1224              data->openFileName = g_strdup_printf("%s/%s", get_default_dir(), DEFAULT_TMP_ILBC_FILE);
1225             break;
1226     }
1227
1228     ULOG_INFO("%s() - creating pipelines", G_STRFUNC);
1229     /* start recording */
1230     /* create related pipelines */
1231     if (createPipeline(data, PIPELINE_REC))
1232     {
1233         ULOG_INFO("%s() - starting recording", G_STRFUNC);
1234         /* start recording */
1235         gst_element_set_state(GST_ELEMENT(data->recPipeline), 
1236                            GST_STATE_PLAYING);
1237
1238         /* update display */
1239         gtk_entry_set_text(GTK_ENTRY(data->mainViewData.fileNameEntry), 
1240             data->saveFileName);
1241
1242         setAppState(data, APPSTATE_RECORDING);
1243         gtk_widget_set_sensitive(data->buttonSaveAs, TRUE);
1244         gtk_window_set_title(GTK_WINDOW(data->mainView), RECORDER_FILE_UNTITLED);
1245         data->file_format = data->filter;
1246     }
1247     else
1248     {
1249         ULOG_ERR("Could not create rec pipeline!");
1250         hildon_banner_show_information(GTK_WIDGET(data->mainView), GTK_STOCK_DIALOG_ERROR, _("Could not create pipeline"));
1251         setAppState(data, APPSTATE_READY);
1252     }
1253  
1254     ULOG_DEBUG("%s() - end", G_STRFUNC);     
1255 }
1256
1257 static void cbPlay(GtkWidget* widget, AppData *data) 
1258 {
1259     const gchar * file = NULL;
1260      
1261     g_assert(NULL != data);
1262
1263     ULOG_DEBUG("%s() - begin", G_STRFUNC);
1264
1265     file = gtk_entry_get_text(GTK_ENTRY(data->mainViewData.fileNameEntry));
1266     if (NULL == data->openFileName || NULL == file || strcmp(file, RECORDER_FILE_UNTITLED) == 0) 
1267     {
1268         ULOG_WARN("%s() - nothing to play", G_STRFUNC);
1269         return;
1270     }
1271
1272     openPlayPipeline(data);
1273      
1274     if (APPSTATE_PLAYING == getAppState(data))
1275     {
1276          if (GST_IS_ELEMENT(data->playPipeline)) 
1277          {
1278              gst_element_set_state(GST_ELEMENT(data->playPipeline), GST_STATE_PAUSED);
1279              setAppState(data, APPSTATE_READY);
1280          }
1281          return;
1282     }
1283
1284     if (APPSTATE_READY != getAppState(data))
1285     {
1286         ULOG_WARN("%s() - state different than PLAYING or READY -> return", G_STRFUNC);
1287         return;
1288     }
1289
1290     ULOG_INFO("filename %s", file);
1291      
1292     /*openPlayPipeline( data );*/
1293      
1294     /*if ( ! GST_IS_ELEMENT(data->playPipeline) ) 
1295     {
1296         if (g_strrstr(data->openFileName, EXTENSION_RAW))
1297         {
1298              ULOG_INFO("cbOpen() - file was raw, assuming audio/x-raw-int, 8kHz, 1 ch, 16-bit");
1299              destroyPipelines(data);
1300              createPipeline(data, PIPELINE_PLAY);      
1301         }
1302     }*/
1303      
1304     if (! GST_IS_ELEMENT(data->playPipeline))
1305     {
1306         ULOG_WARN("%s() - playPipeline does not exist", G_STRFUNC);
1307         return;
1308     }
1309
1310     gst_element_set_state(GST_ELEMENT(data->playPipeline), 
1311                GST_STATE_PLAYING);
1312
1313     setAppState(data, APPSTATE_PLAYING);
1314
1315     g_timeout_add(PLAY_UPDATE_INTERVAL, (GSourceFunc)cbCheckPosition, data);
1316
1317     ULOG_DEBUG("%s() - end", G_STRFUNC);
1318 }
1319
1320 static void cbStop(GtkWidget* widget, AppData *data) 
1321 {
1322     g_assert(NULL != data);
1323
1324     ULOG_DEBUG("%s() - begin", G_STRFUNC); 
1325
1326     /* check if we are playing/recording */
1327
1328     /*
1329     if (APPSTATE_PLAYING != getAppState(data) &&
1330     APPSTATE_RECORDING != getAppState(data))
1331     {
1332       ULOG_WARN("cbStop() - state different than PLAYING or RECORDING "
1333           "-> return");
1334       return;
1335     } 
1336     */
1337
1338     /* stop playing or recording */
1339     gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(data->buttonPlay), GTK_STOCK_MEDIA_PLAY);
1340     gtk_widget_set_state(data->buttonPlay, GTK_STATE_NORMAL);
1341     gtk_widget_set_state(data->buttonRec, GTK_STATE_NORMAL);
1342
1343     /* destroy related pipeline */
1344     switch(getAppState(data))
1345     {
1346         case APPSTATE_PLAYING:
1347             /* don't destroy the playing pipeline. Instead, set the pipeline to PAUSED */
1348             /* destroyPipeline(data, PIPELINE_PLAY); */
1349             ULOG_INFO("%s() - Setting playPipeline state to PAUSED", G_STRFUNC);
1350             gst_element_set_state(GST_ELEMENT(data->playPipeline), 
1351                      GST_STATE_PAUSED);
1352             /* flow through */
1353         case APPSTATE_READY:
1354             /* seek to zero, but not for PCM pipeline */
1355             /* if (data->playPipelineType == PIPELINE_PLAY || seekToZero(data, GST_ELEMENT(data->playPipeline))) */
1356             if ( !GST_IS_ELEMENT(data->playPipeline) || seekToZero(data, GST_ELEMENT(data->playPipeline)))
1357             {
1358               gtk_adjustment_set_value( 
1359                    GTK_ADJUSTMENT(data->mainViewData.adjustment), 0);
1360               gtk_adjustment_value_changed(GTK_ADJUSTMENT(data->mainViewData.adjustment));
1361               gtk_entry_set_text(GTK_ENTRY(data->mainViewData.stateEntry), 
1362                          RECORDER_MSG_STOPPED);  
1363             }
1364             break;
1365
1366         case APPSTATE_RECORDING:
1367         {
1368             gdouble len = -1.0;
1369             gst_element_set_state(GST_ELEMENT(data->recPipeline), 
1370                  GST_STATE_PAUSED);
1371             destroyPipeline(data, PIPELINE_REC);
1372             gtk_widget_set_sensitive(data->buttonSaveAs, TRUE);
1373             data->saved = FALSE;
1374
1375             len = guessMediaLength(data);
1376             if (len > 0.0)
1377                 setLength(data, len);
1378
1379             break;
1380         }
1381      
1382         default:
1383             /* seekToZero(data, GST_ELEMENT(data->playPipeline)); */
1384             /* should not come here */
1385             break;
1386     }
1387
1388     setAppState(data, APPSTATE_READY);
1389
1390     ULOG_DEBUG("%s() - end", G_STRFUNC); 
1391 }
1392
1393
1394 /* ui construction functions */
1395
1396 static GtkWidget* createToolBar(AppData *data)
1397 {
1398      GtkToolbar* toolBar = NULL;
1399      
1400      GtkToolItem* new = NULL;
1401      GtkToolItem* open = NULL;
1402      GtkToolItem* save = NULL;
1403      GtkToolItem* saveas = NULL;
1404      GtkToolItem* sep = NULL;
1405      GtkToolItem* play = NULL;
1406      GtkToolItem* rec = NULL;
1407      GtkToolItem* stop = NULL;
1408      
1409      /* create buttons */
1410      new = gtk_tool_button_new_from_stock(GTK_STOCK_NEW); 
1411      open = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN); 
1412      save = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
1413      data->buttonSave = GTK_WIDGET(save);
1414      saveas = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE_AS);
1415      data->buttonSaveAs = GTK_WIDGET(saveas);
1416      gtk_widget_set_sensitive(data->buttonSave, FALSE);
1417      gtk_widget_set_sensitive(data->buttonSaveAs, FALSE);
1418      data->saved = TRUE;
1419
1420      rec = gtk_tool_button_new_from_stock(GTK_STOCK_MEDIA_RECORD); 
1421      data->buttonRec = GTK_WIDGET(rec);
1422      play = gtk_tool_button_new_from_stock(GTK_STOCK_MEDIA_PLAY); 
1423      data->buttonPlay = GTK_WIDGET(play);
1424      stop = gtk_tool_button_new_from_stock(GTK_STOCK_MEDIA_STOP);
1425      
1426      /* create separator */
1427      sep  = gtk_separator_tool_item_new();
1428
1429      /* create the toolbar itself */
1430      toolBar = GTK_TOOLBAR(gtk_toolbar_new());
1431
1432      /* add items to toolbar */
1433      gtk_toolbar_insert(toolBar, new, -1);
1434      gtk_toolbar_insert(toolBar, open, -1);
1435      /*
1436      gtk_toolbar_insert(toolBar, save, -1);
1437      */
1438      gtk_toolbar_insert(toolBar, saveas, -1);
1439      gtk_toolbar_insert(toolBar, sep,  -1);
1440      gtk_toolbar_insert(toolBar, rec, -1);
1441      gtk_toolbar_insert(toolBar, play, -1);
1442      gtk_toolbar_insert(toolBar, stop, -1);
1443
1444      /* connect signals */
1445      g_signal_connect(G_OBJECT(new), "clicked",
1446               G_CALLBACK(cbNew), 
1447               data);
1448      g_signal_connect(G_OBJECT(open), "clicked",
1449               G_CALLBACK(cbOpen), 
1450               data);
1451      /*
1452      g_signal_connect(G_OBJECT(save), "clicked",
1453               G_CALLBACK(cbSave), 
1454               data);
1455      */
1456      g_signal_connect(G_OBJECT(saveas), "clicked",
1457               G_CALLBACK(cbSaveAs), 
1458               data);
1459      g_signal_connect(G_OBJECT(rec), "clicked",
1460               G_CALLBACK(cbRec), 
1461               data);
1462      g_signal_connect(G_OBJECT(play), "clicked",
1463               G_CALLBACK(cbPlay), 
1464               data);
1465      g_signal_connect(G_OBJECT(stop), "clicked",
1466               G_CALLBACK(cbStop), 
1467               data);
1468
1469      return GTK_WIDGET(toolBar);
1470
1471 }
1472
1473 static void cbItemGroupChanged( gpointer data )
1474 {
1475     AppData* app = (AppData* ) data;
1476     GValue active ={G_TYPE_INVALID};
1477     gint pcma,ilbc,pcm;
1478     
1479     g_value_init(&active, G_TYPE_INT);
1480
1481     g_object_get_property(G_OBJECT(app->radio_pcma), "active", &active);
1482     pcma = g_value_get_int(&active);
1483     g_object_get_property(G_OBJECT(app->radio_ilbc), "active", &active);
1484     ilbc = g_value_get_int(&active);
1485     g_object_get_property(G_OBJECT(app->radio_pcm), "active", &active);
1486     pcm = g_value_get_int(&active);
1487     
1488     ULOG_INFO("change type pcma=%d ilbc=%d pcm=%d",pcma, ilbc, pcm);
1489     if ( pcma == 1 )
1490         app->filter = FORMAT_PCMA;
1491     else if ( ilbc == 1 )
1492         app->filter = FORMAT_ILBC;
1493     else if ( pcm == 1 )
1494         app->filter = FORMAT_WAV;
1495     else
1496         app->filter = -1;
1497   
1498     ULOG_INFO("filter type=%d", app->filter);
1499 }
1500
1501 static void cbItemClose(GtkWidget *widget, gpointer data)
1502 {
1503     g_assert(data);
1504
1505     if (!closeFile(data))
1506         return;
1507
1508     gtk_main_quit();
1509 }
1510
1511 /* Create the menu items needed for the main view */
1512 static void createMenu( AppData *data )
1513 {
1514     /* Create needed variables */
1515     GSList *group = NULL;
1516     GtkMenu *main_menu;
1517     GtkWidget *menu_file;
1518     GtkWidget *menu_others;
1519     GtkWidget *item_file;
1520     GtkWidget *item_file_open;
1521     GtkWidget *item_file_save_as;
1522     GtkWidget *item_others;
1523     GtkWidget *item_pcma;
1524     GtkWidget *item_pcmu;
1525     GtkWidget *item_ilbc;
1526     GtkWidget *item_pcm;
1527     GtkWidget *item_settings;
1528     /*
1529     GtkWidget *item_radio_type1;
1530     */
1531     GtkWidget *item_close;
1532     GtkWidget *item_separator;
1533
1534     /* Get the menu from view */
1535     main_menu = GTK_MENU(gtk_menu_new());
1536     hildon_window_set_menu(data->mainView, main_menu);
1537     
1538     /* Create new submenu for "Others" */
1539     menu_file = gtk_menu_new ();
1540     menu_others = gtk_menu_new ();
1541
1542     /* Create menu items */
1543     item_file = gtk_menu_item_new_with_label (_("File"));
1544     item_file_open = gtk_menu_item_new_with_label(_("Open..."));
1545     item_file_save_as = gtk_menu_item_new_with_label(_("Save as..."));
1546     item_others = gtk_menu_item_new_with_label (_("Recording format"));
1547     item_settings = gtk_menu_item_new_with_label (_("Settings"));
1548     
1549     item_pcma = gtk_radio_menu_item_new_with_label(
1550         group, FORMAT_NAME_PCMA);
1551     item_ilbc = gtk_radio_menu_item_new_with_label_from_widget(
1552         GTK_RADIO_MENU_ITEM(item_pcma), FORMAT_NAME_ILBC);
1553     item_pcmu = gtk_radio_menu_item_new_with_label_from_widget(
1554         GTK_RADIO_MENU_ITEM(item_pcma), FORMAT_NAME_PCMU);
1555     item_pcm = gtk_radio_menu_item_new_with_label_from_widget(
1556         GTK_RADIO_MENU_ITEM(item_pcma), FORMAT_NAME_WAV);
1557     
1558     data->filter = get_default_filter();
1559
1560     if (data->filter == FORMAT_ILBC)
1561         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_ilbc), TRUE);
1562     else if (data->filter == FORMAT_WAV)
1563         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_pcm), TRUE);
1564     else if (data->filter == FORMAT_PCMA)
1565         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_pcma), TRUE);
1566     else 
1567     {
1568         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_ilbc), TRUE);
1569         data->filter = FORMAT_ILBC;
1570     }
1571     
1572     data->radio_pcma = item_pcma;
1573     data->radio_ilbc = item_ilbc;
1574     data->radio_pcm = item_pcm;        
1575     /*
1576     data->radiotype = item_radio_type1;
1577     */
1578     item_close = gtk_menu_item_new_with_label(_("Close"));
1579     item_separator = gtk_separator_menu_item_new();
1580     
1581     /* Add menu items to right menus */
1582     gtk_menu_append( main_menu, item_file );
1583     gtk_menu_append( menu_file, item_file_open );
1584     gtk_menu_append( menu_file, item_file_save_as );
1585     gtk_menu_append( main_menu, item_others );
1586     gtk_menu_append( menu_others, item_pcm );
1587     gtk_menu_append( menu_others, item_pcma );
1588     gtk_menu_append( menu_others, item_ilbc);
1589     
1590     gtk_menu_append( main_menu, item_settings );
1591     
1592     gtk_menu_append( main_menu, item_close );
1593
1594     /* Add others submenu to the "Others" item */
1595     gtk_menu_item_set_submenu(
1596         GTK_MENU_ITEM(item_file), menu_file );
1597     gtk_menu_item_set_submenu(
1598         GTK_MENU_ITEM(item_others), menu_others );
1599
1600     /* Attach the callback functions to the activate signal */
1601     g_signal_connect( G_OBJECT( item_file_open), "activate",
1602         GTK_SIGNAL_FUNC (cbOpen), data);
1603     g_signal_connect( G_OBJECT( item_file_save_as), "activate",
1604         GTK_SIGNAL_FUNC (cbSaveAs), data);
1605     g_signal_connect( G_OBJECT( item_settings ), "activate",
1606         GTK_SIGNAL_FUNC (cbSettings), data);
1607     
1608     g_signal_connect( G_OBJECT( item_close ), "activate",
1609         GTK_SIGNAL_FUNC (cbItemClose), data);
1610
1611     g_signal_connect_swapped(G_OBJECT(item_pcma), "activate", G_CALLBACK(cbItemGroupChanged), data);
1612     g_signal_connect_swapped(G_OBJECT(item_pcm), "activate", G_CALLBACK(cbItemGroupChanged), data);
1613     
1614     /* Make all menu widgets visible */
1615
1616     gtk_widget_show_all( GTK_WIDGET( main_menu ) );
1617 }
1618
1619 gboolean maemo_recorder_ui_new(AppData *data)
1620 {
1621     HildonProgram *app = NULL;
1622     HildonWindow *window = NULL;
1623     GtkWidget *hbox = NULL;
1624     GtkWidget *vbox = NULL;
1625     GtkWidget *label = NULL;
1626     GtkWidget *entry1 = NULL;
1627     GtkWidget *entry2 = NULL;
1628     GtkWidget *entry3 = NULL;
1629     GtkWidget *toolBar = NULL;
1630     GtkWidget *table = NULL;
1631     GtkWidget *scale = NULL;
1632     GtkObject *adjustment = NULL;
1633
1634     g_assert(NULL != data);
1635
1636     app = HILDON_PROGRAM(hildon_program_get_instance());
1637     g_set_application_name(RECORDER_APP_TITLE);
1638      
1639     /* main window */
1640     window = HILDON_WINDOW(hildon_window_new());
1641
1642     hildon_program_add_window(app, window);
1643
1644     /* content for main view */
1645
1646     /* create vbox, divides control area and view area */
1647     vbox = gtk_vbox_new(FALSE, 0);
1648
1649     /* create hbox to divide control area */
1650     hbox = gtk_hbox_new(FALSE, HILDON_MARGIN_DEFAULT);
1651
1652     /* create toolbar */
1653     toolBar = createToolBar(data);
1654
1655     /* create table for labels */
1656     table = gtk_table_new (4, 3, FALSE);
1657     gtk_table_set_homogeneous(GTK_TABLE(table), FALSE);
1658
1659     gtk_table_set_row_spacings (GTK_TABLE (table), 4);
1660     gtk_table_set_col_spacings (GTK_TABLE (table), 0);
1661
1662     label = gtk_label_new_with_mnemonic(_("Filename:"));
1663     /*
1664     gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
1665     */
1666     gtk_table_attach_defaults (GTK_TABLE (table),
1667             label,
1668             0, 1, 0, 1);
1669
1670     entry1 = gtk_entry_new ();
1671     gtk_entry_set_has_frame(GTK_ENTRY(entry1), FALSE);
1672     gtk_entry_set_text (GTK_ENTRY (entry1), _(RECORDER_FILE_UNTITLED));
1673     gtk_table_attach_defaults (GTK_TABLE (table), entry1, 1, 3, 0, 1);
1674     gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry1);
1675
1676     label = gtk_label_new_with_mnemonic (_("Length:"));
1677     /*
1678     gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
1679     */
1680     gtk_table_attach_defaults (GTK_TABLE (table),
1681             label,
1682             0, 1, 1, 2);
1683
1684     entry2 = gtk_entry_new ();
1685     gtk_entry_set_has_frame(GTK_ENTRY(entry2), FALSE);
1686     gtk_entry_set_text (GTK_ENTRY (entry2), "0:00.00");
1687     gtk_table_attach_defaults (GTK_TABLE (table), entry2, 1, 3, 1, 2);
1688     gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry2);
1689
1690     label = gtk_label_new_with_mnemonic (_("State:"));
1691     /*
1692     gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
1693     */
1694     gtk_table_attach_defaults (GTK_TABLE (table),
1695             label,
1696             0, 1, 2, 3);
1697
1698     entry3 = gtk_entry_new ();
1699     gtk_entry_set_has_frame(GTK_ENTRY(entry3), FALSE);
1700     gtk_entry_set_text (GTK_ENTRY (entry3), RECORDER_MSG_READY);
1701     gtk_table_attach_defaults (GTK_TABLE (table), entry3, 1, 3, 2, 3);
1702     gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry3);
1703
1704     adjustment = gtk_adjustment_new (0.00,
1705                   0.00,
1706                   100.00,
1707                   0.01,
1708                   0.01,
1709                   0);
1710
1711     scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment));
1712       
1713 /*     gtk_table_attach_defaults (GTK_TABLE (table),
1714                 label,
1715                 0, 3, 2, 3);
1716 */
1717     /* connect signals */
1718     g_signal_connect(G_OBJECT(adjustment), "value-changed", G_CALLBACK(cbUserSeek), data);
1719     g_signal_connect(G_OBJECT(scale), "format-value", G_CALLBACK(cbFormatSeekbarValue), data);
1720     g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(cbDestroy), data);
1721
1722     /* packing the view */
1723     gtk_container_add (GTK_CONTAINER(window), vbox);
1724     gtk_box_pack_start (GTK_BOX(vbox), table, FALSE, TRUE, 0);
1725     gtk_box_pack_start (GTK_BOX(vbox), scale, FALSE, FALSE, 0);
1726 /*    gtk_box_pack_start (GTK_BOX(vbox), hbox, TRUE, TRUE, 0); */
1727     
1728     hildon_window_add_toolbar(window, GTK_TOOLBAR(toolBar)); 
1729
1730     /* initialise the ui */
1731     gtk_entry_set_editable(GTK_ENTRY(entry1), FALSE);
1732     gtk_entry_set_editable(GTK_ENTRY(entry2), FALSE);
1733     gtk_entry_set_editable(GTK_ENTRY(entry3), FALSE);
1734
1735     /* store needed widgets */
1736     data->app = app;
1737     data->mainView = window;
1738     data->mainViewData.toolBar = GTK_WIDGET(toolBar);
1739     data->mainViewData.fileNameEntry = GTK_WIDGET(entry1);
1740     data->mainViewData.lengthEntry = GTK_WIDGET(entry2);
1741     data->mainViewData.stateEntry = GTK_WIDGET(entry3);
1742     data->mainViewData.adjustment = GTK_OBJECT(adjustment);
1743
1744     /* show the app */
1745     gtk_widget_show_all(GTK_WIDGET(window));
1746
1747     createMenu(data);
1748     
1749     return TRUE;
1750 }
1751
1752 void 
1753 maemo_recorder_mime_open(gpointer user_data, gint argc, gchar **argv)
1754 {
1755     AppData *data;
1756
1757     ULOG_DEBUG("%s with %d arguments", __FUNCTION__, argc);
1758
1759     if (argc == 0)
1760         return;
1761
1762     g_assert(user_data);
1763     data = (AppData *) user_data;
1764
1765     if (argv[0] != NULL)
1766     {
1767         ULOG_DEBUG("request to open %s", argv[0]);
1768         g_free(data->mimeURI);
1769         data->mimeURI = g_strdup(argv[0]);
1770         g_idle_add(openURI, (gpointer) data); 
1771         gtk_window_present(GTK_WINDOW(data->mainView));
1772     }
1773 }
1774
1775 static void seekToTime(GstElement *pipeline, gdouble secs)
1776 {
1777     g_assert(NULL != pipeline);
1778     ULOG_DEBUG("Seeking to: %.2f", secs);
1779
1780     /* time must be nanoseconds */
1781     if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
1782             GST_SEEK_TYPE_SET, (gint64) (secs * GST_SECOND),
1783             GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
1784     {
1785         ULOG_WARN("seekToTime failed!");
1786         return;
1787     }
1788     ULOG_DEBUG("seekToTime succeeded");
1789 }
1790
1791 static gboolean seekToZero(AppData *data, GstElement *pipeline)
1792 {
1793     gint plType;
1794     g_assert(NULL != pipeline);
1795     ULOG_DEBUG("Seeking to zero");
1796
1797     /* time must be nanoseconds */
1798     if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
1799             GST_SEEK_TYPE_SET, (gint64) 0,
1800             GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
1801     {
1802         ULOG_ERR("seekToZero failed! Trying to destroy and re-create pipeline");
1803         plType = data->playPipelineType;
1804
1805         /* gst_element_set_state(pipeline, GST_STATE_READY); */
1806         destroyPipeline(data, plType);
1807         return createPipeline(data, plType);
1808     }
1809
1810     ULOG_DEBUG("seekToZero succeeded");
1811     return TRUE;
1812 }
1813
1814 static void setLength(AppData *data, gdouble secs)
1815 {
1816     guint mins = 0;
1817     gchar *tmp;
1818
1819     if (secs < 0.0)
1820         return;
1821
1822     if (secs > 0)
1823     {
1824         g_object_set(G_OBJECT(data->mainViewData.adjustment), 
1825             "upper", secs, 
1826             NULL);    
1827         gtk_adjustment_changed(GTK_ADJUSTMENT(data->mainViewData.adjustment));
1828     }
1829
1830     if (secs >= 60.0)
1831     {
1832         mins = secs / 60;
1833         secs -= mins * 60.0;
1834     }
1835
1836     tmp = g_strdup_printf("%u:%05.2f", mins, secs);
1837
1838     /*
1839     ULOG_INFO("Setting length to %s", tmp);
1840     */
1841     gtk_entry_set_text(GTK_ENTRY(data->mainViewData.lengthEntry), 
1842                  tmp);
1843     g_free(tmp);
1844 }
1845
1846 static gdouble guessMediaLength(AppData *data)
1847 {
1848     GnomeVFSFileSize size = 0;
1849     gdouble bitrate = 0.0;
1850     gdouble len = -1.0;
1851
1852     if (data->openFileName)
1853         size = getFileLength(data->openFileName);
1854     else
1855         return -1.0;
1856            
1857     if (size == 0)
1858         return -1.0;
1859
1860     ULOG_DEBUG("file size: %llu bytes", size);
1861
1862     switch (data->file_format)
1863     {
1864         case FORMAT_ILBC:
1865             bitrate = ILBC_BITRATE_20;
1866             break; 
1867             
1868         case FORMAT_PCMA:
1869         case FORMAT_PCMU:
1870             bitrate = PCMA_BITRATE;
1871             break;
1872
1873         default:
1874             return -1.0;
1875     }
1876
1877     if (bitrate == 0.0)
1878         return -1.0;
1879
1880     len = (((gdouble) size) * 8.0) / (bitrate);
1881     ULOG_DEBUG("guessed media length: %.2f secs", len);
1882
1883     return len;
1884 }
1885
1886 static GstCaps *createCapsFilter(AudioFormat format)
1887 {
1888     switch (format)
1889     {
1890         case FORMAT_ILBC:
1891             return gst_caps_new_simple(
1892                 GST_TYPE_ILBC,
1893                 "rate", G_TYPE_INT, ILBC_RATE,
1894                 "channels", G_TYPE_INT, DEFAULT_CHANNELS,
1895                 "mode", G_TYPE_INT, 20, /* 20 ms frames */
1896                 NULL);
1897         case FORMAT_PCMA:
1898             return gst_caps_new_simple(
1899                 GST_TYPE_PCMA,
1900                 "rate", G_TYPE_INT, DEFAULT_RATE,
1901                 "channels", G_TYPE_INT, DEFAULT_CHANNELS,
1902                 NULL);
1903         case FORMAT_PCMU:
1904             return gst_caps_new_simple(
1905                 GST_TYPE_PCMU,
1906                 "rate", G_TYPE_INT, DEFAULT_RATE,
1907                 "channels", G_TYPE_INT, DEFAULT_CHANNELS,
1908                 NULL);
1909         case FORMAT_WAV:
1910         case FORMAT_PCM:
1911             return gst_caps_new_simple(
1912                 GST_TYPE_PCM,
1913                 "rate", G_TYPE_INT, PCM_RATE,
1914                 "signed", G_TYPE_BOOLEAN, TRUE,
1915                 "channels", G_TYPE_INT, DEFAULT_CHANNELS,
1916                 "endianness", G_TYPE_INT, PCM_ENDIANNESS,
1917                 "width", G_TYPE_INT, PCM_WIDTH,
1918                 "depth", G_TYPE_INT, PCM_DEPTH,
1919                 NULL);
1920         default:
1921             ULOG_WARN("%s(): creating ANY caps", G_STRFUNC);
1922             return gst_caps_new_any();
1923     }
1924 }
1925
1926 static gboolean cbStopPlayback(AppData *data)
1927 {
1928     gint ret;
1929     ULOG_INFO("Stopping playback");
1930    
1931     g_assert(data != NULL);
1932
1933     ret = gst_element_set_state(GST_ELEMENT(data->playPipeline), 
1934                         GST_STATE_PAUSED);
1935     if (seekToZero(data, GST_ELEMENT(data->playPipeline)))
1936     {
1937         gtk_adjustment_set_value( 
1938            GTK_ADJUSTMENT(data->mainViewData.adjustment), 0);
1939         gtk_adjustment_value_changed(GTK_ADJUSTMENT(data->mainViewData.adjustment));
1940     }
1941     setAppState(data, APPSTATE_READY);
1942     gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(data->buttonPlay), GTK_STOCK_MEDIA_PLAY);
1943     gtk_widget_set_state(data->buttonPlay, GTK_STATE_NORMAL);
1944
1945     return FALSE;
1946 }
1947
1948 static void cbUserSeek(GtkAdjustment *adjustment, gpointer data)
1949 {   
1950     /*ULOG_INFO("cbUserSeek");*/
1951     AppData *app;
1952
1953     g_return_if_fail(data != NULL);
1954     app = (AppData *) data;
1955
1956     if (getAppState(app) != APPSTATE_READY || NULL == app->playPipeline)
1957         return;
1958
1959     seekToTime(app->playPipeline, gtk_adjustment_get_value(adjustment));
1960 }
1961
1962 static gchar *cbFormatSeekbarValue(GtkScale *scale, gdouble value)
1963 {
1964 /*    ULOG_INFO("cbFormatSeekbarValue");*/
1965     gint mins = 0;
1966     gint digits = gtk_scale_get_digits(scale);
1967
1968     if (value >= 60.0)
1969     {
1970         mins = value / 60;
1971         value -= mins * 60.0;
1972         return g_strdup_printf("%d:%0*.*f", mins, digits + 3, digits, value);
1973     }
1974     /* mins:sec.frac */
1975     return g_strdup_printf("%0.*f", digits, value);
1976 }
1977
1978 static gboolean cbUpdateRecLength(AppData *data)
1979 {
1980     struct timeval tv;
1981     guint mins = 0;
1982     gdouble secs;
1983     gchar *tmp;
1984
1985     if (gettimeofday(&tv, NULL) != 0)
1986         return FALSE;
1987
1988     secs = tv.tv_sec - data->recStartTv.tv_sec;
1989     secs += ((tv.tv_usec - data->recStartTv.tv_usec) / 1000000.0);
1990
1991     if (secs >= 60.0)
1992     {
1993         mins = secs / 60;
1994         secs -= mins * 60.0;
1995         tmp = g_strdup_printf("%u:%05.2f", mins, secs);
1996     }
1997     else
1998         tmp = g_strdup_printf("%0.2f", secs);
1999
2000     gtk_entry_set_text(GTK_ENTRY(data->mainViewData.lengthEntry), 
2001                  tmp);
2002     g_free(tmp);
2003
2004     if (getAppState(data) == APPSTATE_RECORDING)
2005         return TRUE;
2006
2007     data->recUpdateId = 0;
2008     return FALSE;
2009 }
2010