Project

General

Profile

infowin.cc

Jim Turner, September 07, 2022 05:21

 
1
/*
2
 * infowin.c
3
 * Copyright 2006-2013 Ariadne Conill, Tomasz Moń, Eugene Zagidullin,
4
 *                     John Lindgren, and Thomas Lange
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * 1. Redistributions of source code must retain the above copyright notice,
10
 *    this list of conditions, and the following disclaimer.
11
 *
12
 * 2. Redistributions in binary form must reproduce the above copyright notice,
13
 *    this list of conditions, and the following disclaimer in the documentation
14
 *    provided with the distribution.
15
 *
16
 * This software is provided "as is" and without any warranty, express or
17
 * implied. In no event shall the authors be liable for any damages arising from
18
 * the use of this software.
19
 */
20

    
21
#include <gtk/gtk.h>
22
#include <stdarg.h>
23
#include <stdlib.h>
24
#include <string.h>
25

    
26
#include <libaudcore/audstrings.h>
27
#include <libaudcore/hook.h>
28
#include <libaudcore/i18n.h>
29
#include <libaudcore/interface.h>
30
#include <libaudcore/mainloop.h>
31
#include <libaudcore/playlist.h>
32
#include <libaudcore/probe.h>
33
#include <libaudcore/runtime.h>
34
#include <libaudcore/tuple.h>
35

    
36
#include "internal.h"
37
#include "libaudgui.h"
38
#include "libaudgui-gtk.h"
39

    
40
#define AUDGUI_STATUS_TIMEOUT 3000
41

    
42
enum {
43
    CODEC_FORMAT,
44
    CODEC_QUALITY,
45
    CODEC_BITRATE,
46
    CODEC_ITEMS
47
};
48

    
49
static const char * codec_labels[CODEC_ITEMS] = {
50
    N_("Format:"),
51
    N_("Quality:"),
52
    N_("Bitrate:")
53
};
54

    
55
static struct {
56
    GtkTextView * location;
57
    GtkWidget * location_scrolled;
58
    GtkTextBuffer * textbuffer;
59
    GtkWidget * title;
60
    GtkWidget * artist;
61
    GtkWidget * album;
62
    GtkWidget * album_artist;
63
    GtkWidget * comment;
64
    GtkWidget * year;
65
    GtkWidget * track;
66
    GtkWidget * genre;
67
    GtkWidget * image;
68
    GtkWidget * codec[3];
69
    GtkWidget * apply;
70
    GtkWidget * autofill;
71
    GtkWidget * ministatus;
72
} widgets;
73

    
74
static GtkWidget * infowin;
75
static Playlist current_playlist;
76
static int current_entry;
77
static String current_file;
78
static Tuple current_tuple;
79
static PluginHandle * current_decoder = nullptr;
80
static bool can_write = false;
81
static QueuedFunc ministatus_timer;
82

    
83
/* This is by no means intended to be a complete list.  If it is not short, it
84
 * is useless: scrolling through ten pages of dropdown list is more work than
85
 * typing out the genre. */
86

    
87
static const char * genre_table[] = {
88
 N_("Acid Jazz"),
89
 N_("Acid Rock"),
90
 N_("Ambient"),
91
 N_("Bebop"),
92
 N_("Bluegrass"),
93
 N_("Blues"),
94
 N_("Chamber Music"),
95
 N_("Classical"),
96
 N_("Country"),
97
 N_("Death Metal"),
98
 N_("Disco"),
99
 N_("Easy Listening"),
100
 N_("Folk"),
101
 N_("Funk"),
102
 N_("Gangsta Rap"),
103
 N_("Gospel"),
104
 N_("Grunge"),
105
 N_("Hard Rock"),
106
 N_("Heavy Metal"),
107
 N_("Hip-hop"),
108
 N_("House"),
109
 N_("Jazz"),
110
 N_("Jungle"),
111
 N_("Metal"),
112
 N_("New Age"),
113
 N_("New Wave"),
114
 N_("Noise"),
115
 N_("Pop"),
116
 N_("Punk Rock"),
117
 N_("Rap"),
118
 N_("Reggae"),
119
 N_("Rock"),
120
 N_("Rock and Roll"),
121
 N_("Rhythm and Blues"),
122
 N_("Ska"),
123
 N_("Soul"),
124
 N_("Swing"),
125
 N_("Techno"),
126
 N_("Trip-hop")};
127

    
128
static GtkWidget * small_label_new (const char * text)
129
{
130
    static PangoAttrList * attrs = nullptr;
131

    
132
    if (! attrs)
133
    {
134
        attrs = pango_attr_list_new ();
135
        pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
136
    }
137

    
138
    GtkWidget * label = gtk_label_new (text);
139
    gtk_label_set_attributes ((GtkLabel *) label, attrs);
140
    gtk_misc_set_alignment ((GtkMisc *) label, 0, 0.5);
141

    
142
    return label;
143
}
144

    
145
static void set_entry_str_from_field (GtkWidget * widget, const Tuple & tuple,
146
 Tuple::Field field, bool editable, bool clear, bool & changed)
147
{
148
    String text = tuple.get_str (field);
149

    
150
    if (! text && ! clear)
151
    {
152
        if (gtk_entry_get_text_length ((GtkEntry *) widget) > 0)
153
            changed = true;
154
        return;
155
    }
156

    
157
    gtk_entry_set_text ((GtkEntry *) widget, text ? text : "");
158
    gtk_editable_set_editable ((GtkEditable *) widget, editable);
159
}
160

    
161
static void set_entry_int_from_field (GtkWidget * widget, const Tuple & tuple,
162
 Tuple::Field field, bool editable, bool clear, bool & changed)
163
{
164
    int value = tuple.get_int (field);
165

    
166
    if (value <= 0 && ! clear)
167
    {
168
        if (gtk_entry_get_text_length ((GtkEntry *) widget) > 0)
169
            changed = true;
170
        return;
171
    }
172

    
173
    gtk_entry_set_text ((GtkEntry *) widget, (value > 0) ? (const char *) int_to_str (value) : "");
174
    gtk_editable_set_editable ((GtkEditable *) widget, editable);
175
}
176

    
177
static void set_field_str_from_entry (Tuple & tuple, Tuple::Field field, GtkWidget * widget)
178
{
179
    const char * text = gtk_entry_get_text ((GtkEntry *) widget);
180

    
181
    if (text[0])
182
        tuple.set_str (field, text);
183
    else
184
        tuple.unset (field);
185
}
186

    
187
static void set_field_int_from_entry (Tuple & tuple, Tuple::Field field, GtkWidget * widget)
188
{
189
    const char * text = gtk_entry_get_text ((GtkEntry *) widget);
190

    
191
    if (text[0])
192
        tuple.set_int (field, atoi (text));
193
    else
194
        tuple.unset (field);
195
}
196

    
197
static void entry_changed ()
198
{
199
    if (can_write)
200
        gtk_widget_set_sensitive (widgets.apply, true);
201
}
202

    
203
static void ministatus_display_message (const char * text)
204
{
205
    gtk_label_set_text ((GtkLabel *) widgets.ministatus, text);
206
    gtk_widget_hide (widgets.autofill);
207
    gtk_widget_show (widgets.ministatus);
208

    
209
    ministatus_timer.queue (AUDGUI_STATUS_TIMEOUT, [] () {
210
        gtk_widget_hide (widgets.ministatus);
211
        gtk_widget_show (widgets.autofill);
212
    });
213
}
214

    
215
static void infowin_update_tuple ()
216
{
217
    set_field_str_from_entry (current_tuple, Tuple::Title, widgets.title);
218
    set_field_str_from_entry (current_tuple, Tuple::Artist, widgets.artist);
219
    set_field_str_from_entry (current_tuple, Tuple::Album, widgets.album);
220
    set_field_str_from_entry (current_tuple, Tuple::AlbumArtist, widgets.album_artist);
221
    set_field_str_from_entry (current_tuple, Tuple::Comment, widgets.comment);
222
    set_field_str_from_entry (current_tuple, Tuple::Genre,
223
     gtk_bin_get_child ((GtkBin *) widgets.genre));
224
    set_field_int_from_entry (current_tuple, Tuple::Year, widgets.year);
225
    set_field_int_from_entry (current_tuple, Tuple::Track, widgets.track);
226

    
227
    if (aud_file_write_tuple (current_file, current_decoder, current_tuple))
228
    {
229
        ministatus_display_message (_("Save successful"));
230
        gtk_widget_set_sensitive (widgets.apply, false);
231
    }
232
    else
233
        ministatus_display_message (_("Save error"));
234
}
235

    
236
static void infowin_select_entry (int entry)
237
{
238
    if (entry >= 0 && entry < current_playlist.n_entries ())
239
    {
240
        current_playlist.select_all (false);
241
        current_playlist.select_entry (entry, true);
242
        current_playlist.set_focus (entry);
243
        audgui_infowin_show (current_playlist, entry);
244
    }
245
    else
246
        audgui_infowin_hide ();
247
}
248

    
249
static void infowin_prev ()
250
{
251
    infowin_select_entry (current_entry - 1);
252
}
253

    
254
static void infowin_next ()
255
{
256
    infowin_select_entry (current_entry + 1);
257
}
258

    
259
static void genre_fill (GtkWidget * combo)
260
{
261
    Index<const char *> list;
262
    for (auto genre : genre_table)
263
        list.append (_(genre));
264

    
265
    list.sort (g_utf8_collate);
266

    
267
    for (auto genre : list)
268
        gtk_combo_box_text_append_text ((GtkComboBoxText *) combo, genre);
269
}
270

    
271
static void autofill_toggled (GtkToggleButton * toggle)
272
{
273
    aud_set_bool ("audgui", "clear_song_fields", ! gtk_toggle_button_get_active (toggle));
274
}
275

    
276
static void infowin_display_image (const char * filename)
277
{
278
    if (! current_file || strcmp (filename, current_file))
279
        return;
280

    
281
    AudguiPixbuf pb = audgui_pixbuf_request (filename);
282
    if (! pb)
283
        pb = audgui_pixbuf_fallback ();
284

    
285
    if (pb)
286
        audgui_scaled_image_set (widgets.image, pb.get ());
287
}
288

    
289
static void infowin_destroyed ()
290
{
291
    hook_dissociate ("art ready", (HookFunction) infowin_display_image);
292

    
293
    ministatus_timer.stop ();
294

    
295
    memset (& widgets, 0, sizeof widgets);
296

    
297
    infowin = nullptr;
298
    current_file = String ();
299
    current_tuple = Tuple ();
300
    current_decoder = nullptr;
301
}
302

    
303
static void add_entry (GtkWidget * grid, const char * title, GtkWidget * entry,
304
 int x, int y, int span)
305
{
306
    GtkWidget * label = small_label_new (title);
307

    
308
    gtk_table_attach ((GtkTable *) grid, label, x, x + span, y, y + 1,
309
     (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
310
    gtk_table_attach ((GtkTable *) grid, entry, x, x + span, y + 1, y + 2,
311
     (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
312

    
313
    g_signal_connect (entry, "changed", (GCallback) entry_changed, nullptr);
314
}
315

    
316
static void create_infowin ()
317
{
318
    int dpi = audgui_get_dpi ();
319

    
320
    infowin = gtk_window_new (GTK_WINDOW_TOPLEVEL);
321
    gtk_container_set_border_width ((GtkContainer *) infowin, 6);
322
    gtk_window_set_title ((GtkWindow *) infowin, _("Song Info"));
323
    gtk_window_set_type_hint ((GtkWindow *) infowin,
324
     GDK_WINDOW_TYPE_HINT_DIALOG);
325

    
326
    GtkWidget * main_grid = gtk_table_new (0, 0, false);
327
    gtk_table_set_col_spacings ((GtkTable *) main_grid, 6);
328
    gtk_table_set_row_spacings ((GtkTable *) main_grid, 6);
329
    gtk_container_add ((GtkContainer *) infowin, main_grid);
330

    
331
    widgets.image = audgui_scaled_image_new (nullptr);
332
    gtk_widget_set_size_request (widgets.image, 2 * dpi, 2 * dpi);
333
    gtk_table_attach ((GtkTable *) main_grid, widgets.image, 0, 1, 0, 1,
334
     GTK_FILL, GTK_FILL, 0, 0);
335

    
336
    widgets.location_scrolled = gtk_scrolled_window_new (nullptr, nullptr);
337
    gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) widgets.location_scrolled, GTK_SHADOW_IN);
338
    gtk_scrolled_window_set_policy ((GtkScrolledWindow *) widgets.location_scrolled,
339
     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
340

    
341
    widgets.location = (GtkTextView *) gtk_text_view_new ();
342
    gtk_text_view_set_editable (widgets.location, false);
343
    gtk_text_view_set_cursor_visible (widgets.location, true);
344
    gtk_text_view_set_left_margin (widgets.location, 4);
345
    gtk_text_view_set_right_margin (widgets.location, 4);
346
    gtk_text_view_set_wrap_mode (widgets.location, GTK_WRAP_CHAR);
347
    widgets.textbuffer = gtk_text_view_get_buffer (widgets.location);
348

    
349
    gtk_container_add ((GtkContainer *) widgets.location_scrolled, (GtkWidget *) widgets.location);
350
    GtkWidget * scrolled_vbox = gtk_vbox_new (false, 6);
351
    gtk_box_pack_start ((GtkBox *) scrolled_vbox, widgets.location_scrolled, true, true, 0);
352
    gtk_widget_show_all (scrolled_vbox);
353
    gtk_table_attach ((GtkTable *) main_grid, scrolled_vbox, 0, 1, 1, 2,
354
     GTK_FILL, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), 0, 0);
355

    
356
    GtkWidget * codec_grid = gtk_table_new (0, 0, false);
357
    gtk_table_set_row_spacings ((GtkTable *) codec_grid, 2);
358
    gtk_table_set_col_spacings ((GtkTable *) codec_grid, 12);
359
    gtk_table_attach ((GtkTable *) main_grid, codec_grid, 0, 1, 2, 3,
360
     GTK_FILL, GTK_FILL, 0, 8);
361

    
362
    for (int row = 0; row < CODEC_ITEMS; row ++)
363
    {
364
        GtkWidget * label = small_label_new (_(codec_labels[row]));
365
        gtk_table_attach ((GtkTable *) codec_grid, label, 0, 1, row, row + 1,
366
         GTK_FILL, GTK_FILL, 0, 0);
367

    
368
        widgets.codec[row] = small_label_new (nullptr);
369
        gtk_table_attach ((GtkTable *) codec_grid, widgets.codec[row], 1, 2, row, row + 1,
370
         GTK_FILL, GTK_FILL, 0, 0);
371
    }
372

    
373
    GtkWidget * grid = gtk_table_new (0, 0, false);
374
    gtk_table_set_row_spacings ((GtkTable *) grid, 2);
375
    gtk_table_set_col_spacings ((GtkTable *) grid, 6);
376
    gtk_table_attach ((GtkTable *) main_grid, grid, 1, 2, 0, 3,
377
     (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
378

    
379
    widgets.title = gtk_entry_new ();
380
    gtk_widget_set_size_request (widgets.title, 3 * dpi, -1);
381
    add_entry (grid, _("Title"), widgets.title, 0, 0, 2);
382

    
383
    widgets.artist = gtk_entry_new ();
384
    add_entry (grid, _("Artist"), widgets.artist, 0, 2, 2);
385

    
386
    widgets.album = gtk_entry_new ();
387
    add_entry (grid, _("Album"), widgets.album, 0, 4, 2);
388

    
389
    widgets.album_artist = gtk_entry_new ();
390
    add_entry (grid, _("Album Artist"), widgets.album_artist, 0, 6, 2);
391

    
392
    widgets.comment = gtk_entry_new ();
393
    add_entry (grid, _("Comment"), widgets.comment, 0, 8, 2);
394

    
395
    widgets.genre = gtk_combo_box_text_new_with_entry ();
396
    genre_fill (widgets.genre);
397
    add_entry (grid, _("Genre"), widgets.genre, 0, 10, 2);
398

    
399
    widgets.year = gtk_entry_new ();
400
    add_entry (grid, _("Year"), widgets.year, 0, 12, 1);
401

    
402
    widgets.track = gtk_entry_new ();
403
    add_entry (grid, _("Track Number"), widgets.track, 1, 12, 1);
404

    
405
    GtkWidget * bottom_hbox = gtk_hbox_new (false, 6);
406
    gtk_table_attach ((GtkTable *) main_grid, bottom_hbox, 0, 2, 3, 4,
407
     GTK_FILL, GTK_FILL, 0, 0);
408

    
409
    widgets.autofill = gtk_check_button_new_with_mnemonic (_("_Auto-fill empty fields"));
410

    
411
    gtk_toggle_button_set_active ((GtkToggleButton *) widgets.autofill,
412
     ! aud_get_bool ("audgui", "clear_song_fields"));
413
    g_signal_connect (widgets.autofill, "toggled", (GCallback) autofill_toggled, nullptr);
414

    
415
    gtk_widget_set_no_show_all (widgets.autofill, true);
416
    gtk_widget_show (widgets.autofill);
417
    gtk_box_pack_start ((GtkBox *) bottom_hbox, widgets.autofill, true, true, 0);
418

    
419
    widgets.ministatus = small_label_new (nullptr);
420
    gtk_widget_set_no_show_all (widgets.ministatus, true);
421
    gtk_box_pack_start ((GtkBox *) bottom_hbox, widgets.ministatus, true, true, 0);
422

    
423
    widgets.apply = audgui_button_new (_("_Save"), "document-save",
424
     (AudguiCallback) infowin_update_tuple, nullptr);
425

    
426
    GtkWidget * close_button = audgui_button_new (_("_Close"), "window-close",
427
     (AudguiCallback) audgui_infowin_hide, nullptr);
428

    
429
    GtkWidget * prev_button = audgui_button_new (_("_Previous"), "go-previous",
430
     (AudguiCallback) infowin_prev, nullptr);
431

    
432
    GtkWidget * next_button = audgui_button_new (_("_Next"), "go-next",
433
     (AudguiCallback) infowin_next, nullptr);
434

    
435
    gtk_box_pack_end ((GtkBox *) bottom_hbox, close_button, false, false, 0);
436
    gtk_box_pack_end ((GtkBox *) bottom_hbox, widgets.apply, false, false, 0);
437
    gtk_box_pack_end ((GtkBox *) bottom_hbox, next_button, false, false, 0);
438
    gtk_box_pack_end ((GtkBox *) bottom_hbox, prev_button, false, false, 0);
439

    
440
    audgui_destroy_on_escape (infowin);
441
    g_signal_connect (infowin, "destroy", (GCallback) infowin_destroyed, nullptr);
442

    
443
    hook_associate ("art ready", (HookFunction) infowin_display_image, nullptr);
444
}
445

    
446
static void infowin_show (Playlist list, int entry, const String & filename,
447
 const Tuple & tuple, PluginHandle * decoder, bool writable)
448
{
449
    if (! infowin)
450
        create_infowin ();
451

    
452
    current_playlist = list;
453
    current_entry = entry;
454
    current_file = filename;
455
    current_tuple = tuple.ref ();
456
    current_decoder = decoder;
457
    can_write = writable;
458

    
459
    bool clear = aud_get_bool ("audgui", "clear_song_fields");
460
    bool changed = false;
461

    
462
    set_entry_str_from_field (widgets.title, tuple, Tuple::Title, writable, clear, changed);
463
    set_entry_str_from_field (widgets.artist, tuple, Tuple::Artist, writable, clear, changed);
464
    set_entry_str_from_field (widgets.album, tuple, Tuple::Album, writable, clear, changed);
465
    set_entry_str_from_field (widgets.album_artist, tuple, Tuple::AlbumArtist, writable, clear, changed);
466
    set_entry_str_from_field (widgets.comment, tuple, Tuple::Comment, writable, clear, changed);
467
    set_entry_str_from_field (gtk_bin_get_child ((GtkBin *) widgets.genre),
468
     tuple, Tuple::Genre, writable, clear, changed);
469

    
470
    gtk_text_buffer_set_text (widgets.textbuffer, uri_to_display (filename), -1);
471

    
472
    set_entry_int_from_field (widgets.year, tuple, Tuple::Year, writable, clear, changed);
473
    set_entry_int_from_field (widgets.track, tuple, Tuple::Track, writable, clear, changed);
474

    
475
    String codec_values[CODEC_ITEMS];
476

    
477
    codec_values[CODEC_FORMAT] = tuple.get_str (Tuple::Codec);
478
    codec_values[CODEC_QUALITY] = tuple.get_str (Tuple::Quality);
479

    
480
    if (tuple.get_value_type (Tuple::Bitrate) == Tuple::Int)
481
        codec_values[CODEC_BITRATE] = String (str_printf (_("%d kbit/s"),
482
         tuple.get_int (Tuple::Bitrate)));
483

    
484
    for (int row = 0; row < CODEC_ITEMS; row ++)
485
    {
486
        const char * text = codec_values[row] ? (const char *) codec_values[row] : _("N/A");
487
        gtk_label_set_text ((GtkLabel *) widgets.codec[row], text);
488
    }
489

    
490
    infowin_display_image (filename);
491

    
492
    gtk_widget_set_sensitive (widgets.apply, changed);
493
    gtk_widget_grab_focus (widgets.title);
494

    
495
    if (! audgui_reshow_unique_window (AUDGUI_INFO_WINDOW))
496
        audgui_show_unique_window (AUDGUI_INFO_WINDOW, infowin);
497
}
498

    
499
EXPORT void audgui_infowin_show (Playlist playlist, int entry)
500
{
501
    String filename = playlist.entry_filename (entry);
502
    g_return_if_fail (filename != nullptr);
503

    
504
    String error;
505
    PluginHandle * decoder = playlist.entry_decoder (entry, Playlist::Wait, & error);
506
    Tuple tuple = decoder ? playlist.entry_tuple (entry, Playlist::Wait, & error) : Tuple ();
507

    
508
    if (decoder && tuple.valid () && ! aud_custom_infowin (filename, decoder))
509
    {
510
        /* cuesheet entries cannot be updated */
511
        bool can_write = aud_file_can_write_tuple (filename, decoder) &&
512
         ! tuple.is_set (Tuple::StartTime);
513

    
514
        tuple.delete_fallbacks ();
515
        infowin_show (playlist, entry, filename, tuple, decoder, can_write);
516
    }
517
    else
518
        audgui_infowin_hide ();
519

    
520
    if (error)
521
        aud_ui_show_error (str_printf (_("Error opening %s:\n%s"),
522
         (const char *) filename, (const char *) error));
523
}
524

    
525
EXPORT void audgui_infowin_show_current ()
526
{
527
    auto playlist = Playlist::playing_playlist ();
528
    if (playlist == Playlist ())
529
        playlist = Playlist::active_playlist ();
530

    
531
    int position = playlist.get_position ();
532
    if (position < 0)
533
        return;
534

    
535
    audgui_infowin_show (playlist, position);
536
}
537

    
538
EXPORT void audgui_infowin_hide ()
539
{
540
    audgui_hide_unique_window (AUDGUI_INFO_WINDOW);
541
}