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