diff options
Diffstat (limited to 'src/libaudgui/infowin.cc')
-rw-r--r-- | src/libaudgui/infowin.cc | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/src/libaudgui/infowin.cc b/src/libaudgui/infowin.cc new file mode 100644 index 0000000..1eabdb6 --- /dev/null +++ b/src/libaudgui/infowin.cc @@ -0,0 +1,519 @@ +/* + * infowin.c + * Copyright 2006-2013 William Pitcock, Tomasz Moń, Eugene Zagidullin, + * John Lindgren, and Thomas Lange + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <gtk/gtk.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include <libaudcore/audstrings.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/interface.h> +#include <libaudcore/playlist.h> +#include <libaudcore/probe.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> + +#include "internal.h" +#include "libaudgui.h" +#include "libaudgui-gtk.h" + +#define AUDGUI_STATUS_TIMEOUT 3000 + +enum { + CODEC_FORMAT, + CODEC_QUALITY, + CODEC_BITRATE, + CODEC_ITEMS +}; + +static const char * codec_labels[CODEC_ITEMS] = { + N_("Format:"), + N_("Quality:"), + N_("Bitrate:") +}; + +static struct { + GtkWidget * location; + GtkWidget * title; + GtkWidget * artist; + GtkWidget * album; + GtkWidget * album_artist; + GtkWidget * comment; + GtkWidget * year; + GtkWidget * track; + GtkWidget * genre; + GtkWidget * image; + GtkWidget * codec[3]; + GtkWidget * apply; + GtkWidget * clear; + GtkWidget * ministatus; +} widgets; + +static GtkWidget * infowin; +static int current_playlist_id, current_entry; +static String current_file; +static PluginHandle * current_decoder = nullptr; +static bool can_write = false; +static int timeout_source = 0; + +/* This is by no means intended to be a complete list. If it is not short, it + * is useless: scrolling through ten pages of dropdown list is more work than + * typing out the genre. */ + +static const char * genre_table[] = { + N_("Acid Jazz"), + N_("Acid Rock"), + N_("Ambient"), + N_("Bebop"), + N_("Bluegrass"), + N_("Blues"), + N_("Chamber Music"), + N_("Classical"), + N_("Country"), + N_("Death Metal"), + N_("Disco"), + N_("Easy Listening"), + N_("Folk"), + N_("Funk"), + N_("Gangsta Rap"), + N_("Gospel"), + N_("Grunge"), + N_("Hard Rock"), + N_("Heavy Metal"), + N_("Hip-hop"), + N_("House"), + N_("Jazz"), + N_("Jungle"), + N_("Metal"), + N_("New Age"), + N_("New Wave"), + N_("Noise"), + N_("Pop"), + N_("Punk Rock"), + N_("Rap"), + N_("Reggae"), + N_("Rock"), + N_("Rock and Roll"), + N_("Rhythm and Blues"), + N_("Ska"), + N_("Soul"), + N_("Swing"), + N_("Techno"), + N_("Trip-hop")}; + +static GtkWidget * small_label_new (const char * text) +{ + static PangoAttrList * attrs = nullptr; + + if (! attrs) + { + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL)); + } + + GtkWidget * label = gtk_label_new (text); + gtk_label_set_attributes ((GtkLabel *) label, attrs); + gtk_misc_set_alignment ((GtkMisc *) label, 0, 0.5); + + return label; +} + +static void set_entry_str_from_field (GtkWidget * widget, const Tuple & tuple, + Tuple::Field field, bool editable, bool clear) +{ + String text = tuple.get_str (field); + if (! text && ! clear) + return; + + gtk_entry_set_text ((GtkEntry *) widget, text ? text : ""); + gtk_editable_set_editable ((GtkEditable *) widget, editable); +} + +static void set_entry_int_from_field (GtkWidget * widget, const Tuple & tuple, + Tuple::Field field, bool editable, bool clear) +{ + int value = tuple.get_int (field); + if (value <= 0 && ! clear) + return; + + gtk_entry_set_text ((GtkEntry *) widget, (value > 0) ? (const char *) int_to_str (value) : ""); + gtk_editable_set_editable ((GtkEditable *) widget, editable); +} + +static void set_field_str_from_entry (Tuple & tuple, Tuple::Field field, GtkWidget * widget) +{ + const char * text = gtk_entry_get_text ((GtkEntry *) widget); + + if (text[0]) + tuple.set_str (field, text); + else + tuple.unset (field); +} + +static void set_field_int_from_entry (Tuple & tuple, Tuple::Field field, GtkWidget * widget) +{ + const char * text = gtk_entry_get_text ((GtkEntry *) widget); + + if (text[0]) + tuple.set_int (field, atoi (text)); + else + tuple.unset (field); +} + +static void entry_changed (GtkEditable * editable) +{ + if (can_write) + gtk_widget_set_sensitive (widgets.apply, true); +} + +static gboolean ministatus_timeout_proc () +{ + gtk_widget_hide (widgets.ministatus); + gtk_widget_show (widgets.clear); + + timeout_source = 0; + return G_SOURCE_REMOVE; +} + +static void ministatus_display_message (const char * text) +{ + gtk_label_set_text ((GtkLabel *) widgets.ministatus, text); + gtk_widget_hide (widgets.clear); + gtk_widget_show (widgets.ministatus); + + if (timeout_source) + g_source_remove (timeout_source); + + timeout_source = g_timeout_add (AUDGUI_STATUS_TIMEOUT, (GSourceFunc) + ministatus_timeout_proc, nullptr); +} + +static void infowin_update_tuple () +{ + Tuple tuple; + tuple.set_filename (current_file); + + set_field_str_from_entry (tuple, Tuple::Title, widgets.title); + set_field_str_from_entry (tuple, Tuple::Artist, widgets.artist); + set_field_str_from_entry (tuple, Tuple::Album, widgets.album); + set_field_str_from_entry (tuple, Tuple::AlbumArtist, widgets.album_artist); + set_field_str_from_entry (tuple, Tuple::Comment, widgets.comment); + set_field_str_from_entry (tuple, Tuple::Genre, gtk_bin_get_child ((GtkBin *) + widgets.genre)); + set_field_int_from_entry (tuple, Tuple::Year, widgets.year); + set_field_int_from_entry (tuple, Tuple::Track, widgets.track); + + if (aud_file_write_tuple (current_file, current_decoder, tuple)) + { + ministatus_display_message (_("Save successful")); + gtk_widget_set_sensitive (widgets.apply, false); + } + else + ministatus_display_message (_("Save error")); +} + +static void infowin_next () +{ + int list = aud_playlist_by_unique_id (current_playlist_id); + + if (list >= 0 && current_entry + 1 < aud_playlist_entry_count (list)) + audgui_infowin_show (list, current_entry + 1); + else + audgui_infowin_hide (); +} + +static gboolean genre_fill (GtkWidget * combo) +{ + GList * list = nullptr; + GList * node; + + for (const char * genre : genre_table) + list = g_list_prepend (list, _(genre)); + + list = g_list_sort (list, (GCompareFunc) strcmp); + + for (node = list; node != nullptr; node = node->next) + gtk_combo_box_text_append_text ((GtkComboBoxText *) combo, (const char *) node->data); + + g_list_free (list); + return G_SOURCE_REMOVE; +} + +static void clear_toggled (GtkToggleButton * toggle) +{ + aud_set_bool ("audgui", "clear_song_fields", gtk_toggle_button_get_active (toggle)); +} + +static void infowin_display_image (const char * filename) +{ + if (! current_file || strcmp (filename, current_file)) + return; + + GdkPixbuf * pb = audgui_pixbuf_request (filename); + if (! pb) + pb = audgui_pixbuf_fallback (); + + if (pb) + { + audgui_scaled_image_set (widgets.image, pb); + g_object_unref (pb); + } +} + +static void infowin_destroyed () +{ + hook_dissociate ("art ready", (HookFunction) infowin_display_image); + + if (timeout_source) + { + g_source_remove (timeout_source); + timeout_source = 0; + } + + memset (& widgets, 0, sizeof widgets); + + infowin = nullptr; + current_file = String (); + current_decoder = nullptr; +} + +static void add_entry (GtkWidget * grid, const char * title, GtkWidget * entry, + int x, int y, int span) +{ + GtkWidget * label = small_label_new (title); + + gtk_table_attach ((GtkTable *) grid, label, x, x + span, y, y + 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach ((GtkTable *) grid, entry, x, x + span, y + 1, y + 2, + GTK_FILL, GTK_FILL, 0, 0); + + g_signal_connect (entry, "changed", (GCallback) entry_changed, nullptr); +} + +static void create_infowin () +{ + infowin = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width ((GtkContainer *) infowin, 6); + gtk_window_set_title ((GtkWindow *) infowin, _("Song Info")); + gtk_window_set_type_hint ((GtkWindow *) infowin, + GDK_WINDOW_TYPE_HINT_DIALOG); + + GtkWidget * main_grid = gtk_table_new (0, 0, false); + gtk_table_set_col_spacings ((GtkTable *) main_grid, 6); + gtk_table_set_row_spacings ((GtkTable *) main_grid, 6); + gtk_container_add ((GtkContainer *) infowin, main_grid); + + widgets.image = audgui_scaled_image_new (nullptr); + gtk_table_attach_defaults ((GtkTable *) main_grid, widgets.image, 0, 1, 0, 1); + + widgets.location = gtk_label_new (""); + gtk_widget_set_size_request (widgets.location, 200, -1); + gtk_label_set_line_wrap ((GtkLabel *) widgets.location, true); + gtk_label_set_line_wrap_mode ((GtkLabel *) widgets.location, PANGO_WRAP_WORD_CHAR); + gtk_label_set_selectable ((GtkLabel *) widgets.location, true); + gtk_table_attach ((GtkTable *) main_grid, widgets.location, 0, 1, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + + GtkWidget * codec_grid = gtk_table_new (0, 0, false); + gtk_table_set_row_spacings ((GtkTable *) codec_grid, 2); + gtk_table_set_col_spacings ((GtkTable *) codec_grid, 12); + gtk_table_attach ((GtkTable *) main_grid, codec_grid, 0, 1, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + + for (int row = 0; row < CODEC_ITEMS; row ++) + { + GtkWidget * label = small_label_new (_(codec_labels[row])); + gtk_table_attach ((GtkTable *) codec_grid, label, 0, 1, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + + widgets.codec[row] = small_label_new (nullptr); + gtk_table_attach ((GtkTable *) codec_grid, widgets.codec[row], 1, 2, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + } + + GtkWidget * grid = gtk_table_new (0, 0, false); + gtk_table_set_row_spacings ((GtkTable *) grid, 2); + gtk_table_set_col_spacings ((GtkTable *) grid, 6); + gtk_table_attach ((GtkTable *) main_grid, grid, 1, 2, 0, 3, + GTK_FILL, GTK_FILL, 0, 0); + + widgets.title = gtk_entry_new (); + add_entry (grid, _("Title"), widgets.title, 0, 0, 2); + + widgets.artist = gtk_entry_new (); + add_entry (grid, _("Artist"), widgets.artist, 0, 2, 2); + + widgets.album = gtk_entry_new (); + add_entry (grid, _("Album"), widgets.album, 0, 4, 2); + + widgets.album_artist = gtk_entry_new (); + add_entry (grid, _("Album Artist"), widgets.album_artist, 0, 6, 2); + + widgets.comment = gtk_entry_new (); + add_entry (grid, _("Comment"), widgets.comment, 0, 8, 2); + + widgets.genre = gtk_combo_box_text_new_with_entry (); + add_entry (grid, _("Genre"), widgets.genre, 0, 10, 2); + g_idle_add ((GSourceFunc) genre_fill, widgets.genre); + + widgets.year = gtk_entry_new (); + add_entry (grid, _("Year"), widgets.year, 0, 12, 1); + + widgets.track = gtk_entry_new (); + add_entry (grid, _("Track Number"), widgets.track, 1, 12, 1); + + GtkWidget * bottom_hbox = gtk_hbox_new (false, 6); + gtk_table_attach ((GtkTable *) main_grid, bottom_hbox, 0, 2, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + + widgets.clear = gtk_check_button_new_with_mnemonic + (_("Clea_r fields when moving to next song")); + + gtk_toggle_button_set_active ((GtkToggleButton *) widgets.clear, + aud_get_bool ("audgui", "clear_song_fields")); + g_signal_connect (widgets.clear, "toggled", (GCallback) clear_toggled, nullptr); + + gtk_widget_set_no_show_all (widgets.clear, true); + gtk_widget_show (widgets.clear); + gtk_box_pack_start ((GtkBox *) bottom_hbox, widgets.clear, false, false, 0); + + widgets.ministatus = small_label_new (nullptr); + gtk_widget_set_no_show_all (widgets.ministatus, true); + gtk_box_pack_start ((GtkBox *) bottom_hbox, widgets.ministatus, true, true, 0); + + widgets.apply = audgui_button_new (_("_Save"), "document-save", + (AudguiCallback) infowin_update_tuple, nullptr); + + GtkWidget * close_button = audgui_button_new (_("_Close"), "window-close", + (AudguiCallback) audgui_infowin_hide, nullptr); + + GtkWidget * next_button = audgui_button_new (_("_Next"), "go-next", + (AudguiCallback) infowin_next, nullptr); + + gtk_box_pack_end ((GtkBox *) bottom_hbox, close_button, false, false, 0); + gtk_box_pack_end ((GtkBox *) bottom_hbox, next_button, false, false, 0); + gtk_box_pack_end ((GtkBox *) bottom_hbox, widgets.apply, false, false, 0); + + audgui_destroy_on_escape (infowin); + g_signal_connect (infowin, "destroy", (GCallback) infowin_destroyed, nullptr); + + hook_associate ("art ready", (HookFunction) infowin_display_image, nullptr); +} + +static void infowin_show (int list, int entry, const char * filename, + const Tuple & tuple, PluginHandle * decoder, bool writable) +{ + if (! infowin) + create_infowin (); + + current_playlist_id = aud_playlist_get_unique_id (list); + current_entry = entry; + current_file = String (filename); + current_decoder = decoder; + can_write = writable; + + bool clear = aud_get_bool ("audgui", "clear_song_fields"); + + set_entry_str_from_field (widgets.title, tuple, Tuple::Title, writable, clear); + set_entry_str_from_field (widgets.artist, tuple, Tuple::Artist, writable, clear); + set_entry_str_from_field (widgets.album, tuple, Tuple::Album, writable, clear); + set_entry_str_from_field (widgets.album_artist, tuple, Tuple::AlbumArtist, writable, clear); + set_entry_str_from_field (widgets.comment, tuple, Tuple::Comment, writable, clear); + set_entry_str_from_field (gtk_bin_get_child ((GtkBin *) widgets.genre), + tuple, Tuple::Genre, writable, clear); + + gtk_label_set_text ((GtkLabel *) widgets.location, uri_to_display (filename)); + + set_entry_int_from_field (widgets.year, tuple, Tuple::Year, writable, clear); + set_entry_int_from_field (widgets.track, tuple, Tuple::Track, writable, clear); + + String codec_values[CODEC_ITEMS]; + + codec_values[CODEC_FORMAT] = tuple.get_str (Tuple::Codec); + codec_values[CODEC_QUALITY] = tuple.get_str (Tuple::Quality); + + if (tuple.get_value_type (Tuple::Bitrate) == Tuple::Int) + codec_values[CODEC_BITRATE] = String (str_printf (_("%d kb/s"), + tuple.get_int (Tuple::Bitrate))); + + for (int row = 0; row < CODEC_ITEMS; row ++) + { + const char * text = codec_values[row] ? (const char *) codec_values[row] : _("N/A"); + gtk_label_set_text ((GtkLabel *) widgets.codec[row], text); + } + + infowin_display_image (filename); + + /* nothing has been changed yet */ + gtk_widget_set_sensitive (widgets.apply, false); + + gtk_widget_grab_focus (widgets.title); + + if (! audgui_reshow_unique_window (AUDGUI_INFO_WINDOW)) + audgui_show_unique_window (AUDGUI_INFO_WINDOW, infowin); +} + +EXPORT void audgui_infowin_show (int playlist, int entry) +{ + String filename = aud_playlist_entry_get_filename (playlist, entry); + g_return_if_fail (filename != nullptr); + + String error; + PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry, + Playlist::Wait, & error); + + if (decoder && ! aud_custom_infowin (filename, decoder)) + { + Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::Wait, & error); + if (tuple) + { + tuple.delete_fallbacks (); + infowin_show (playlist, entry, filename, tuple, decoder, + aud_file_can_write_tuple (filename, decoder)); + } + } + + if (error) + aud_ui_show_error (str_printf (_("Error opening %s:\n%s"), + (const char *) filename, (const char *) error)); +} + +EXPORT void audgui_infowin_show_current () +{ + int playlist = aud_playlist_get_playing (); + int position; + + if (playlist == -1) + playlist = aud_playlist_get_active (); + + position = aud_playlist_get_position (playlist); + + if (position == -1) + return; + + audgui_infowin_show (playlist, position); +} + +EXPORT void audgui_infowin_hide () +{ + audgui_hide_unique_window (AUDGUI_INFO_WINDOW); +} |