1 /*
2  * Copyright (c) 2010, 2011 Ryan Flannery <ryan.flannery@gmail.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "paint.h"
18 
19 /* globalx */
20 _colors colors;
21 bool showing_file_info = false;
22 
23 char *player_get_field2show(const meta_info *mi);
24 char *num2fmt(int n, Direction d);
25 
26 
27 /*
28  * This is used to get which field in the playing file to display on a given
29  * paint of the player window.
30  * The field displayed rotates between artist, album, and title, changing
31  * every 3 seconds.
32  */
33 char *
player_get_field2show(const meta_info * mi)34 player_get_field2show(const meta_info *mi)
35 {
36    static time_t last_updated = 0;
37    static int    index        = 0;
38    static int    offset;
39    static int    fields[] = { MI_CINFO_ARTIST, MI_CINFO_ALBUM,
40                               MI_CINFO_TITLE };
41 
42    /* determine which cinfo item to show */
43    if (time(NULL) - last_updated >= 3) {
44       last_updated = time(NULL);
45       index = (index + 1) % 3;
46    }
47 
48    if (mi->cinfo[ fields[index] ] != NULL)
49       return mi->cinfo[ fields[index] ];
50    else {
51       /* draw the filename if field info not there, but trim it down */
52       offset = 0;
53       if (strlen(mi->filename) > 49)
54          offset += strlen(mi->filename) - 49;
55 
56       return mi->filename + offset;
57    }
58 }
59 
60 /*
61  * Given a number N and an alignment, this builds and returns a printf(3)-
62  * style string ("%Ns" or "%-Ns")
63  */
64 char *
num2fmt(int n,Direction d)65 num2fmt(int n, Direction d)
66 {
67    static char format[255];
68 
69    if (n <= 0) {
70       endwin();
71       errx(1, "num2sfmt: invalid number %d provided", n);
72    }
73 
74    if (d == LEFT)
75       snprintf(format, sizeof(format), "%%-%d.%ds", n, n);
76    else
77       snprintf(format, sizeof(format), "%%%d.%ds", n, n);
78 
79    return format;
80 }
81 
82 /* paint the status bar */
83 void
paint_status_bar()84 paint_status_bar()
85 {
86    static char scratchpad[500];
87    char       *focusName;
88    int         percent;
89    int         w, h;
90 
91    getmaxyx(stdscr, h, w);
92 
93    /*
94     * XXX NOTE: to right-align the text we snprintf to the scratchpad above and
95     * then use the paint that to the window.
96     */
97 
98    /* determine focus'd window name */
99    if (ui.active == ui.library)
100       focusName = "library";
101    else
102       focusName = "playlist";
103 
104    /* determine how far we've scrolled through the window */
105    if (ui.active->nrows == 0)
106       percent = 100;
107    else
108       percent = 100 * (ui.active->voffset + ui.active->crow + 1) / ui.active->nrows;
109 
110    /* build the string to print */
111    snprintf(scratchpad, sizeof(scratchpad),
112       "[%s%s%s] %6d,%-3d %3d%%",
113       focusName,
114       (ui.active == ui.library ? "" : ":"),
115       (ui.active == ui.library ? "" : viewing_playlist->name),
116       ui.active->voffset + ui.active->crow + 1,
117       ui.active->hoffset,
118       percent);
119 
120    /* do the printing */
121    werase(ui.command);
122    wattron(ui.command, COLOR_PAIR(colors.status));
123    mvwprintw(ui.player, 0, 0, num2fmt(w, LEFT), " "); /* this fills the bg color */
124    mvwprintw(ui.command, 0, 0, num2fmt(w, RIGHT), scratchpad);
125    wattroff(ui.command, COLOR_PAIR(colors.status));
126    wrefresh(ui.command);
127 }
128 
129 /* paint the player */
130 void
paint_player()131 paint_player()
132 {
133    static char *playmode;
134    static char *finfo;
135    static int   in_hour;
136    static int   in_minute;
137    static int   in_second;
138    static int   percent, whole;
139    int w, h;
140 
141    getmaxyx(stdscr, h, w);
142 
143    /* if nothing's playing, a shameless plug */
144    if (!player.playing()) {
145       werase(ui.player);
146       wattron(ui.player, COLOR_PAIR(colors.player));
147       mvwprintw(ui.player, 0, 0, num2fmt(w, LEFT), "vitunes...");
148       wattroff(ui.player, COLOR_PAIR(colors.player));
149       wrefresh(ui.player);
150       return;
151    }
152 
153    /* determine time into current selection */
154    in_hour   = (int)  roundf(player.position() / 3600);
155    in_minute = ((int) roundf(player.position())) % 3600 / 60;
156    in_second = ((int) roundf(player.position())) % 60;
157 
158    /* determine percent time into current selection */
159    percent = -1;
160    if (playing_playlist->files[player_info.qidx]->length > 0) {
161       whole = playing_playlist->files[player_info.qidx]->length;
162       percent = roundf(100.0 * player.position() / whole);
163    }
164 
165    /* get character for playmode */
166    switch (player_info.mode) {
167       case MODE_LINEAR:
168          playmode = "-";
169          break;
170       case MODE_LOOP:
171          playmode = "O";
172          break;
173       case MODE_RANDOM:
174          playmode = "?";
175          break;
176    }
177 
178    /* determine info about song to show */
179    finfo = player_get_field2show(playing_playlist->files[player_info.qidx]);
180 
181    /* draw */
182    werase(ui.player);
183    wattron(ui.player, COLOR_PAIR(colors.player));
184    mvwprintw(ui.player, 0, 0, num2fmt(w, LEFT), " "); /* this fills the bg color */
185    mvwprintw(ui.player, 0, 0,
186       "[%s] %8.8s +%2.2d:%2.2d:%2.2d (%d%%) %-49.49s",
187       playmode,
188       (player.paused() ? "-PAUSED-" : ""),
189       in_hour, in_minute, in_second,
190       percent,
191       finfo);
192    wattroff(ui.player, COLOR_PAIR(colors.player));
193    wrefresh(ui.player);
194 }
195 
196 /* paint the library window */
197 void
paint_library()198 paint_library()
199 {
200    char *str;
201    int   row, hoff, index, x;
202 
203    /* if library window is hidden, nothing to do */
204    if (ui.library->cwin == NULL) return;
205 
206    werase(ui.library->cwin);
207    wattron(ui.library->cwin, COLOR_PAIR(colors.library));
208 
209    for (row = 0; row < ui.library->h; row++) {
210 
211       index = ui.library->voffset + row;
212       x = 0;
213 
214       /* apply attributes */
215       if (index < mdb.nplaylists && mdb.playlists[index] == playing_playlist)
216          wattron(ui.library->cwin, COLOR_PAIR(colors.playing_library));
217 
218       if (index < mdb.nplaylists && mdb.playlists[index]->needs_saving) {
219          wattron(ui.library->cwin, A_BOLD);
220          mvwprintw(ui.library->cwin, row, 0, "+");
221          x = 1;
222       }
223 
224       if (row == ui.library->crow) {
225          if (ui.active == ui.library)
226             wattron(ui.library->cwin, A_REVERSE);
227          else
228             wattron(ui.library->cwin, COLOR_PAIR(colors.current_inactive));
229       }
230 
231       if (index >= mdb.nplaylists)
232          wattron(ui.library->cwin, COLOR_PAIR(colors.tildas_library));
233 
234       /* draw the row */
235       if (index >= mdb.nplaylists)
236          mvwprintw(ui.library->cwin, row, 0, "~");
237       else {
238          /* determine horizontal offset */
239          str = mdb.playlists[index]->name;
240          hoff = ui.library->hoffset;
241          if (hoff >= (int)strlen(str))
242             hoff = strlen(str);
243 
244          /* draw it */
245          mvwprintw(ui.library->cwin, row, x,
246             num2fmt(ui.library->w, LEFT),
247             str + hoff);
248       }
249 
250       /* un-apply attributes */
251       if (index >= mdb.nplaylists) {
252          wattroff(ui.library->cwin, COLOR_PAIR(colors.tildas_library));
253          wattron(ui.library->cwin, COLOR_PAIR(colors.library));
254       }
255 
256       if (row == ui.library->crow) {
257          if (ui.active == ui.library)
258             wattroff(ui.library->cwin, A_REVERSE);
259          else
260             wattroff(ui.library->cwin, COLOR_PAIR(colors.current_inactive));
261 
262          wattron(ui.library->cwin, COLOR_PAIR(colors.library));
263       }
264 
265       if (index < mdb.nplaylists && mdb.playlists[index]->needs_saving) {
266          wattroff(ui.library->cwin, A_BOLD);
267          wattron(ui.library->cwin, COLOR_PAIR(colors.library));
268       }
269 
270       if (index < mdb.nplaylists && mdb.playlists[index] == playing_playlist) {
271          wattroff(ui.library->cwin, COLOR_PAIR(colors.playing_library));
272          wattron(ui.library->cwin, COLOR_PAIR(colors.library));
273       }
274    }
275 
276    wattroff(ui.library->cwin, COLOR_PAIR(colors.library));
277    wrefresh(ui.library->cwin);
278 }
279 
280 /* paint the playlist window */
281 void
paint_playlist()282 paint_playlist()
283 {
284    playlist   *plist;
285    bool        hasinfo;
286    bool        visual;
287    char       *str;
288    int         findex, row, col, colwidth;
289    int         xoff, hoff, strhoff;
290    int         cattr;
291 
292 
293    showing_file_info = false;
294    plist = viewing_playlist;
295 
296    werase(ui.playlist->cwin);
297 
298    for (row = 0; row < ui.playlist->h; row++) {
299 
300       /* get index of file to show */
301       findex = row + ui.playlist->voffset;
302 
303       /* determine if visual mode row */
304       visual = false;
305       if (visual_mode_start != -1 && ui.active == ui.playlist) {
306          if (visual_mode_start <= findex && findex <= ui.active->voffset + ui.active->crow)
307             visual = true;
308          if (ui.active->voffset + ui.active->crow <= findex && findex <= visual_mode_start)
309             visual = true;
310       }
311 
312       /* apply row attributes */
313        wattron(ui.playlist->cwin, COLOR_PAIR(colors.playlist));
314 
315       if (plist == playing_playlist && findex == player_info.qidx)
316          wattron(ui.playlist->cwin, COLOR_PAIR(colors.playing_playlist));
317 
318       if ((row == ui.playlist->crow && ui.active == ui.playlist) || visual)
319          wattron(ui.playlist->cwin, A_REVERSE);
320 
321       if (row == ui.playlist->crow && ui.active != ui.playlist)
322          wattron(ui.playlist->cwin, COLOR_PAIR(colors.current_inactive));
323 
324       if (findex >= plist->nfiles)
325          wattron(ui.playlist->cwin, COLOR_PAIR(colors.tildas_playlist));
326 
327       /* draw the row */
328       if (findex >= plist->nfiles)
329          mvwprintw(ui.playlist->cwin, row, 0, "~");
330       else {
331          /* this acheives the A_REVERSE attribute spanning the entire row */
332          mvwprintw(ui.playlist->cwin, row, 0,
333             num2fmt(ui.playlist->w, LEFT), " ");
334 
335          /* does the file have any meta-info? */
336          hasinfo = false;
337          for (col = 0; col < mi_display.nfields; col++) {
338             if (plist->files[findex]->cinfo[mi_display.order[col]] != NULL)
339                hasinfo = true;
340          }
341 
342          /* if there's no meta info, just show filename */
343          if (!hasinfo) {
344             mvwprintw(ui.playlist->cwin, row, 0, num2fmt(ui.playlist->w, LEFT),
345                plist->files[findex]->filename);
346          } else {
347 
348             /* loop through all fields of file and display each ... */
349             xoff = 0;
350             hoff = ui.playlist->hoffset;
351             for (col = 0; col < mi_display.nfields; col++) {
352 
353                /* is horizontal offset big enough to skip this field? */
354                if (hoff >= mi_display.widths[col]) {
355                   hoff -= mi_display.widths[col];
356                   continue;
357                }
358 
359                /* field shown off the screen? */
360                if (xoff >= ui.playlist->w)
361                   continue;
362 
363                /* get string to show (str) */
364                str = plist->files[findex]->cinfo[mi_display.order[col]];
365 
366                /* determine horizontal offset (strhoff) to apply to str */
367                strhoff = 0;
368                if (str != NULL) {
369                   if (mi_display.align[col] == LEFT) {
370                      if (hoff > (int)strlen(str))
371                         strhoff = strlen(str);
372                      else
373                         strhoff = hoff;
374                   } else {
375                      if ((int)strlen(str) > mi_display.widths[col])
376                         strhoff = hoff;
377                      else if (hoff < mi_display.widths[col] - (int)strlen(str))
378                         strhoff = 0;
379                      else
380                         strhoff = hoff - (mi_display.widths[col] - strlen(str));
381 
382                      if (strhoff > (int)strlen(str))
383                         strhoff = strlen(str);
384                   }
385                }
386 
387                /* apply column attribute (only if file is NOT playing) */
388                cattr = COLOR_PAIR(colors.cinfos[mi_display.order[col]]);
389                if ((plist != playing_playlist || findex != player_info.qidx)
390                && colors.cinfos_set[mi_display.order[col]])
391                   wattron(ui.playlist->cwin, cattr);
392 
393                /* determine width of this field */
394                colwidth = mi_display.widths[col] - hoff;
395                if (xoff + colwidth > ui.playlist->w)
396                   colwidth = ui.playlist->w - xoff;
397 
398                /* print the column */
399                mvwprintw(ui.playlist->cwin, row, xoff,
400                   num2fmt(colwidth, mi_display.align[col]),
401                   (str == NULL ? " " : str + strhoff));
402 
403                /* un-apply column attribute */
404                if ((plist != playing_playlist || findex != player_info.qidx)
405                && colors.cinfos_set[mi_display.order[col]]) {
406                   wattroff(ui.playlist->cwin, cattr);
407                   wattron(ui.playlist->cwin, COLOR_PAIR(colors.playlist));
408                }
409 
410                xoff += 1 + colwidth; /* +1 for space between columns */
411                hoff = 0;
412             }
413          }
414       }
415 
416       /* un-apply row attributes */
417       if (findex >= plist->nfiles)
418          wattroff(ui.playlist->cwin, COLOR_PAIR(colors.tildas_playlist));
419 
420       if ((row == ui.playlist->crow && ui.active == ui.playlist) || visual)
421          wattroff(ui.playlist->cwin, A_REVERSE);
422 
423       if (row == ui.playlist->crow && ui.active != ui.playlist)
424          wattroff(ui.playlist->cwin, COLOR_PAIR(colors.current_inactive));
425 
426       if (plist == playing_playlist && findex == player_info.qidx)
427          wattroff(ui.playlist->cwin, COLOR_PAIR(colors.playing_playlist));
428 
429       wattroff(ui.playlist->cwin, COLOR_PAIR(colors.playlist));
430    }
431 
432    wrefresh(ui.playlist->cwin);
433 }
434 
435 /* paint borders between windows */
436 void
paint_borders()437 paint_borders()
438 {
439    int w, h;
440    getmaxyx(stdscr, h, w);
441 
442    wattron(stdscr, COLOR_PAIR(colors.bars));
443    mvhline(1, 0, ACS_HLINE, w);
444    if (ui.library->cwin != NULL) {
445       mvvline(1, ui.lwidth, ACS_VLINE, h - 2);
446       mvaddch(1, ui.lwidth, ACS_TTEE);
447    }
448    wattroff(stdscr, COLOR_PAIR(colors.bars));
449    refresh();
450 }
451 
452 /* paint individual file info in playlist window */
453 void
paint_playlist_file_info(const meta_info * m)454 paint_playlist_file_info(const meta_info *m)
455 {
456    struct tm *ltime;
457    char stime[255];
458    int row, nrows, i;
459    int h, w;
460 
461    getmaxyx(ui.playlist->cwin, h, w);
462    werase(ui.playlist->cwin);
463    wattron(ui.playlist->cwin, COLOR_PAIR(colors.playlist));
464 
465    /* figure out number of rows filename will take */
466    nrows = strlen(m->filename) / w;
467    if (strlen(m->filename) % w != 0) nrows++;
468 
469    /* start painting file info */
470    row = 0;
471    mvwprintw(ui.playlist->cwin, row++, 0, "What vitunes knows about the file/URL:");
472    nl();
473    mvwprintw(ui.playlist->cwin, row, 0,  m->filename);
474    nonl();
475 
476    row += nrows + 1;
477 
478    /* paint meta-info */
479    mvwprintw(ui.playlist->cwin, row++, 0, "Meta-Information:");
480    for (i = 0; i < MI_NUM_CINFO; i++) {
481       mvwprintw(ui.playlist->cwin, row++, 0, "%10s: \"%s\"",
482          MI_CINFO_NAMES[i], m->cinfo[i]);
483    }
484 
485    row += 1;
486 
487    /* paint other details */
488    mvwprintw(ui.playlist->cwin, row++, 0, "Additional Details:");
489    mvwprintw(ui.playlist->cwin, row++, 0, "%15s: %i seconds",
490       "Length", m->length);
491    mvwprintw(ui.playlist->cwin, row++, 0, "%15s: %s",
492       "URL?", (m->is_url ? "Yes" : "No"));
493 
494    ltime = localtime(&(m->last_updated));
495    strftime(stime, sizeof(stime), "%d %B %Y at %H:%M:%S", ltime);
496    mvwprintw(ui.playlist->cwin, row, 0, "%15s: %s", "Last Updated", stime);
497 
498    wattroff(ui.playlist->cwin, COLOR_PAIR(colors.playlist));
499    wrefresh(ui.playlist->cwin);
500    showing_file_info = true;
501 }
502 
503 /* paint all windows */
504 void
paint_all()505 paint_all()
506 {
507    paint_borders();
508    paint_player();
509    paint_status_bar();
510    paint_library();
511    paint_playlist();
512 }
513 
514 /*
515  * Paints an error message to the command/status window.  The usage is
516  * identical to that of printf(3) (vwprintw(3) actually).
517  */
518 void
paint_error(char * fmt,...)519 paint_error(char *fmt, ...)
520 {
521    va_list ap;
522 
523    werase(ui.command);
524    wmove(ui.command, 0, 0);
525    wattron(ui.command, COLOR_PAIR(colors.errors));
526 
527    va_start(ap, fmt);
528    vwprintw(ui.command, fmt, ap);
529    va_end(ap);
530 
531    beep();
532    wattroff(ui.command, COLOR_PAIR(colors.errors));
533    wrefresh(ui.command);
534 }
535 
536 /*
537  * Paints an informational message to the command/status window.  The usage
538  * is identical to that of printf(3) (vwprintw(3) actually).
539  */
540 void
paint_message(char * fmt,...)541 paint_message(char *fmt, ...)
542 {
543    va_list ap;
544 
545    werase(ui.command);
546    wmove(ui.command, 0, 0);
547    wattron(ui.command, COLOR_PAIR(colors.messages));
548 
549    va_start(ap, fmt);
550    vwprintw(ui.command, fmt, ap);
551    va_end(ap);
552 
553    wattroff(ui.command, COLOR_PAIR(colors.messages));
554    wrefresh(ui.command);
555 }
556 
557 /*
558  * Each of these members of the global color object will be
559  * run through init_pair(3)
560  */
561 void
paint_setup_colors()562 paint_setup_colors()
563 {
564    int i;
565 
566    /* setup the indices to be used for init_pair/COLOR_PAIR */
567    colors.bars     = 1;
568    colors.player   = 2;
569    colors.status   = 3;
570    colors.library  = 4;
571    colors.playlist = 5;
572    colors.errors   = 6;
573    colors.messages = 7;
574    colors.tildas_library   = 8;
575    colors.tildas_playlist  = 9;
576    colors.playing_library  = 10;
577    colors.playing_playlist = 11;
578    colors.current_inactive = 12;
579 
580    /* setup default colors */
581    use_default_colors();
582    init_pair(colors.bars,     -1, -1);
583    init_pair(colors.player,   COLOR_GREEN, -1);
584    init_pair(colors.status,   -1, -1);
585    init_pair(colors.library,  -1, -1);
586    init_pair(colors.playlist, -1, -1);
587    init_pair(colors.errors,   -1, COLOR_RED);
588    init_pair(colors.messages, COLOR_RED,   -1);
589    init_pair(colors.tildas_library,   COLOR_BLUE, -1);
590    init_pair(colors.tildas_playlist,  COLOR_BLUE, -1);
591    init_pair(colors.playing_library,  COLOR_GREEN, -1);
592    init_pair(colors.playing_playlist, COLOR_GREEN, -1);
593    init_pair(colors.current_inactive, -1, -1);
594 
595    /* colors for cinfo fields (columns in playlist window) */
596    for (i = 0; i < MI_NUM_CINFO; i++) {
597       colors.cinfos[i] = 13 + i;
598       colors.cinfos_set[i] = false;
599    }
600 }
601 
602 int
paint_str2item(const char * str)603 paint_str2item(const char *str)
604 {
605    int i;
606 
607    if (strcasecmp(str, "bars") == 0)
608       return colors.bars;
609    else if (strcasecmp(str, "player") == 0)
610       return colors.player;
611    else if (strcasecmp(str, "status") == 0)
612       return colors.status;
613    else if (strcasecmp(str, "library") == 0)
614       return colors.library;
615    else if (strcasecmp(str, "playlist") == 0)
616       return colors.playlist;
617    else if (strcasecmp(str, "errors") == 0)
618       return colors.errors;
619    else if (strcasecmp(str, "messages") == 0)
620       return colors.messages;
621    else if (strcasecmp(str, "tildas-library") == 0)
622       return colors.tildas_library;
623    else if (strcasecmp(str, "tildas-playlist") == 0)
624       return colors.tildas_playlist;
625    else if (strcasecmp(str, "playing-library") == 0)
626       return colors.playing_library;
627    else if (strcasecmp(str, "playing-playlist") == 0)
628       return colors.playing_playlist;
629    else if (strcasecmp(str, "current-inactive") == 0)
630       return colors.current_inactive;
631 
632    /* if reached here, check cinfo's array */
633    for (i = 0; i < MI_NUM_CINFO; i++) {
634       if (strcasecmp(str, MI_CINFO_NAMES[i]) == 0)
635          return colors.cinfos[i];
636    }
637 
638    return -1;
639 }
640 
641 int
paint_str2color(const char * str)642 paint_str2color(const char *str)
643 {
644    if (strcasecmp(str, "black") == 0)
645       return COLOR_BLACK;
646    else if (strcasecmp(str, "red") == 0)
647       return COLOR_RED;
648    else if (strcasecmp(str, "green") == 0)
649       return COLOR_GREEN;
650    else if (strcasecmp(str, "yellow") == 0)
651       return COLOR_YELLOW;
652    else if (strcasecmp(str, "blue") == 0)
653       return COLOR_BLUE;
654    else if (strcasecmp(str, "magenta") == 0)
655       return COLOR_MAGENTA;
656    else if (strcasecmp(str, "cyan") == 0)
657       return COLOR_CYAN;
658    else if (strcasecmp(str, "white") == 0)
659       return COLOR_WHITE;
660    else if (strcasecmp(str, "default") == 0)
661       return -1;
662    else
663       return -2;
664 }
665