Commit eba6deff authored by Canek Peláez's avatar Canek Peláez
Browse files

Application refactorization.

Move all functionality to Application, so ApplicationWindow only does
UI work.
parent 3cd8a3fa
Loading
Loading
Loading
Loading
+308 −171
Original line number Diff line number Diff line
@@ -22,111 +22,151 @@

namespace MLM {

    /**
     * MLM GUI application window class.
     */
    [GtkTemplate (ui = "/mx/unam/MLM/mlm.ui")]
    public class ApplicationWindow : Gtk.ApplicationWindow {

        /* Cascade Style Sheet. */
        private const string CSS_URI = "resource:///mx/unam/MLM/mlm.css";
        /* CD icon name. */
        private const string ICON_NAME_CD = "media-optical-cd-audio-symbolic";
        /* Avatar icon name. */
        private const string ICON_NAME_AVATAR = "avatar-default-symbolic";
        /* Play icon name. */
        private const string ICON_NAME_PLAY = "media-playback-start-symbolic";
        /* Pause icon name. */
        private const string ICON_NAME_PAUSE = "media-playback-pause-symbolic";
        /* Icon size. */
        private const Gtk.IconSize ICON_SIZE = Gtk.IconSize.SMALL_TOOLBAR;

        /* The header bar. */
        [GtkChild]
        private Gtk.HeaderBar header_bar;

        /* The previous button. */
        [GtkChild]
        private Gtk.Button previous_button;

        /* The next button. */
        [GtkChild]
        private Gtk.Button next_button;

        /* The save button. */
        [GtkChild]
        private Gtk.Button save_button;

        /* The encode menu button. */
        [GtkChild]
        private Gtk.MenuButton reencode_menu_button;
        private Gtk.MenuButton encode_menu_button;

        /* The encode popover. */
        [GtkChild]
        private Gtk.Popover reencode_popover;
        private Gtk.Popover encode_popover;

        /* The encode progress bar. */
        [GtkChild]
        private Gtk.ProgressBar bar;
        private Gtk.ProgressBar encode_progress_bar;

        /* The tags frame. */
        [GtkChild]
        private Gtk.Frame tags_frame;

        /* The artist entry. */
        [GtkChild]
        private Gtk.Entry artist_entry;

        /* The title entry. */
        [GtkChild]
        private Gtk.Entry title_entry;

        /* The album entry. */
        [GtkChild]
        private Gtk.Entry album_entry;

        /* The band entry. */
        [GtkChild]
        private Gtk.Entry band_entry;

        /* The year spin button. */
        [GtkChild]
        private Gtk.SpinButton year_spin_button;

        /* The year adjustment. */
        [GtkChild]
        private Gtk.Adjustment year_adjustment;

        /* The disc spin button. */
        [GtkChild]
        private Gtk.SpinButton disc_spin_button;

        /* The track spin button. */
        [GtkChild]
        private Gtk.SpinButton track_spin_button;

        /* The total spin button. */
        [GtkChild]
        private Gtk.SpinButton total_spin_button;

        /* The genre combo box. */
        [GtkChild]
        private Gtk.ComboBox genre_combobox;
        private Gtk.ComboBox genre_combo_box;

        /* The genre entry. */
        [GtkChild]
        private Gtk.Entry genre_entry;

        /* The genre model. */
        [GtkChild]
        private Gtk.ListStore genre_model;

        /* The comment entry. */
        [GtkChild]
        private Gtk.Entry comment_entry;

        /* The composer entry. */
        [GtkChild]
        private Gtk.Entry composer_entry;

        /* The original entry. */
        [GtkChild]
        private Gtk.Entry original_entry;

        /* The cover image. */
        [GtkChild]
        private Gtk.Image cover_image;

        /* The artist image. */
        [GtkChild]
        private Gtk.Image artist_image;

        /* The play image. */
        [GtkChild]
        private Gtk.Image play_image;

        /* The play adjustment. */
        [GtkChild]
        private Gtk.Adjustment play_adjustment;

        /* The time label. */
        [GtkChild]
        private Gtk.Label time_label;

        /* The filename label. */
        [GtkChild]
        private Gtk.Label filename_label;

        private bool ignore_popover;

        /* The MLM application. */
        private Application mlm;

        private const string CSS_URI = "resource:///mx/unam/MLM/mlm.css";
        private const string ICON_NAME_CD = "media-optical-cd-audio-symbolic";
        private const string ICON_NAME_AVATAR = "avatar-default-symbolic";
        private const string ICON_NAME_PLAY = "media-playback-start-symbolic";
        private const string ICON_NAME_PAUSE = "media-playback-pause-symbolic";
        private const Gtk.IconSize ICON_SIZE = Gtk.IconSize.SMALL_TOOLBAR;

        /**
         * Initializes the application window.
         * @param application the application.
         */
        public ApplicationWindow(Gtk.Application application) {
            GLib.Object(application: application);
            mlm = application as Application;

            Gtk.Window.set_default_icon_name("mlm");
            var provider = new Gtk.CssProvider();
            try {
@@ -139,156 +179,122 @@ namespace MLM {
                                                      provider, 999);
            var date_time = new GLib.DateTime.now_local();
            year_adjustment.upper = date_time.get_year();

            reencode.bind_property("active", popover, "visible",
                                   GLib.BindingFlags.BIDIRECTIONAL);

            encode_menu_button.bind_property(
                "active", encode_popover,
                "visible", GLib.BindingFlags.BIDIRECTIONAL);
            genre_model.set_sort_column_id(0, Gtk.SortType.ASCENDING);
        }

        /* The on match selected callback. */
        [GtkCallback]
        public bool on_match_selected(Gtk.TreeModel m, Gtk.TreeIter i) {
            genre_combobox.set_active_iter(i);
        private bool on_match_selected(Gtk.TreeModel m, Gtk.TreeIter i) {
            genre_combo_box.set_active_iter(i);
            return true;
        }

        /* The on previous clicked callback. */
        [GtkCallback]
        public void on_previous_clicked() {
        private void on_previous_clicked() {
            mlm.previous();
        }

        /* The on next clicked callback. */
        [GtkCallback]
        public void on_next_clicked() {
        private void on_next_clicked() {
            mlm.next();
        }

        /* The on popover visibility changed callback. */
        [GtkCallback]
        public void on_clear_artist_clicked() {}

        [GtkCallback]
        public void on_clear_cover_clicked() {}

        private uint8[]? select_image(string title) {
            var dialog =
                new Gtk.FileChooserDialog(title, this,
                                          Gtk.FileChooserAction.OPEN,
                                          _("_Cancel"), Gtk.ResponseType.CANCEL,
                                          _("_Open"), Gtk.ResponseType.ACCEPT);
            int r = dialog.run();
            string fn = dialog.get_filename();
            dialog.destroy();
            if (r != Gtk.ResponseType.ACCEPT)
                return null;
            try {
                uint8[] data;
                FileUtils.get_data(fn, out data);
                return data;
            } catch (GLib.FileError fe) {
                warning(_("There was an error loading image '%s'").printf(fn));
            }
            return null;
        }

        [GtkCallback]
        public void on_open_artist_clicked() {
            var data = select_image(_("Select image for artist"));
            mlm.set_artist_data(data);
            update_image(artist_image, data);
        }

        [GtkCallback]
        public void on_open_cover_clicked() {
            var data = select_image(_("Select image for cover"));
            mlm.set_cover_data(data);
            update_image(cover_image, data);
        }

        [GtkCallback]
        public void popover_visibility_changed() {
            if (!popover.visible) {
        private void on_popover_visibility_changed() {
            if (!encode_popover.visible) {
                mlm.stop_encoder();
            } else {
                progress_bar.set_fraction(0.0);
                mlm.start_encoder()
                encode_progress_bar.set_fraction(0.0);
                mlm.start_encoder();
            }
        }

        /* The on save clicked callback. */
        [GtkCallback]
        public void on_save_clicked() {
        private void on_save_clicked() {
            mlm.save();
            save.sensitive = false;
            save_button.sensitive = false;
        }

        private void player_state_changed(Gst.State state) {
            switch (state) {
            case Gst.State.PLAYING:
        /* The on play clicked callback. */
        [GtkCallback]
        private void on_play_clicked() {
            if (!mlm.is_player_playing()) {
                play_image.set_from_icon_name(ICON_NAME_PAUSE, ICON_SIZE);
                GLib.Idle.add(monitor_play);
                break;
            case Gst.State.PAUSED:
                int64 d, p;
                if (player.get_completion(out d, out p) == 0.0)
                    reset_timer();
                mlm.start_player();
            } else {
                play_image.set_from_icon_name(ICON_NAME_PLAY, ICON_SIZE);
                break;
            }
                mlm.pause_player();
            }

        public void upgrade_progress_bar(double percentage) {
            progress_bar.set_fraction(percentage);
        }

        public void hide_progress_bar() {
            ignore_popover = true;
            popover.visible = false;
            ignore_popover = false;
        /* The on open artist image clicked callback. */
        [GtkCallback]
        private void on_open_artist_image_clicked() {
            var data = select_image(_("Select image for artist"));
            mlm.set_artist_picture_data(data);
            update_image(artist_image, data);
        }

        private void reset_timer() {
            play_image.set_from_icon_name(ICON_NAME_PLAY, ICON_SIZE);
            play_adjustment.set_value(0.0);
            time.set_text("00:00");
        /* The on clear artist image clicked callback. */
        [GtkCallback]
        private void on_clear_artist_image_clicked() {
            mlm.set_artist_picture_data(null);
            set_default_image(artist_image);
            save_button.sensitive = true;
        }

        private bool monitor_play() {
            if (!player.working) {
                play_image.set_from_icon_name(ICON_NAME_PLAY, ICON_SIZE);
                return false;
        /* The on open cover image clicked callback. */
        [GtkCallback]
        private void on_open_cover_image_clicked() {
            var data = select_image(_("Select image for cover"));
            mlm.set_cover_picture_data(data);
            update_image(cover_image, data);
        }

            int64 position = -1, duration = -1;
            double p = player.get_completion(out position, out duration);
            play_adjustment.set_value(p);
            int64 tsecs = duration / 1000000000l;
            int mins = (int)(tsecs / 60);
            int secs = (int)(tsecs % 60);
            time.set_text("%02d:%02d".printf(mins, secs));
            return true;
        /* The on clear cover image clicked callback. */
        [GtkCallback]
        private void on_clear_cover_image_clicked() {
            mlm.set_cover_picture_data(null);
            set_default_image(cover_image);
            save_button.sensitive = true;
        }

        /* The on scale change callback. */
        [GtkCallback]
        public void on_play_clicked() {
            if (!player.working) {
                play_image.set_from_icon_name(ICON_NAME_PAUSE, ICON_SIZE);
                mlm.play();
            } else {
                play_image.set_from_icon_name(ICON_NAME_PLAY, ICON_SIZE);
                mlm.pause();
        private bool on_scale_change_value(Gtk.ScrollType scroll, double value) {
            if (value < 0.0)
                value = 0.0;
            if (value > 1.0)
                value = 1.0;
            if (!mlm.seek_player(value)) {
                reset_timer();
                return true;
            }
            return false;
        }

        /* The on window destroy callback. */
        [GtkCallback]
        public void on_window_destroy() {
        private void on_window_destroy() {
            mlm.quit();
        }

        /* The on tags changed callback. */
        [GtkCallback]
        public void tags_changed() {
            save.sensitive = true;
        private void on_tags_changed() {
            save_button.sensitive = true;
        }

        /* The on window key press callback. */
        [GtkCallback]
        public bool on_window_key_press(Gdk.EventKey e) {
        private bool on_window_key_press(Gdk.EventKey e) {
            if (e.keyval == Gdk.Key.Page_Up) {
                on_previous_clicked();
                return true;
@@ -308,19 +314,42 @@ namespace MLM {
            return false;
        }

        [GtkCallback]
        public bool on_scale_change_value(Gtk.ScrollType scroll, double value) {
            if (value < 0.0)
                value = 0.0;
            if (value > 1.0)
                value = 1.0;
            if (!player.seek(value)) {
                reset_timer();
                return true;
        /**
         * Updates the encoding popover.
         * @param percentage the percentage of the encoding.
         */
        public void update_encoding(double percentage) {
            encode_progress_bar.set_fraction(percentage);
        }
            return false;

        /**
         * Hides the encoding popover.
         */
        public void hide_encoding() {
            encode_popover.visible = false;
        }

        /**
         * Sets the view at play started.
         */
        public void play_started() {
            play_image.set_from_icon_name(ICON_NAME_PAUSE, ICON_SIZE);
            GLib.Idle.add(monitor_player);
        }

        /**
         * Sets the view at play paused.
         */
        public void play_paused() {
            if (mlm.player_completion() == 0.0)
                reset_timer();
            play_image.set_from_icon_name(ICON_NAME_PLAY, ICON_SIZE);
        }

        /**
         * Shows a warning dialog.
         * @param message the warning message.
         */
        public void warning(string message) {
            var dialog = new Gtk.MessageDialog(
                this, Gtk.DialogFlags.DESTROY_WITH_PARENT,
@@ -331,6 +360,154 @@ namespace MLM {
            dialog.destroy();
        }

        /* Monitors the player in the model. */
        private bool monitor_player() {
            if (!mlm.is_player_playing()) {
                play_image.set_from_icon_name(ICON_NAME_PLAY, ICON_SIZE);
                return false;
            }
            play_adjustment.set_value(mlm.player_completion());
            time_label.set_text(mlm.player_time());
            return true;
        }

        /* Resets the timer. */
        private void reset_timer() {
            play_image.set_from_icon_name(ICON_NAME_PLAY, ICON_SIZE);
            play_adjustment.set_value(0.0);
            time_label.set_text("00:00");
        }

        /**
         * Enables the UI.
         */
        public void enable_ui(bool enable) {
            encode_menu_button.sensitive =
            previous_button.sensitive =
            next_button.sensitive =
            save_button.sensitive =
            tags_frame.sensitive = enable;
        }

        /**
         * Updates the view.
         * @param filename the filename.
         * @param tags the tags.
         * @param current the current file index.
         * @param total the total number of files.
         */
        public void update_view(string filename, FileTags tags,
                                int current, int total) {
            header_bar.set_subtitle("%d / %d".printf(current, total));
            var basename = GLib.Path.get_basename(filename);
            var markup = GLib.Markup.printf_escaped("<b>%s</b>", basename);
            filename_label.set_markup(markup);
            reset_timer();

            artist_entry.text = tags.artist != null ? tags.artist : "";
            title_entry.text = tags.title != null ? tags.title : "";
            album_entry.text = tags.album != null ? tags.album : "";
            band_entry.text = tags.band != null ? tags.band : "";
            year_spin_button.value = tags.year != -1 ? tags.year : 1900;
            disc_spin_button.value = tags.disc != -1 ? tags.disc : 1;
            track_spin_button.value = tags.track != -1 ? tags.track : 1;
            total_spin_button.value = tags.total != -1 ? tags.total : 1;
            genre_entry.text =
                 (tags.genre >= 0 && tags.genre < Genre.total()) ?
                Genre.all()[tags.genre].to_string() : "";
            comment_entry.text = tags.comment != null ? tags.comment : "";
            composer_entry.text = tags.composer != null ? tags.composer : "";
            original_entry.text = tags.original != null ? tags.original : "";
            update_image(cover_image, tags.cover_picture);
            update_image(artist_image, tags.artist_picture);
            previous_button.sensitive = current != 1;
            next_button.sensitive = current < total;
            save_button.sensitive = false;
        }

        /**
         * Updates the model.
         * @param tags the tags to update.
         */
        public void update_model(FileTags tags) {
            tags.artist = artist_entry.text != "" ? artist_entry.text : null;
            tags.title = title_entry.text != "" ? title_entry.text : null;
            tags.album = album_entry.text != "" ? album_entry.text : null;
            tags.band = band_entry.text != "" ? band_entry.text : null;
            tags.year = (int)year_spin_button.value;
            tags.disc = (int)disc_spin_button.value;
            tags.track = (int)track_spin_button.value;
            tags.total = (int)total_spin_button.value;
            tags.genre = genre_entry.text != "" ?
                Genre.index_of(genre_entry.text) : -1;
            tags.comment = comment_entry.text != "" ?
                comment_entry.text : null;
            tags.composer = composer_entry.text != "" ?
                composer_entry.text : null;
            tags.original = original_entry.text != "" ?
                original_entry.text : null;
        }

        /**
         * Returns the normalized filename for the view.
         * @param filename the filename.
         */
        public string get_normalized_filename(string filename) {
            var norm = "";
            int cont = 0;
            do {
                string d = Path.get_dirname(filename);
                string s = GLib.Path.DIR_SEPARATOR_S;
                string a = artist_entry.text.replace("/", "_");
                string t = title_entry.text.replace("/", "_");
                string e = (cont == 0) ? ".mp3" : "-%d.mp3".printf(cont);
                norm = d + s + a + " - " + t + e;
                cont++;
            } while (GLib.FileUtils.test(norm, GLib.FileTest.EXISTS));
            return norm;
        }

        /**
         * Shows the about dialog.
         */
        public void about() {
            string[] authors = { "Canek Peláez Valdés <canek@ciencias.unam.mx>" };
            Gtk.show_about_dialog(
                this,
                "authors",        authors,
                "comments",       _("A Gtk+ based music library maintainer"),
                "copyright",      "Copyright © 2013-2018 Canek Peláez Valdés",
                "license-type",   Gtk.License.GPL_3_0,
                "logo-icon-name", "mlm",
                "version",        Config.PACKAGE_VERSION,
                "website",        ("https://canek@aztlan.fciencias.unam.mx/" +
                                   "gitlab/canek/mlm.git"),
                "wrap-license",   true);
        }

        /* Selects an image from a file. */
        private uint8[]? select_image(string title) {
            var dialog =
                new Gtk.FileChooserDialog(title, this,
                                          Gtk.FileChooserAction.OPEN,
                                          _("_Cancel"), Gtk.ResponseType.CANCEL,
                                          _("_Open"), Gtk.ResponseType.ACCEPT);
            int r = dialog.run();
            string fn = dialog.get_filename();
            dialog.destroy();
            if (r != Gtk.ResponseType.ACCEPT)
                return null;
            try {
                uint8[] data;
                FileUtils.get_data(fn, out data);
                return data;
            } catch (GLib.FileError fe) {
                warning(_("There was an error loading image '%s'").printf(fn));
            }
            return null;
        }

        /* Sets a default image. */
        private void set_default_image(Gtk.Image image) {
            if (image == cover_image)
                image.set_from_icon_name(ICON_NAME_CD, ICON_SIZE);
@@ -339,10 +516,12 @@ namespace MLM {
            image.pixel_size = 140;
        }

        private void update_image(Gtk.Image image,
                                  uint8[]   data) {
            if (data == null)
        /* Updates an image. */
        private void update_image(Gtk.Image image, uint8[] data) {
            if (data == null) {
                set_default_image(image);
                return;
            }
            var mis = new MemoryInputStream.from_data(data, null);
            Gdk.Pixbuf thumb = null;
            try {
@@ -353,53 +532,11 @@ namespace MLM {
                                            Gdk.InterpType.BILINEAR);
            } catch (GLib.Error e) {
                warning(_("Could not set pixbuf from data."));
                set_default_image(image);
                return;
            }
            save_button.sensitive = true;
            image.set_from_pixbuf(thumb);
        }

        private bool dispose_player() {
            if (player == null || player.state == Gst.State.NULL) {
                player = new Player(filename);
                player.state_changed.connect(player_state_changed);
                return false;
            }
            return true;
        }

        private void define_genre(string g) {
            Gtk.TreeIter iter = {};
            if (!genre_model.get_iter_first(out iter))
                return;
            int c = 0;
            do {
                string ig;
                genre_model.get(iter, 0, out ig);
                if (g == ig)
                    genre_combobox.active = c;
                c++;
            } while (genre_model.iter_next(ref iter));
        }

        private void define_filename(string f) {
            _filename = f;
            var basename = GLib.Path.get_basename(_filename);
            var markup = GLib.Markup.printf_escaped("<b>%s</b>", basename);
            filename_widget.set_markup(markup);
            if (player != null)
                player.finish();
            reset_timer();
            GLib.Idle.add(dispose_player);
        }

        private void define_current(int c) {
            _current = c;
            header.set_subtitle("%d / %d".printf(_current, _last));
        }

        private void define_last(int l) {
            _last = l;
            header.set_subtitle("%d / %d".printf(_current, _last));
        }
    }
}
+195 −117

File changed.

Preview size limit exceeded, changes collapsed.