1 /*-
2  * This file implements the user interface for tcd, a tiny CD player.
3  */
4 
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 
9 #include <ncurses.h>
10 
11 #include "cd-utils.h"
12 #include "cddb.h"
13 #include "user-interface.h"
14 #include "ui-layout.h"
15 
16 static struct coords screen, play_status, track_list, cpanel, info;
17 
update_coords(void)18 static void update_coords(void)
19 {
20     int maxy, maxx;
21     getmaxyx(stdscr, maxy, maxx);
22     set_coords(&screen, 0, 0, maxy, maxx);
23     set_coords(&play_status, 1, 1, 8, 28);
24     set_coords(&track_list, play_status.bottom + 1, 1, screen.bottom - 1,
25                28);
26     set_coords(&info, screen.bottom - 5, play_status.right + 1,
27                screen.bottom - 1, screen.right - 1);
28     set_coords(&cpanel, 1, play_status.right + 1, info.top,
29                screen.right - 1);
30 }
31 
is_playable(struct tcd_state * state)32 static int is_playable(struct tcd_state *state)
33 {
34     const CDstatus st = state->cdrom->status;
35     return (st == CD_STOPPED || st == CD_PAUSED || st == CD_PLAYING);
36 }
37 
play_method(PlayMethod pm)38 static const char *play_method(PlayMethod pm)
39 {
40     switch (pm) {
41     case REPEAT_CD:
42         return "Repeat CD";
43     case REPEAT_TRK:
44         return "Repeat Track";
45     case NORMAL:
46         return "Normal";
47     }
48     return "(Unknown)";
49 }
50 
play_status_str(struct tcd_state * state)51 static const char *play_status_str(struct tcd_state *state)
52 {
53     switch (state->cdrom->status) {
54     case CD_TRAYEMPTY:
55         return "No Audio";
56     case CD_PLAYING:
57         return "Playing";
58     case CD_PAUSED:
59         return "Paused";
60     case CD_STOPPED:
61         return "Stopped";
62     case CD_ERROR:
63         return "Error";
64     }
65     return "(Unknown)";
66 }
67 
init_colors(void)68 static void init_colors(void)
69 {
70     start_color();
71     init_pair(0, COLOR_BLACK, COLOR_BLACK);
72     init_pair(1, COLOR_RED, COLOR_BLACK);
73     init_pair(2, COLOR_GREEN, COLOR_BLACK);
74     init_pair(3, COLOR_YELLOW, COLOR_BLACK);
75     init_pair(4, COLOR_BLUE, COLOR_BLACK);
76     init_pair(5, COLOR_MAGENTA, COLOR_BLACK);
77     init_pair(6, COLOR_CYAN, COLOR_BLACK);
78     init_pair(7, COLOR_WHITE, COLOR_BLACK);
79 }
80 
phelp(int y,int x,char key,const char * func,int enabled)81 static void phelp(int y, int x, char key, const char *func, int enabled)
82 {
83     const int text_attr = (enabled) ? C_YELLOW | A_BOLD : A_DIM;
84     const int maxwidth = cpanel.width - (x + 4);
85     y += cpanel.top, x += cpanel.left;
86     attron(text_attr);
87     mvprintw(y, x, "[%c]", key);
88     attroff(text_attr);
89     mvaddlstr(y, x + 4, func, maxwidth);
90 }
91 
draw_tracklist(struct tcd_state * state)92 static void draw_tracklist(struct tcd_state *state)
93 {
94     const int base_x = track_list.left + 2, base_y = track_list.top + 1;
95     const int delta_x = 12, delta_y = 1;
96     const int tracks_x = 2, tracks_y = track_list.height - 1;
97     int i, x, y, track_first = 0, track_last = state->cdrom->numtracks - 1;
98 
99     if (track_last + 1 > tracks_x * tracks_y) {
100         track_first = state->cdrom->cur_track - tracks_x * tracks_y / 2;
101         track_last = state->cdrom->cur_track + tracks_x * tracks_y / 2 - 1;
102         if (track_first < 0) {
103             track_last -= track_first;
104             track_first -= track_first;
105         }
106         if (track_last > state->cdrom->numtracks - 1) {
107             track_first -= track_last - (state->cdrom->numtracks - 1);
108             track_last -= track_last - (state->cdrom->numtracks - 1);
109         }
110     }
111 
112     for (i = track_first, x = 0, y = 0; i <= track_last; i++) {
113         int cd_min, cd_sec, cd_frm;
114         int is_current = (state->cdrom->status == CD_PLAYING
115                           && state->cdrom->cur_track == i);
116         if (is_current) {
117             attron(A_BOLD);
118         }
119         FRAMES_TO_MSF(state->cdrom->track[i].length, &cd_min, &cd_sec,
120                       &cd_frm);
121         mvprintw(base_y + y * delta_y, base_x + x * delta_x,
122                  "%02u - %02u:%02u", 1 + i, cd_min, cd_sec);
123         if (is_current) {
124             attroff(A_BOLD);
125         }
126         if (y < tracks_y - 1) {
127             y++;
128         } else {
129             y = 0;
130             if (x < tracks_x - 1) {
131                 x++;
132             } else {
133                 break;
134             }
135         }
136     }
137 }
138 
draw_play_status(struct tcd_state * state)139 static void draw_play_status(struct tcd_state *state)
140 {
141     const struct coords *const ps = &play_status;
142     const int left1 = play_status.left + 1;
143     const int left2 = play_status.left + 13;
144     const int width1 = left2 - left1;
145     const int width2 = play_status.width - 13;
146     int cd_min, cd_sec, ti_min, ti_sec, dummy;
147 
148     FRAMES_TO_MSF(cd_length(state->cdrom), &cd_min, &cd_sec, &dummy);
149     FRAMES_TO_MSF(state->cdrom->cur_frame, &ti_min, &ti_sec, &dummy);
150 
151     mvaddlstr(ps->top + 1, left1, "Status:", width1);
152     mvaddlstr(ps->top + 2, left1, "Play mode:", width1);
153     mvaddlstr(ps->top + 3, left1, "Track:", width1);
154     mvaddlstr(ps->top + 4, left1, "Time:", width1);
155     mvaddlstr(ps->top + 5, left1, "CD:", width1);
156 
157     mvaddlstr(ps->top + 1, left2, play_status_str(state), width2);
158     mvaddlstr(ps->top + 2, left2, play_method(state->play_method), width2);
159     mvprintw(ps->top + 3, left2, "%d of %d", 1 + state->cdrom->cur_track,
160              state->cdrom->numtracks);
161     mvprintw(ps->top + 4, left2, "%02d:%02d", ti_min, ti_sec);
162     mvprintw(ps->top + 5, left2, "%02d:%02d", cd_min, cd_sec);
163 }
164 
draw_control_panel(struct tcd_state * state)165 static void draw_control_panel(struct tcd_state *state)
166 {
167     const int playable = is_playable(state);
168     phelp(1, 1, 'P', "- Start playing", playable);
169     phelp(2, 1, 'U', "- Pause or restart", playable);
170     phelp(3, 1, 'E', "- Eject CDROM",
171           state->cdrom->status != CD_TRAYEMPTY);
172     phelp(4, 1, 'S', "- Stop playing", playable);
173     phelp(5, 1, 'M', "- Change play mode", 1);
174     phelp(1, 26, '-', "- Previous track", playable);
175     phelp(2, 26, '+', "- Next track", playable);
176     phelp(3, 26, 'G', "- Go to track", playable);
177     phelp(4, 26, ']', "- Skip ahead", playable);
178     phelp(5, 26, '[', "- Skip back", playable);
179     phelp(7, 1, '*', "- Increase volume", playable);
180     phelp(8, 1, '/', "- Decrease volume", playable);
181     phelp(10, 1, 'T', "- Edit track database", playable);
182     phelp(11, 1, 'Q', "- Quit", 1);
183 }
184 
draw_info(struct tcd_state * state)185 static void draw_info(struct tcd_state *state)
186 {
187     const int left1 = info.left + 1;
188     const int left2 = info.left + 10;
189     const int width1 = left2 - left1;
190     const int width2 = info.width - 10;
191     mvaddlstr(info.top + 1, left1, "Title:", width1);
192     mvaddlstr(info.top + 2, left1, "Track:", width1);
193     attron(C_RED | A_BOLD);
194     mvaddlstr(info.top + 1, left2, state->cd_info.disc_title, width2);
195     mvaddlstr(info.top + 2, left2,
196               state->cd_info.trk[state->cdrom->cur_track].name, width2);
197     attroff(C_RED | A_BOLD);
198 }
199 
describe(struct coords * cbox,const char * descr)200 static void describe(struct coords *cbox, const char *descr)
201 {
202     mvaddlstr(cbox->top - 1, cbox->left + 1, descr, cbox->width - 3);
203 }
204 
draw_screen(struct tcd_state * state)205 static void draw_screen(struct tcd_state *state)
206 {
207     attron(C_BLUE);
208     box(stdscr, ACS_VLINE, ACS_HLINE);
209     mvvsplit(screen.top, play_status.right, screen.height);
210     mvhsplit(info.top - 1, info.left - 1, info.width + 2);
211     mvhsplit(play_status.bottom, play_status.left - 1,
212              play_status.width + 2);
213     attroff(C_BLUE);
214 
215     attron(C_BLUE | A_BOLD);
216     describe(&cpanel, "Control Panel");
217     describe(&info, "Disc Information");
218     describe(&track_list, "Track List");
219     attroff(C_BLUE | A_BOLD);
220 
221     attron(C_WHITE | A_BOLD);
222     mvaddlstr(screen.top, screen.left + 2, PACKAGE_STRING,
223               screen.width - 4);
224     attroff(C_WHITE | A_BOLD);
225 
226     draw_play_status(state);
227     draw_tracklist(state);
228     draw_control_panel(state);
229     draw_info(state);
230 }
231 
get_i_track(void)232 extern int get_i_track(void)
233 {
234     char tmp[5];
235     if (input_box(tmp, sizeof(tmp), "Go to which track?") != -1) {
236         return atoi(tmp) - 1;
237     }
238     return -1;
239 }
240 
tcd_ui_init(void)241 extern void tcd_ui_init(void)
242 {
243     if (initscr() == NULL) {
244         fprintf(stderr, "Error: Could not start ncurses\n");
245         exit(EXIT_FAILURE);
246     }
247     if (has_colors()) {
248         init_colors();
249     }
250     cbreak(), noecho(), leaveok(stdscr, TRUE);
251     keypad(stdscr, TRUE), halfdelay(5);
252 }
253 
tcd_ui_shutdown(void)254 extern void tcd_ui_shutdown(void)
255 {
256     wclear(stdscr);
257     wrefresh(stdscr);
258     wmove(stdscr, 0, 0);
259     endwin();
260 }
261 
tcd_ui_update(struct tcd_state * state)262 extern void tcd_ui_update(struct tcd_state *state)
263 {
264     erase();
265     update_coords();
266     draw_screen(state);
267 }
268 
tcd_ui_readkey(int delay)269 extern int tcd_ui_readkey(int delay)
270 {
271     if (delay > 0) {
272         halfdelay(delay);
273     } else {
274         nocbreak();
275     }
276     return getch();
277 }
278