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