2 * Dictix / DixUi - dix-ui.vala
4 * Copyright (C) Martin Blanchard 2011 <tinram@gmx.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
42 public enum NotificationDelay
{
48 public class Ui
: Window
{
49 private State last_state
;
50 private bool was_recording
;
51 private bool was_playing
;
53 private TreeIter now_playing_iter
;
54 private TreeIter last_played_iter
;
57 private Gtk
.Action save_as_action
;
58 private Gtk
.Action delete_action
;
59 private Gtk
.Action properties_action
;
61 private Properties properties_dialog
;
63 private Preferences preferences_dialog
;
65 private Gtk
.Action play_pause_action
;
66 private Gtk
.Action previous_action
;
67 private Gtk
.Action next_action
;
68 private Gtk
.Action record_stop_action
;
70 private Button play_pause_button
;
71 private Adjustment scale_adjustment
;
72 private Scale seek_scale
;
73 private Label position_label
;
74 private Label duration_label
;
76 private Label status_label
;
77 private Button record_stop_button
;
79 private Spinner spinner
;
80 private Label no_records_label
;
81 private TreeView tree_view
;
83 private FileFilter file_filter
;
85 private Image play_image
;
86 private Image pause_image
;
88 public Ui (Builder builder
, UIManager manager
) {
89 AccelGroup accel_group
= null;
90 CellRenderer renderer
= null;
91 CellRendererText editable
= null;
92 TreeViewColumn column
= null;
93 TreeSelection selection
= null;
96 last_state
= State
.UNKNOWN
;
100 Window
.set_default_icon_name (Config
.PACKAGE
);
101 this
.set_title (Config
.PACKAGE
);
102 this
.set_default_size (400, 280);
103 // this.set_resizable (false); /*TODO: fix size ? */
104 accel_group
= manager
.get_accel_group ();
105 if (accel_group
!= null) {
106 this
.add_accel_group (accel_group
);
109 box
= new
Box (Orientation
.VERTICAL
, 0);
110 manager
.insert_action_group (builder
.get_object ("action-group") as Gtk
.ActionGroup
, 0);
111 box
.pack_start (manager
.get_widget ("/menubar") as Widget
, false, false, 0);
112 box
.pack_start (builder
.get_object ("main-box") as Widget
, true, true, 0);
113 this
.add (box as Widget
);
115 save_as_action
= builder
.get_object ("save-as-action") as Gtk
.Action
;
116 delete_action
= builder
.get_object ("delete-action") as Gtk
.Action
;
117 properties_action
= builder
.get_object ("properties-action") as Gtk
.Action
;
119 file_filter
= builder
.get_object ("file-filter") as FileFilter
;
120 file_filter
.set_name (_("Audio files"));
122 properties_dialog
= new
Properties (builder
);
123 properties_dialog
.set_transient_for (this as Window
);
125 preferences_dialog
= new
Preferences (builder
);
127 play_pause_action
= builder
.get_object ("play-pause-action") as Gtk
.Action
;
128 previous_action
= builder
.get_object ("previous-action") as Gtk
.Action
;
129 next_action
= builder
.get_object ("next-action") as Gtk
.Action
;
130 record_stop_action
= builder
.get_object ("record-stop-action") as Gtk
.Action
;
132 play_pause_button
= builder
.get_object ("play-pause-button") as Button
;
133 scale_adjustment
= builder
.get_object ("scale-adjustment") as Adjustment
;
134 seek_scale
= builder
.get_object ("seek-scale") as Scale
;
135 position_label
= builder
.get_object ("position-label") as Label
;
136 duration_label
= builder
.get_object ("duration-label") as Label
;
138 status_label
= builder
.get_object ("status-label") as Label
;
139 record_stop_button
= builder
.get_object ("record-stop-button") as Button
;
141 spinner
= builder
.get_object ("spinner") as Spinner
;
142 no_records_label
= builder
.get_object ("no-records-label") as Label
;
143 tree_view
= builder
.get_object ("tree-view") as TreeView
;
145 tree_view
.set_model (Global
.records as TreeModel
);
147 column
= new
TreeViewColumn ();
148 column
.set_title (_("State"));
149 column
.set_min_width (36); /* FIXME; get rid of this hack, columns should auto-size... */
150 column
.set_expand (false);
151 column
.set_resizable (false);
152 renderer
= new
CellRendererPixbuf ();
153 renderer
.set ("stock-size", IconSize
.MENU
, "xpad", 8);
154 column
.pack_start (renderer
, true);
155 column
.set_attributes (renderer
, "stock-id", Column
.PIXBUF
);
156 tree_view
.append_column (column
);
158 column
= new
TreeViewColumn ();
159 column
.set_title (_("Title"));
160 column
.set_expand (true);
161 column
.set_resizable (false);
162 editable
= new
CellRendererText ();
163 editable
.set ("editable", true);
164 editable
.edited
.connect (on_cell_edited_cb
);
165 column
.pack_start (editable
, true);
166 column
.set_attributes (editable
, "text", Column
.TITLE
);
167 tree_view
.append_column (column
);
169 column
= new
TreeViewColumn ();
170 column
.set_title (_("Duration"));
171 column
.set_expand (false);
172 column
.set_resizable (false);
173 renderer
= new
CellRendererText ();
174 renderer
.set ("xpad", 8);
175 column
.set_cell_data_func (renderer
, on_duration_cell_render_cb
);
176 column
.pack_end (renderer
, false);
177 tree_view
.append_column (column
);
179 selection
= tree_view
.get_selection ();
180 selection
.changed
.connect (on_selection_changed_cb
);
182 play_image
= builder
.get_object ("play-image") as Image
;
183 pause_image
= builder
.get_object ("pause-image") as Image
;
185 builder
.connect_signals (this
);
186 Timeout
.add (100, on_timeout_cb
);
190 public void on_duration_cell_render_cb (CellLayout layout
, CellRenderer renderer
, TreeModel model
, TreeIter iter
) {
193 model
.get (iter
, Column
.DURATION
, out duration
);
194 renderer
.set ("text", Utils
.duration_to_string (duration
));
197 public void on_cell_edited_cb (string path
, string text
) {
198 TagWriter writer
= null;
199 TreeModel model
= null;
200 TreeSelection selection
= null;
205 model
= tree_view
.get_model ();
206 selection
= tree_view
.get_selection ();
208 if (unlikely (model
== null || selection
== null)) {
212 selection
.get_selected (null, out iter
);
213 model
.get (iter
, Column
.URI
, out uri
);
214 model
.get (iter
, Column
.TITLE
, out title
);
216 if (unlikely (uri
== null)) {
220 if (title
!= null && text
!= null) {
222 writer
= new
TagWriter (uri
);
224 writer
.set_title (text
);
230 public void update_ui_state (State state
) {
233 save_as_action
.set_sensitive (false);
234 delete_action
.set_sensitive (false);
235 properties_action
.set_sensitive (false);
237 play_pause_action
.set_sensitive (false);
238 previous_action
.set_sensitive (false);
239 next_action
.set_sensitive (false);
240 record_stop_action
.set_sensitive (false);
242 seek_scale
.set_sensitive (false);
246 no_records_label
.hide ();
250 play_pause_action
.set_sensitive (true);
252 status_label
.set_text (_("Ready"));
253 record_stop_action
.set_sensitive (true);
257 if (Global
.records
!= null) {
258 no_records_label
.hide ();
261 no_records_label
.show ();
270 public void on_notification_action_cb (Notification notification
, string action
) {
271 if (unlikely (Global
.state
!= State
.READY
)) {
275 if (action
== "stop") {
276 if (Global
.now_recording
== true) {
277 Global
.recorder
.stop_recording ();
282 public bool on_timeout_cb () {
283 TreeSelection selection
= null;
284 Notification notification
= null;
285 double progress
= 0.0;
288 TreePath path
= null;
291 if (unlikely (Global
.state
!= last_state
)) {
292 update_ui_state (Global
.state
);
293 last_state
= Global
.state
;
296 if (Global
.state
== State
.WORKING
) {
300 if (Global
.records
.get_iter_first (out iter
) == false) {
301 save_as_action
.set_sensitive (false);
302 delete_action
.set_sensitive (false);
303 properties_action
.set_sensitive (false);
305 play_pause_action
.set_sensitive (false);
306 previous_action
.set_sensitive (false);
307 next_action
.set_sensitive (false);
309 seek_scale
.set_sensitive (false);
311 no_records_label
.show ();
317 no_records_label
.hide ();
320 play_pause_action
.set_sensitive (true);
322 selection
= tree_view
.get_selection ();
324 if (likely (selection
!= null)) {
325 if (selection
.count_selected_rows () != 0) {
326 save_as_action
.set_sensitive (true);
327 delete_action
.set_sensitive (true);
328 properties_action
.set_sensitive (true);
330 seek_scale
.set_sensitive (true);
332 save_as_action
.set_sensitive (false);
333 delete_action
.set_sensitive (false);
334 properties_action
.set_sensitive (false);
336 seek_scale
.set_sensitive (false);
340 if (Global
.now_recording
!= was_recording
) {
341 was_recording
= Global
.now_recording
;
343 if (Global
.now_recording
== true) {
344 status_label
.set_text (_("Now recording..."));
345 record_stop_button
.set_label (_("Stop"));
348 notification
= new
Notification (_("Now recording audio..."),
351 notification
.set_urgency (Urgency
.NORMAL
);
352 notification
.add_action ("stop",
354 on_notification_action_cb
);
355 notification
.add_action ("ingnore",
357 on_notification_action_cb
);
358 notification
.show ();
359 Global
.notification_timer
= NotificationDelay
.SHORT
;
360 } catch (Error error
) {
361 warning (error
.message
);
364 status_label
.set_text (_("Ready"));
365 record_stop_button
.set_label (_("Record"));
366 Global
.notification_timer
= NotificationDelay
.INVALID
;
368 if (Global
.last_record
.stamp
!= 0) {
369 /* FIXME: Looks a bit dirty don't you think ? */
370 path
= tree_view
.get_model ().get_path (Global
.last_record
);
371 tree_view
.set_cursor (path
,
372 tree_view
.get_column (Column
.TITLE
),
374 tree_view
.grab_focus ();
376 Global
.last_record
.stamp
= 0;
381 if (Global
.now_recording
== true) {
382 if (Global
.notification_timer
>= 0) {
383 Global
.notification_timer
--;
385 if (Global
.notification_timer
== 0) {
386 if (notification
== null) {
388 notification
= new
Notification (_("Reminder : audio is still recording !"),
391 notification
.set_urgency (Urgency
.NORMAL
);
392 notification
.add_action ("stop",
394 on_notification_action_cb
);
395 notification
.add_action ("ingnore",
397 on_notification_action_cb
);
398 notification
.show ();
399 } catch (Error error
) {
400 warning (error
.message
);
404 Global
.notification_timer
= NotificationDelay
.NORMAL
;
409 if (now_playing_iter
!= last_played_iter
) {
410 if (last_played_iter
.stamp
!= 0) {
411 Global
.records
.set (last_played_iter
, Column
.PIXBUF
, null);
413 Global
.records
.set (now_playing_iter
, Column
.PIXBUF
, Stock
.MEDIA_PLAY
);
415 last_played_iter
= now_playing_iter
;
417 Global
.records
.get (now_playing_iter
, Column
.DURATION
, out duration
);
418 duration_label
.set_text (Utils
.duration_to_string (duration
));
421 if (Global
.seeking
== false) {
422 /* Lot's of nasty signal emition during seeking ... */
423 if (Global
.now_playing
!= was_playing
) {
424 was_playing
= Global
.now_playing
;
426 if (Global
.now_playing
== true) {
427 play_pause_button
.set_image (pause_image
);
428 Global
.records
.set (now_playing_iter
, Column
.PIXBUF
, Stock
.MEDIA_PLAY
);
431 play_pause_button
.set_image (play_image
);
432 if (Global
.transport_is_paused
== false) {
434 scale_adjustment
.set_value (0.0);
435 position_label
.set_text ("0:00");
436 if (now_playing_iter
.stamp
!= 0) {
437 Global
.records
.set (now_playing_iter
, Column
.PIXBUF
, null);
440 Global
.records
.set (now_playing_iter
, Column
.PIXBUF
, Stock
.MEDIA_PAUSE
);
445 if (Global
.now_playing
== true) {
446 progress
= Global
.player
.query_progress (out position
);
448 if (position
>= 0 && progress
>= 0.0) {
449 scale_adjustment
.set_value (progress
);
450 position_label
.set_text (Utils
.duration_to_string (position
));
458 [CCode (instance_pos
= -1)]
459 public void on_save_as_cb (Gtk
.Action source
) {
460 FileChooserDialog chooser
= null;
463 if (unlikely (Global
.state
!= State
.READY
)) {
467 chooser
= new
FileChooserDialog (_("Save as..."),
469 FileChooserAction
.SAVE
,
470 Stock
.CANCEL
, ResponseType
.CANCEL
,
471 Stock
.SAVE
, ResponseType
.ACCEPT
);
472 chooser
.set_do_overwrite_confirmation (true);
473 chooser
.add_filter (file_filter
);
475 response
= chooser
.run ();
477 if (response
== ResponseType
.ACCEPT
) {
484 [CCode (instance_pos
= -1)]
485 public void on_delete_action_cb (Gtk
.Action source
) {
486 TreeModel model
= null;
487 TreeSelection selection
= null;
492 if (unlikely (Global
.state
!= State
.READY
)) {
496 model
= tree_view
.get_model ();
497 selection
= tree_view
.get_selection ();
499 if (unlikely (model
== null || selection
== null)) {
503 if (selection
.count_selected_rows () != 0) {
504 selection
.get_selected (null, out iter
);
505 model
.get (iter
, Column
.URI
, out uri
);
507 if (Global
.now_playing
== true || Global
.transport_is_paused
== true) {
508 if (iter
== now_playing_iter
) {
509 Global
.player
.stop ();
511 now_playing_iter
.stamp
= 0;
512 last_played_iter
.stamp
= 0;
515 /* Avoid access of an invlid iter in the timeout_cb, seems to be working. */
516 if (iter
== now_playing_iter
) {
517 /* Should not a happenned but bugs come from here... */
518 now_playing_iter
.stamp
= 0;
520 if (iter
== last_played_iter
) {
521 last_played_iter
.stamp
= 0;
525 file
= File
.new_for_uri (uri
);
530 Global
.records
.remove (iter
);
531 } catch (Error error
) {
532 warning (_("Impossible to delete %s"), uri
);
538 [CCode (instance_pos
= -1)]
539 public void on_properties_action_cb (Gtk
.Action source
) {
540 TreeModel model
= null;
541 TreeSelection selection
= null;
544 if (unlikely (Global
.state
!= State
.READY
)) {
548 model
= tree_view
.get_model ();
549 selection
= tree_view
.get_selection ();
551 if (unlikely (model
== null || selection
== null)) {
555 if (selection
.count_selected_rows () != 0) {
556 selection
.get_selected (null, out iter
);
557 properties_dialog
.set_record (model
, iter
);
559 properties_dialog
.reset ();
562 properties_dialog
.run ();
565 [CCode (instance_pos
= -1)]
566 public void on_quit_action_cb (Gtk
.Action source
) {
570 [CCode (instance_pos
= -1)]
571 public void on_sound_levels_action_cb (Gtk
.Action source
) {
572 string[] argv
= {null, null, null, null};
574 argv
[0] = Environment
.find_program_in_path ("gnome-control-center");
576 // argv[2] = "--page";
577 // argv[3] = "input";
580 Process
.spawn_async (null, argv
, null, 0, null, null);
581 } catch (Error error
) {
582 warning (error
.message
);
586 [CCode (instance_pos
= -1)]
587 public void on_preferences_action_cb (Gtk
.Action source
) {
588 preferences_dialog
.run ();
591 [CCode (instance_pos
= -1)]
592 public void on_previous_action_cb (Gtk
.Action source
) {
593 TreeModel model
= null;
594 TreeSelection selection
= null;
598 if (unlikely (Global
.state
!= State
.READY
)) {
602 model
= tree_view
.get_model ();
603 selection
= tree_view
.get_selection ();
605 if (unlikely (model
== null || selection
== null)) {
609 if (selection
.count_selected_rows () != 0) {
610 selection
.get_selected (null, out iter
);
611 if (model
.iter_previous (ref iter
) == true) {
612 selection
.select_iter (iter
);
614 if (Global
.now_playing
== true) {
615 model
.get (iter
, Column
.URI
, out uri
);
617 Global
.player
.start_playing (uri
);
618 now_playing_iter
= iter
;
624 [CCode (instance_pos
= -1)]
625 public void on_play_pause_action_cb (Gtk
.Action source
) {
626 TreeModel model
= null;
627 TreeSelection selection
= null;
631 if (unlikely (Global
.state
!= State
.READY
)) {
635 if (Global
.now_playing
== false) {
636 if (Global
.transport_is_paused
== false) {
637 model
= tree_view
.get_model ();
640 if (model
.get_iter_first (out iter
) == false) {
641 /* No records listed, nothing to play... */
645 selection
= tree_view
.get_selection ();
646 if (selection
.count_selected_rows () == 0) {
647 model
.get (iter
, Column
.URI
, out uri
);
649 selection
.select_iter (iter
);
650 now_playing_iter
= iter
;
652 selection
.get_selected (null, out iter
);
653 model
.get (iter
, Column
.URI
, out uri
);
654 now_playing_iter
= iter
;
659 Global
.player
.start_playing (uri
);
661 Global
.player
.pause ();
665 [CCode (instance_pos
= -1)]
666 public void on_next_action_cb (Gtk
.Action source
) {
667 TreeModel model
= null;
668 TreeSelection selection
= null;
672 if (unlikely (Global
.state
!= State
.READY
)) {
676 model
= tree_view
.get_model ();
677 selection
= tree_view
.get_selection ();
679 if (unlikely (model
== null || selection
== null)) {
683 if (selection
.count_selected_rows () != 0) {
684 selection
.get_selected (null, out iter
);
685 if (model
.iter_next (ref iter
) == true) {
686 selection
.select_iter (iter
);
688 if (Global
.now_playing
== true) {
689 model
.get (iter
, Column
.URI
, out uri
);
691 Global
.player
.start_playing (uri
);
692 now_playing_iter
= iter
;
698 [CCode (instance_pos
= -1)]
699 public void on_record_stop_action_cb (Gtk
.Action source
) {
700 if (unlikely (Global
.state
!= State
.READY
)) {
704 if (Global
.now_recording
== false) {
705 status_label
.set_text (_("Initializing a new record..."));
706 Global
.recorder
.start_recording ();
708 Global
.recorder
.stop_recording ();
712 [CCode (instance_pos
= -1)]
713 public void on_about_action_cb (Gtk
.Action source
) {
714 show_about_dialog (this as Window
,
715 "logo-icon-name", Config
.PACKAGE
,
716 "title", _("About ") + Config
.PACKAGE_NAME
,
717 "version", Config
.PACKAGE_VERSION
,
718 "comments", _("A simple sound recorder for the GNOME desktop"),
719 "copyright", Global
.copyrights
,
720 "authors", Global
.authors
);
723 [CCode (instance_pos
= -1)]
724 public bool on_button_press_even_cb (Scale source
) {
725 if (unlikely (Global
.state
!= State
.READY
)) {
729 Global
.seeking
= true;
734 [CCode (instance_pos
= -1)]
735 public void on_value_changed_cb (Scale source
) {
736 double location
= 0.0;
740 if (unlikely (Global
.state
!= State
.READY
)) {
744 if (Global
.seeking
== true) {
745 location
= scale_adjustment
.get_value ();
746 duration
= Global
.player
.query_duration ();
749 position
= (int64) ((double) duration
* location
);
751 Global
.player
.move (location
);
752 position_label
.set_text (Utils
.duration_to_string (position
));
757 [CCode (instance_pos
= -1)]
758 public bool on_button_release_even_cb (Scale source
) {
759 if (unlikely (Global
.state
!= State
.READY
)) {
763 Global
.seeking
= false;
768 [CCode (instance_pos
= -1)]
769 public void on_volume_changed_cb (ScaleButton source
, double volume
) {
770 if (unlikely (Global
.state
!= State
.READY
)) {
774 Global
.player
.set_volume (volume
);
777 [CCode (instance_pos
= -1)]
778 public void on_row_activated_cb (TreeView source
, TreePath path
, TreeViewColumn column
) {
779 TreeModel model
= null;
783 if (unlikely (Global
.state
!= State
.READY
)) {
787 model
= tree_view
.get_model ();
789 if (likely (model
!= null)) {
790 model
.get_iter (out iter
, path
);
791 model
.get (iter
, Column
.URI
, out uri
);
793 Global
.player
.start_playing (uri
);
794 now_playing_iter
= iter
;
798 [CCode (instance_pos
= -1)]
799 public void on_selection_changed_cb () {
800 TreeModel model
= null;
801 TreeSelection selection
= null;
802 TreeIter iter
, previous
, next
;
804 if (unlikely (Global
.state
!= State
.READY
)) {
808 model
= tree_view
.get_model ();
809 selection
= tree_view
.get_selection ();
811 if (unlikely (model
== null || selection
== null)) {
815 if (selection
.count_selected_rows () == 0) {
816 save_as_action
.set_sensitive (false);
817 delete_action
.set_sensitive (false);
818 properties_action
.set_sensitive (false);
820 previous_action
.set_sensitive (false);
821 next_action
.set_sensitive (false);
823 scale_adjustment
.set_value (0.0);
824 position_label
.set_text ("0:00");
825 duration_label
.set_text ("0:00");
827 save_as_action
.set_sensitive (true);
828 delete_action
.set_sensitive (true);
829 properties_action
.set_sensitive (true);
830 properties_dialog
.reset ();
832 selection
.get_selected (null, out iter
);
836 if (model
.iter_previous (ref previous
) == false) {
837 previous_action
.set_sensitive (false);
839 previous_action
.set_sensitive (true);
842 if (model
.iter_next (ref next
) == false) {
843 next_action
.set_sensitive (false);
845 next_action
.set_sensitive (true);