Add the TODO file.
[dictix.git] / src / dix-ui.vala
bloba59547cd0fae0582dbce2e7655cc99ed14668e57
1 /**
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
22 using GLib;
23 using Gtk;
24 using Notify;
25 using Dix;
27 namespace Dictix {
28 private enum Column {
29 URI,
30 TIMEVAL,
31 PIXBUF,
32 TITLE,
33 CODEC,
34 SIZE,
35 DURATION,
36 CHANNELS,
37 SAMPLE_RATE,
38 BIT_RATE,
42 public enum NotificationDelay {
43 SHORT = 600,
44 NORMAL = 3000,
45 INVALID = -1
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;
94 Box box = null;
96 last_state = State.UNKNOWN;
97 was_recording = true;
98 was_playing = true;
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);
187 this.show_all ();
190 public void on_duration_cell_render_cb (CellLayout layout, CellRenderer renderer, TreeModel model, TreeIter iter) {
191 int64 duration = 0;
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;
201 string uri = null;
202 string title = null;
203 TreeIter iter;
205 model = tree_view.get_model ();
206 selection = tree_view.get_selection ();
208 if (unlikely (model == null || selection == null)) {
209 return;
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)) {
217 return;
220 if (title != null && text != null) {
221 if (title != text) {
222 writer = new TagWriter (uri);
224 writer.set_title (text);
225 writer.save ();
230 public void update_ui_state (State state) {
231 switch (state) {
232 case State.WORKING:
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);
244 spinner.show ();
245 spinner.start();
246 no_records_label.hide ();
247 tree_view.hide ();
248 break;
249 case State.READY:
250 play_pause_action.set_sensitive (true);
252 status_label.set_text (_("Ready"));
253 record_stop_action.set_sensitive (true);
255 spinner.hide ();
256 spinner.stop();
257 if (Global.records != null) {
258 no_records_label.hide ();
259 tree_view.show ();
260 } else {
261 no_records_label.show ();
262 tree_view.hide ();
264 break;
265 default:
266 break;
270 public void on_notification_action_cb (Notification notification, string action) {
271 if (unlikely (Global.state != State.READY)) {
272 return;
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;
286 int64 position = 0;
287 int64 duration = 0;
288 TreePath path = null;
289 TreeIter iter;
291 if (unlikely (Global.state != last_state)) {
292 update_ui_state (Global.state);
293 last_state = Global.state;
296 if (Global.state == State.WORKING) {
297 return true;
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 ();
312 tree_view.hide ();
314 return true;
317 no_records_label.hide ();
318 tree_view.show ();
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);
331 } else {
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"));
347 try {
348 notification = new Notification (_("Now recording audio..."),
349 null,
350 Config.PACKAGE);
351 notification.set_urgency (Urgency.NORMAL);
352 notification.add_action ("stop",
353 _("Stop"),
354 on_notification_action_cb);
355 notification.add_action ("ingnore",
356 _("Ignore"),
357 on_notification_action_cb);
358 notification.show ();
359 Global.notification_timer = NotificationDelay.SHORT;
360 } catch (Error error) {
361 warning (error.message);
363 } else {
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),
373 true);
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) {
387 try {
388 notification = new Notification (_("Reminder : audio is still recording !"),
389 null,
390 Config.PACKAGE);
391 notification.set_urgency (Urgency.NORMAL);
392 notification.add_action ("stop",
393 _("Stop"),
394 on_notification_action_cb);
395 notification.add_action ("ingnore",
396 _("Ignore"),
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);
430 } else {
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);
439 } else {
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));
455 return true;
458 [CCode (instance_pos = -1)]
459 public void on_save_as_cb (Gtk.Action source) {
460 FileChooserDialog chooser = null;
461 int response;
463 if (unlikely (Global.state != State.READY)) {
464 return;
467 chooser = new FileChooserDialog (_("Save as..."),
468 this as Window,
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) {
481 chooser.destroy ();
484 [CCode (instance_pos = -1)]
485 public void on_delete_action_cb (Gtk.Action source) {
486 TreeModel model = null;
487 TreeSelection selection = null;
488 File file = null;
489 string uri = null;
490 TreeIter iter;
492 if (unlikely (Global.state != State.READY)) {
493 return;
496 model = tree_view.get_model ();
497 selection = tree_view.get_selection ();
499 if (unlikely (model == null || selection == null)) {
500 return;
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;
514 } else {
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);
526 if (file != null) {
527 try {
528 file.delete ();
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;
542 TreeIter iter;
544 if (unlikely (Global.state != State.READY)) {
545 return;
548 model = tree_view.get_model ();
549 selection = tree_view.get_selection ();
551 if (unlikely (model == null || selection == null)) {
552 return;
555 if (selection.count_selected_rows () != 0) {
556 selection.get_selected (null, out iter);
557 properties_dialog.set_record (model, iter);
558 } else {
559 properties_dialog.reset ();
562 properties_dialog.run ();
565 [CCode (instance_pos = -1)]
566 public void on_quit_action_cb (Gtk.Action source) {
567 this.destroy ();
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");
575 argv[1] = "sound";
576 // argv[2] = "--page";
577 // argv[3] = "input";
579 try {
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;
595 string uri = null;
596 TreeIter iter;
598 if (unlikely (Global.state != State.READY)) {
599 return;
602 model = tree_view.get_model ();
603 selection = tree_view.get_selection ();
605 if (unlikely (model == null || selection == null)) {
606 return;
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;
628 string uri = null;
629 TreeIter iter;
631 if (unlikely (Global.state != State.READY)) {
632 return;
635 if (Global.now_playing == false) {
636 if (Global.transport_is_paused == false) {
637 model = tree_view.get_model ();
639 if (model != null) {
640 if (model.get_iter_first (out iter) == false) {
641 /* No records listed, nothing to play... */
642 return;
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;
651 } else {
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);
660 } else {
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;
669 string uri = null;
670 TreeIter iter;
672 if (unlikely (Global.state != State.READY)) {
673 return;
676 model = tree_view.get_model ();
677 selection = tree_view.get_selection ();
679 if (unlikely (model == null || selection == null)) {
680 return;
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)) {
701 return;
704 if (Global.now_recording == false) {
705 status_label.set_text (_("Initializing a new record..."));
706 Global.recorder.start_recording ();
707 } else {
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)) {
726 return false;
729 Global.seeking = true;
731 return false;
734 [CCode (instance_pos = -1)]
735 public void on_value_changed_cb (Scale source) {
736 double location = 0.0;
737 int64 duration = 0;
738 int64 position = 0;
740 if (unlikely (Global.state != State.READY)) {
741 return;
744 if (Global.seeking == true) {
745 location = scale_adjustment.get_value ();
746 duration = Global.player.query_duration ();
748 if (duration >= 0) {
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)) {
760 return false;
763 Global.seeking = false;
765 return false;
768 [CCode (instance_pos = -1)]
769 public void on_volume_changed_cb (ScaleButton source, double volume) {
770 if (unlikely (Global.state != State.READY)) {
771 return;
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;
780 string uri = null;
781 TreeIter iter;
783 if (unlikely (Global.state != State.READY)) {
784 return;
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)) {
805 return;
808 model = tree_view.get_model ();
809 selection = tree_view.get_selection ();
811 if (unlikely (model == null || selection == null)) {
812 return;
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");
826 } else {
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);
833 previous = iter;
834 next = iter;
836 if (model.iter_previous (ref previous) == false) {
837 previous_action.set_sensitive (false);
838 } else {
839 previous_action.set_sensitive (true);
842 if (model.iter_next (ref next) == false) {
843 next_action.set_sensitive (false);
844 } else {
845 next_action.set_sensitive (true);