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