Replace manual option handling by use of glib
[lcid-xwax.git] / interface.c
blob0ef926fc3c106a143865bb6a4c721624bb63c90d
1 /*
2 * Copyright (C) 2008 Mark Hills <mark@pogo.org.uk>
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License version 2 for more details.
13 * You should have received a copy of the GNU General Public License
14 * version 2 along with this program; if not, write to the Free
15 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16 * MA 02110-1301, USA.
20 #include <errno.h>
21 #include <math.h>
22 #include <pthread.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
30 #include <SDL.h>
31 #include <SDL_ttf.h>
33 #include <glib.h>
35 #include "interface.h"
36 #include "library.h"
37 #include "player.h"
38 #include "rig.h"
39 #include "timecoder.h"
40 #include "xwax.h"
41 #include "record.h"
42 #include "include.h"
45 /* Screen refresh time in milliseconds */
47 #define REFRESH 10
50 /* Font definitions */
52 #define FONT "Vera.ttf"
53 #define FONT_SIZE 10
54 #define FONT_SPACE 15
56 #define EM_FONT "VeraIt.ttf"
58 #define CLOCK_FONT FONT
59 #define CLOCK_FONT_SIZE 32
61 #define DECI_FONT FONT
62 #define DECI_FONT_SIZE 20
64 #define DETAIL_FONT "VeraMono.ttf"
65 #define DETAIL_FONT_SIZE 9
68 /* Screen dimensions */
70 #define BORDER 12
71 #define SPACER 8
73 #define CURSOR_WIDTH 4
75 #define PLAYER_HEIGHT 213
76 #define OVERVIEW_HEIGHT 16
78 #define LIBRARY_MIN_WIDTH 64
79 #define LIBRARY_MIN_HEIGHT 64
81 #define DEFAULT_WIDTH 960
82 #define DEFAULT_HEIGHT 720
83 #define DEFAULT_METER_SCALE 8
85 #define MAX_METER_SCALE 11
87 #define SEARCH_HEIGHT (FONT_SPACE)
88 #define STATUS_HEIGHT (FONT_SPACE)
90 //#define RESULTS_ARTIST_WIDTH 200
92 #define CLOCKS_WIDTH 160
94 #define SPINNER_SIZE (CLOCK_FONT_SIZE * 2 - 6)
95 #define SCOPE_SIZE (CLOCK_FONT_SIZE * 2 - 6)
96 #define SCOPE_SCALE 1
98 #define SCROLLBAR_SIZE 10
101 /* Function key (F1-F12) definitions */
103 #define FUNC_LOAD 0
104 #define FUNC_RECUE 1
105 #define FUNC_DISCONNECT 2
106 #define FUNC_RECONNECT 3
109 /* State variables used to trigger certain actions */
111 #define UPDATE_NONE 0
112 #define UPDATE_REDRAW 1
114 #define RESULTS_REFINE 2
115 #define RESULTS_EXPAND 3
118 /* Macro functions */
120 #define MIN(x,y) ((x)<(y)?(x):(y))
122 #define SQ(x) ((x)*(x))
124 #define LOCK(sf) if(SDL_MUSTLOCK(sf)) SDL_LockSurface(sf)
125 #define UNLOCK(sf) if(SDL_MUSTLOCK(sf)) SDL_UnlockSurface(sf)
126 #define UPDATE(sf, rect) SDL_UpdateRect(sf, (rect)->x, (rect)->y, \
127 (rect)->w, (rect)->h)
130 /* List of directories to use as search path for fonts. */
132 char *font_dirs[] = {
133 "/usr/X11R6/lib/X11/fonts/TTF",
134 "/usr/share/fonts/truetype/ttf-bitstream-vera",
135 "/usr/share/fonts/TTF",
136 NULL
140 TTF_Font *clock_font, *deci_font, *detail_font, *font, *em_font;
142 SDL_Color background_col = {0, 0, 0, 255},
143 text_col = {224, 224, 224, 255},
144 warn_col = {192, 64, 0, 255},
145 ok_col = {32, 128, 3, 255},
146 elapsed_col = {0, 32, 255, 255},
147 remain_col = {0, 0, 64, 255},
148 cursor_col = {192, 0, 0, 255},
149 selected_col = {0, 48, 64, 255},
150 detail_col = {128, 128, 128, 255},
151 needle_col = {255, 255, 255, 255},
152 vinyl_col = {0, 0, 0, 255},
153 vinyl_ppm_col = {255, 255, 255, 255};
155 int spinner_angle[SPINNER_SIZE * SPINNER_SIZE];
158 struct rect_t {
159 signed short x, y, w, h;
163 /* Split the given rectangle split v pixels from the top, with a gap
164 * of 'space' between the output rectangles */
166 static void split_top(const struct rect_t *source, struct rect_t *upper,
167 struct rect_t *lower, int v, int space)
169 int u;
171 u = v + space;
173 if(upper) {
174 upper->x = source->x;
175 upper->y = source->y;
176 upper->w = source->w;
177 upper->h = v;
180 if(lower) {
181 lower->x = source->x;
182 lower->y = source->y + u;
183 lower->w = source->w;
184 lower->h = source->h - u;
189 /* As above, x pixels from the bottom */
191 static void split_bottom(const struct rect_t *source, struct rect_t *upper,
192 struct rect_t *lower, int v, int space)
194 split_top(source, upper, lower, source->h - v - space, space);
198 /* As above, v pixels from the left */
200 static void split_left(const struct rect_t *source, struct rect_t *left,
201 struct rect_t *right, int v, int space)
203 int u;
205 u = v + space;
207 if(left) {
208 left->x = source->x;
209 left->y = source->y;
210 left->w = v;
211 left->h = source->h;
214 if(right) {
215 right->x = source->x + u;
216 right->y = source->y;
217 right->w = source->w - u;
218 right->h = source->h;
223 /* As above, v pixels from the right */
225 static void split_right(const struct rect_t *source, struct rect_t *left,
226 struct rect_t *right, int v, int space)
228 split_left(source, left, right, source->w - v - space, space);
232 static void time_to_clock(char *buf, char *deci, int t)
234 int minutes, seconds, frac, neg;
236 if(t < 0) {
237 t = abs(t);
238 neg = 1;
239 } else
240 neg = 0;
242 minutes = (t / 60 / 1000) % (60*60);
243 seconds = (t / 1000) % 60;
244 frac = t % 1000;
246 if(neg)
247 *buf++ = '-';
249 sprintf(buf, "%02d:%02d.", minutes, seconds);
250 sprintf(deci, "%03d", frac);
254 static int calculate_spinner_lookup(int *angle, int *distance, int size)
256 int r, c, nr, nc;
257 float theta, rat;
259 for(r = 0; r < size; r++) {
260 nr = r - size / 2;
262 for(c = 0; c < size; c++) {
263 nc = c - size / 2;
265 if(nr == 0)
266 theta = M_PI_2;
268 else if(nc == 0) {
269 theta = 0;
271 if(nr < 0)
272 theta = M_PI;
274 } else {
275 rat = (float)(nc) / -nr;
276 theta = atanf(rat);
278 if(rat < 0)
279 theta += M_PI;
282 if(nc <= 0)
283 theta += M_PI;
285 angle[r * size + c]
286 = ((int)(theta * 1024 / (M_PI * 2)) + 1024) % 1024;
288 if(distance)
289 distance[r * size + c] = sqrt(SQ(nc) + SQ(nr));
293 return 0;
297 /* Open a font, given the leafname. This scans the available font
298 * directories for the file, to account for different software
299 * distributions. */
301 static TTF_Font* open_font(const char *name, int size) {
302 int r;
303 char buf[256], **dir;
304 struct stat st;
305 TTF_Font *font;
307 dir = &font_dirs[0];
309 while(*dir) {
311 sprintf(buf, "%s/%s", *dir, name);
313 r = stat(buf, &st);
315 if(r != -1) { /* something exists at this path */
316 fprintf(stderr, "Loading font '%s', %dpt...\n", buf, size);
318 font = TTF_OpenFont(buf, size);
319 if(!font) {
320 fputs("Font error: ", stderr);
321 fputs(TTF_GetError(), stderr);
322 fputc('\n', stderr);
324 return font; /* or NULL */
327 if(errno != ENOENT) {
328 perror("stat");
329 return NULL;
332 dir++;
333 continue;
336 fprintf(stderr, "Font '%s' cannot be found in", name);
338 dir = &font_dirs[0];
339 while(*dir) {
340 fputc(' ', stderr);
341 fputs(*dir, stderr);
342 dir++;
344 fputc('.', stderr);
345 fputc('\n', stderr);
347 return NULL;
351 static int load_fonts(void)
353 clock_font = open_font(CLOCK_FONT, CLOCK_FONT_SIZE);
354 if(!clock_font)
355 return -1;
357 deci_font = open_font(DECI_FONT, DECI_FONT_SIZE);
358 if(!deci_font)
359 return -1;
361 font = open_font(FONT, FONT_SIZE);
362 if(!font)
363 return -1;
365 em_font = open_font(EM_FONT, FONT_SIZE);
366 if(!em_font)
367 return -1;
369 detail_font = open_font(DETAIL_FONT, DETAIL_FONT_SIZE);
370 if(!detail_font)
371 return -1;
373 return 0;
377 static void clear_fonts(void)
379 TTF_CloseFont(deci_font);
380 TTF_CloseFont(font);
381 TTF_CloseFont(em_font);
382 TTF_CloseFont(clock_font);
383 TTF_CloseFont(detail_font);
387 static Uint32 palette(SDL_Surface *sf, SDL_Color *col)
389 return SDL_MapRGB(sf->format, col->r, col->g, col->b);
392 static SDL_Surface* render_font(TTF_Font *font, const char *buf, SDL_Color fg, SDL_Color bg)
394 return TTF_RenderText_Shaded(font, buf, fg, bg);
397 static int draw_font(SDL_Surface *sf, int x, int y, int w, int h,
398 const char *buf, TTF_Font *font,
399 SDL_Color fg, SDL_Color bg)
401 SDL_Surface *rendered;
402 SDL_Rect dst, src, fill;
404 if(buf == NULL) {
405 src.w = 0;
406 src.h = 0;
408 } else if(buf[0] == '\0') { /* SDL_ttf fails for empty string */
409 src.w = 0;
410 src.h = 0;
412 } else {
413 rendered = render_font(font, buf, fg, bg);
415 src.x = 0;
416 src.y = 0;
417 src.w = MIN(w, rendered->w);
418 src.h = MIN(h, rendered->h);
420 dst.x = x;
421 dst.y = y;
423 SDL_BlitSurface(rendered, &src, sf, &dst);
424 SDL_FreeSurface(rendered);
427 /* Complete the remaining space with a blank rectangle */
429 if(src.w < w) {
430 fill.x = x + src.w;
431 fill.y = y;
432 fill.w = w - src.w;
433 fill.h = h;
434 SDL_FillRect(sf, &fill, palette(sf, &bg));
437 if(src.h < h) {
438 fill.x = x;
439 fill.y = y + src.h;
440 fill.w = src.w; /* the x-fill rectangle does the corner */
441 fill.h = h - src.h;
442 SDL_FillRect(sf, &fill, palette(sf, &bg));
445 return src.w;
449 static int draw_font_rect(SDL_Surface *surface, const struct rect_t *rect,
450 const char *buf, TTF_Font *font,
451 SDL_Color fg, SDL_Color bg)
453 return draw_font(surface, rect->x, rect->y, rect->w, rect->h,
454 buf, font, fg, bg);
458 /* Draw the display of artist name and track name */
460 static void draw_track_summary(SDL_Surface *surface, const struct rect_t *rect,
461 struct track_t *track)
463 const char *s;
464 struct rect_t top, bottom;
466 split_top(rect, &top, &bottom, FONT_SPACE, 0);
468 if(track->artist)
469 s = track->artist;
470 else
471 s = track->name;
473 draw_font_rect(surface, &top, s, font, text_col, background_col);
475 if(track->title)
476 s = track->title;
477 else
478 s = track->name;
480 draw_font_rect(surface, &bottom, s, em_font, text_col, background_col);
484 /* Draw a single clock, in hours:minutes.seconds format */
486 static void draw_clock(SDL_Surface *surface, const struct rect_t *rect, int t,
487 SDL_Color col)
489 char hms[8], deci[8];
490 short int v;
491 int offset;
492 struct rect_t sr;
494 time_to_clock(hms, deci, t);
496 v = draw_font_rect(surface, rect, hms, clock_font, col, background_col);
498 offset = CLOCK_FONT_SIZE - DECI_FONT_SIZE * 1.04;
500 sr.x = rect->x + v;
501 sr.y = rect->y + offset;
502 sr.w = rect->w - v;
503 sr.h = rect->h - offset;
505 draw_font_rect(surface, &sr, deci, deci_font, col, background_col);
509 /* Draw the visual display of the input audio to the timecoder (the
510 * 'scope') */
512 static void draw_scope(SDL_Surface *surface, const struct rect_t *rect,
513 struct timecoder_t *tc)
515 int r, c, v, mid;
516 Uint8 *p;
518 mid = tc->mon_size / 2;
520 for(r = 0; r < tc->mon_size; r++) {
521 for(c = 0; c < tc->mon_size; c++) {
522 p = surface->pixels
523 + (rect->y + r) * surface->pitch
524 + (rect->x + c) * surface->format->BytesPerPixel;
526 v = tc->mon[r * tc->mon_size + c];
528 if((r == mid || c == mid) && v < 64)
529 v = 64;
531 p[0] = v;
532 p[1] = p[0];
533 p[2] = p[1];
539 /* Draw the spinner which shows the rotational position of the record,
540 * and matches the physical rotation of the vinyl record */
542 static void draw_spinner(SDL_Surface *surface, const struct rect_t *rect,
543 struct player_t *pl)
545 int x, y, r, c, rangle, pangle, position;
546 Uint8 *rp, *p;
547 SDL_Color col;
549 x = rect->x;
550 y = rect->y;
552 position = pl->position - pl->offset;
553 rangle = (int)(pl->position * 1024 * 10 / 18 / TRACK_RATE) % 1024;
555 for(r = 0; r < SPINNER_SIZE; r++) {
557 /* Store a pointer to this row of the framebuffer */
559 rp = surface->pixels + (y + r) * surface->pitch;
561 for(c = 0; c < SPINNER_SIZE; c++) {
563 /* Use the lookup table to provide the angle at each
564 * pixel */
566 pangle = spinner_angle[r * SPINNER_SIZE + c];
568 if(position < 0 || position >= track_get_sample_count(pl->track))
569 col = warn_col;
570 else
571 col = ok_col;
573 if((rangle - pangle + 1024) % 1024 < 512) {
574 col.r >>= 2;
575 col.g >>= 2;
576 col.b >>= 2;
579 /* Calculate the final pixel location and set it */
581 p = rp + (x + c) * surface->format->BytesPerPixel;
583 p[0] = col.b;
584 p[1] = col.g;
585 p[2] = col.r;
591 /* Draw the clocks which show time elapsed and time remaining */
593 static void draw_deck_clocks(SDL_Surface *surface, const struct rect_t *rect,
594 struct player_t *pl)
596 int elapse, remain, pos;
597 struct rect_t upper, lower;
598 SDL_Color col;
600 split_top(rect, &upper, &lower, CLOCK_FONT_SIZE, 0);
602 pos = pl->position - pl->offset;
604 elapse = (long long)pos * 1000 / TRACK_RATE;
605 remain = ((long long)pos - track_get_sample_count(pl->track)) * 1000 / TRACK_RATE;
607 if(elapse < 0)
608 col = warn_col;
609 else if(remain <= 0)
610 col = ok_col;
611 else
612 col = text_col;
614 draw_clock(surface, &upper, elapse, col);
616 if(remain > 0)
617 col = warn_col;
618 else
619 col = text_col;
621 if(pl->track->status == TRACK_STATUS_IMPORTING) {
622 col.r >>= 2;
623 col.g >>= 2;
624 col.b >>= 2;
627 draw_clock(surface, &lower, remain, col);
631 /* Draw the high-level overview meter which shows the whole length
632 * of the track */
634 static void draw_overview(SDL_Surface *surface, const struct rect_t *rect,
635 const struct track_t *tr, int position)
637 int x, y, w, h, r, c, sp, fade, bytes_per_pixel, pitch, height;
638 Uint8 *pixels, *p;
639 SDL_Color col;
641 x = rect->x;
642 y = rect->y;
643 w = rect->w;
644 h = rect->h;
646 pixels = surface->pixels;
647 bytes_per_pixel = surface->format->BytesPerPixel;
648 pitch = surface->pitch;
650 for(c = 0; c < w; c++) {
652 /* Collect the correct meter value for this column */
654 sp = (long long)track_get_sample_count(tr) * c / w;
656 if(sp < track_get_sample_count(tr)) /* account for rounding */
657 height = track_get_overview(tr, sp) * h / 256;
658 else
659 height = 0;
661 /* Choose a base colour to display in */
663 if(!track_get_sample_count(tr))
664 col = background_col;
665 else if(position > track_get_sample_count(tr) - TRACK_RATE * 20)
666 col = warn_col;
667 else
668 col = elapsed_col;
670 fade = 0;
672 if(tr->status == TRACK_STATUS_IMPORTING)
673 fade++;
675 if(track_get_sample_count(tr) && c <= (long long)position * w / track_get_sample_count(tr))
676 fade++;
678 col.r >>= fade;
679 col.g >>= fade;
680 col.b >>= fade;
682 /* Store a pointer to this column of the framebuffer */
684 p = pixels + y * pitch + (x + c) * bytes_per_pixel;
686 r = h;
687 while(r > height) {
688 p[0] = col.b >> 2;
689 p[1] = col.g >> 2;
690 p[2] = col.r >> 2;
691 p += pitch;
692 r--;
694 while(r) {
695 p[0] = col.b;
696 p[1] = col.g;
697 p[2] = col.r;
698 p += pitch;
699 r--;
705 /* Draw the close-up meter, which can be zoomed to a level set by
706 * 'scale' */
708 static void draw_closeup(SDL_Surface *surface, const struct rect_t *rect,
709 struct track_t *tr, int position, int scale)
711 int x, y, w, h, r, c, sp, fade, bytes_per_pixel, pitch, height;
712 Uint8 *pixels, *p;
713 SDL_Color col;
715 x = rect->x;
716 y = rect->y;
717 w = rect->w;
718 h = rect->h;
720 pixels = surface->pixels;
721 bytes_per_pixel = surface->format->BytesPerPixel;
722 pitch = surface->pitch;
724 for(c = 0; c < w; c++) {
726 /* Work out the meter height in pixels for this column */
728 sp = position - (position % (1 << scale))
729 + ((c - w / 2) << scale);
731 if(sp < track_get_sample_count(tr) && sp > 0)
732 height = track_get_ppm(tr, sp) * h / 256;
733 else
734 height = 0;
736 /* Select the appropriate colour */
738 if(c == w / 2) {
739 col = needle_col;
740 fade = 1;
741 } else {
742 col = elapsed_col;
743 fade = 3;
746 /* Get a pointer to the top of the column, and increment
747 * it for each row */
749 p = pixels + y * pitch + (x + c) * bytes_per_pixel;
751 r = h;
752 while(r > height) {
753 p[0] = col.b >> fade;
754 p[1] = col.g >> fade;
755 p[2] = col.r >> fade;
756 p += pitch;
757 r--;
759 while(r) {
760 p[0] = col.b;
761 p[1] = col.g;
762 p[2] = col.r;
763 p += pitch;
764 r--;
770 /* Draw the audio meters for a deck */
772 static void draw_meters(SDL_Surface *surface, const struct rect_t *rect,
773 struct track_t *tr, int position, int scale)
775 struct rect_t overview, closeup;
777 split_top(rect, &overview, &closeup, OVERVIEW_HEIGHT, SPACER);
779 if(closeup.h > OVERVIEW_HEIGHT)
780 draw_overview(surface, &overview, tr, position);
781 else
782 closeup = *rect;
784 draw_closeup(surface, &closeup, tr, position, scale);
788 /* Draw the current playback status -- clocks, spinner and scope */
790 static void draw_deck_top(SDL_Surface *surface, const struct rect_t *rect,
791 struct player_t *pl)
793 struct rect_t clocks, left, right, spinner, scope;
795 split_left(rect, &clocks, &right, CLOCKS_WIDTH, SPACER);
797 /* If there is no timecoder to display information on, or not enough
798 * available space, just draw clocks which span the overall space */
800 if(!pl->timecoder || right.w < 0) {
801 draw_deck_clocks(surface, rect, pl);
802 return;
805 draw_deck_clocks(surface, &clocks, pl);
807 split_right(&right, &left, &spinner, SPINNER_SIZE, SPACER);
808 if(left.w < 0)
809 return;
810 split_bottom(&spinner, NULL, &spinner, SPINNER_SIZE, 0);
811 draw_spinner(surface, &spinner, pl);
813 split_right(&left, &clocks, &scope, SCOPE_SIZE, SPACER);
814 if(clocks.w < 0)
815 return;
816 split_bottom(&scope, NULL, &scope, SCOPE_SIZE, 0);
817 draw_scope(surface, &scope, pl->timecoder);
821 /* Draw the textual description of playback status, which includes
822 * information on the timecode */
824 static void draw_deck_status(SDL_Surface *surface,
825 const struct rect_t *rect,
826 struct player_t *pl)
828 char buf[128];
829 int tc;
831 if(pl->timecoder && (tc=timecoder_get_position(pl->timecoder, NULL)) != -1)
832 sprintf(buf, "timecode:%d ", tc);
833 else
834 sprintf(buf, "timecode: ");
836 sprintf(buf + 17, "pitch:%+0.2f (sync %0.2f %+4.0f = %+0.2f) %s",
837 pl->pitch,
838 pl->sync_pitch,
839 pl->last_difference,
840 pl->pitch * pl->sync_pitch,
841 pl->reconnect ? "RECN " : "");
843 draw_font_rect(surface, rect, buf, detail_font,
844 detail_col, background_col);
848 /* Draw a single deck */
850 static void draw_deck(SDL_Surface *surface, const struct rect_t *rect,
851 struct player_t *pl, int meter_scale)
853 int position;
854 struct rect_t track, top, meters, status, rest, lower;
856 position = pl->position - pl->offset;
858 split_top(rect, &track, &rest, FONT_SPACE * 2, 0);
859 if(rest.h < 160)
860 rest = *rect;
861 else
862 draw_track_summary(surface, &track, pl->track);
864 split_top(&rest, &top, &lower, CLOCK_FONT_SIZE * 2, SPACER);
865 if(lower.h < 64)
866 lower = rest;
867 else
868 draw_deck_top(surface, &top, pl);
870 split_bottom(&lower, &meters, &status, FONT_SPACE, SPACER);
871 if(meters.h < 64)
872 meters = lower;
873 else
874 draw_deck_status(surface, &status, pl);
876 draw_meters(surface, &meters, pl->track, position, meter_scale);
880 /* Draw all the decks in the system */
882 static void draw_decks(SDL_Surface *surface, const struct rect_t *rect,
883 int ndecks, struct player_t **player,
884 int meter_scale)
886 int d, deck_width;
887 struct rect_t single;
889 deck_width = (rect->w - BORDER * (ndecks - 1)) / ndecks;
891 single = *rect;
892 single.w = deck_width;
894 for(d = 0; d < ndecks; d++) {
895 single.x = rect->x + (deck_width + BORDER) * d;
896 draw_deck(surface, &single, player[d], meter_scale);
901 /* Draw the status bar */
903 static void draw_status(SDL_Surface *sf, const struct rect_t *rect,
904 const char *text)
906 draw_font(sf, rect->x, rect->y, rect->w, FONT_SPACE, text, detail_font,
907 detail_col, background_col);
911 /* Draw the search field which the user types into */
913 static void draw_search(SDL_Surface *surface, const struct rect_t *rect,
914 const char *search, int entries)
916 const char *buf;
917 char cm[32];
918 int x, y, w, s;
919 SDL_Rect cursor;
921 x = rect->x;
922 y = rect->y;
923 w = rect->w;
925 if(search[0] != '\0')
926 buf = search;
927 else
928 buf = NULL;
930 x += SCROLLBAR_SIZE + SPACER;
931 w -= SCROLLBAR_SIZE + SPACER;
933 s = draw_font(surface, x, y, w, FONT_SPACE, buf, font,
934 text_col, background_col);
936 x += s;
937 w -= s;
939 cursor.x = x;
940 cursor.y = y;
941 cursor.w = CURSOR_WIDTH;
942 cursor.h = FONT_SPACE;
944 SDL_FillRect(surface, &cursor, palette(surface, &cursor_col));
946 if(entries > 1)
947 sprintf(cm, "%d matches", entries);
948 else if(entries > 0)
949 sprintf(cm, "1 match");
950 else
951 sprintf(cm, "no matches");
953 x += CURSOR_WIDTH + SPACER;
954 w -= CURSOR_WIDTH + SPACER;
956 w = draw_font(surface, x, y, w, FONT_SPACE, cm, em_font,
957 detail_col, background_col);
960 static void draw_cell_spacer(SDL_Surface* surface, int* x, int y, SDL_Color col)
962 SDL_Rect box;
964 box.x = *x;
965 box.y = y;
966 box.w = SPACER;
967 box.h = FONT_SPACE;
969 SDL_FillRect(surface, &box, palette(surface, &col));
971 *x += SPACER;
974 static void draw_cell(SDL_Surface* surface, int* x, int y, const uint w, const struct record_t* re, uint fieldIndex, SDL_Color col)
976 draw_font(surface, *x, y, w, FONT_SPACE,
977 record_get_field(re, fieldIndex), font, text_col, col);
979 *x += w;
981 draw_cell_spacer(surface, x, y, col);
984 /* Display a record library listing, with scrollbar and current
985 * selection. Return the number of lines which fit on the display. */
987 static int draw_listing(SDL_Surface *surface, const struct rect_t *rect,
988 const struct listing_t *ls, int selected,
989 int view_offset)
991 int x, y, w, h, n, r, ox;
992 struct record_t *re;
993 SDL_Rect box;
994 SDL_Color col;
996 x = rect->x;
997 y = rect->y;
998 w = rect->w;
999 h = rect->h;
1001 ox = x;
1003 w -= SCROLLBAR_SIZE + SPACER;
1005 for(n = 0; n + view_offset < listing_get_count(ls); n++) {
1006 re = listing_get_record(ls, n + view_offset);
1008 if((n + 1) * FONT_SPACE > h)
1009 break;
1011 x = rect->x;
1012 x += SCROLLBAR_SIZE + SPACER;
1014 r = y + n * FONT_SPACE;
1016 if(n + view_offset == selected)
1017 col = selected_col;
1018 else
1019 col = background_col;
1021 uint fieldCount = record_get_field_count(re);
1023 for(uint f = 0; f != fieldCount; f++)
1025 draw_cell(surface, &x, r, listing_get_field_size(ls, f), re, f, col);
1029 if(isempty(record_get_field(re, 0))) {
1030 draw_font(surface, x, r, w, FONT_SPACE, record_get_name(re), font,
1031 text_col, col);
1033 } else {
1034 draw_font(surface, x, r, RESULTS_ARTIST_WIDTH, FONT_SPACE,
1035 record_get_field(re, 0), font, text_col, col);
1037 box.x = x + RESULTS_ARTIST_WIDTH;
1038 box.y = r;
1039 box.w = SPACER;
1040 box.h = FONT_SPACE;
1042 SDL_FillRect(surface, &box, palette(surface, &col));
1044 draw_font(surface, x + RESULTS_ARTIST_WIDTH + SPACER, r,
1045 w - RESULTS_ARTIST_WIDTH - SPACER, FONT_SPACE,
1046 record_get_field(re, 1), em_font, text_col, col);
1051 /* Blank any remaining space */
1053 box.x = ox;
1054 box.y = y + n * FONT_SPACE;
1055 box.w = w;
1056 box.h = h - (n * FONT_SPACE);
1058 SDL_FillRect(surface, &box, palette(surface, &background_col));
1060 /* Return to the origin and output the scrollbar -- now that we have
1061 * a value for n */
1063 x = ox;
1065 col = selected_col;
1066 col.r >>= 1;
1067 col.g >>= 1;
1068 col.b >>= 1;
1070 box.x = x;
1071 box.y = y;
1072 box.w = SCROLLBAR_SIZE;
1073 box.h = h;
1075 SDL_FillRect(surface, &box, palette(surface, &col));
1077 guint entries = listing_get_count(ls);
1079 if(entries > 0) {
1080 box.x = x;
1081 box.y = y + h * view_offset / entries;
1082 box.w = SCROLLBAR_SIZE;
1083 box.h = h * n / entries;
1085 SDL_FillRect(surface, &box, palette(surface, &selected_col));
1088 return n;
1092 /* Display the music library, which consists of the query, and search
1093 * results. Return the number of lines in the library display. */
1095 static int draw_library(SDL_Surface *surface, const struct rect_t *rect,
1096 const char *search, const struct listing_t *listing,
1097 int selected, int view_offset)
1099 int lines;
1100 struct rect_t rsearch, rresults;
1102 split_top(rect, &rsearch, &rresults, SEARCH_HEIGHT, SPACER);
1103 draw_search(surface, &rsearch, search, listing_get_count(listing));
1104 lines = draw_listing(surface, &rresults, listing, selected, view_offset);
1106 return lines;
1110 /* Initiate the process of loading a record from the library into a
1111 * track in memory */
1113 static int do_loading(struct track_t *track, const struct record_t *record)
1115 track_import(track, record_get_pathname(record));
1117 if(!isempty(record_get_field(record, 0)))
1118 track->artist = record_get_field(record, 0);
1119 else
1120 track->artist = NULL;
1122 if(!isempty(record_get_field(record, 1)))
1123 track->title = record_get_field(record, 1);
1124 else
1125 track->title = NULL;
1127 track->name = record_get_name(record);
1129 fprintf(stderr, "Loading '%s'.\n", record_get_name(record));
1131 return 0;
1135 static SDL_Surface* set_size(int w, int h, struct rect_t *rect)
1137 SDL_Surface *surface;
1139 surface = SDL_SetVideoMode(w, h, 32, SDL_RESIZABLE);
1140 if(surface == NULL) {
1141 fprintf(stderr, "%s\n", SDL_GetError());
1142 return NULL;
1145 rect->x = BORDER;
1146 rect->y = BORDER;
1147 rect->w = w - 2 * BORDER;
1148 rect->h = h - 2 * BORDER;
1150 fprintf(stderr, "New interface size is %dx%d.\n", w, h);
1152 return surface;
1156 static Uint32 ticker(Uint32 interval, void *p)
1158 SDL_Event event;
1160 if(!SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_EVENTMASK(SDL_USEREVENT)))
1162 event.type = SDL_USEREVENT;
1163 SDL_PushEvent(&event);
1166 return interval;
1170 void interface_init(struct interface_t *in)
1172 int n;
1174 for(n = 0; n < MAX_PLAYERS; n++)
1175 in->player[n] = NULL;
1177 in->listing = NULL;
1180 gchar* utf16_get_utf8_search_string(gunichar2* string)
1182 GError* error = NULL;
1183 glong items_read = 0;
1184 glong items_written = 0;
1186 gchar* enteredKey = g_utf16_to_utf8(string, -1, &items_read, &items_written, &error);
1188 char enteredChar = g_utf8_get_char(enteredKey);
1192 g_unichar_isalnum(enteredChar)
1193 || g_unichar_ispunct(enteredChar)
1195 return enteredKey;
1197 g_free(enteredKey);
1198 return NULL;
1201 gchar utf16_get_utf8_lower_char(gunichar2* string)
1203 gchar* character = utf16_get_utf8_search_string(string);
1204 gchar returnValue = g_unichar_tolower(*character);
1205 g_free(character);
1206 return returnValue;
1209 gboolean isAcceptingKey(gunichar2* string)
1211 gchar* character = utf16_get_utf8_search_string(string);
1212 if(character)
1214 g_free(character);
1215 return TRUE;
1218 return FALSE;
1221 int interface_run(struct interface_t *in)
1223 int selected, view_offset, meter_scale,
1224 library_lines, p, search_len,
1225 finished,
1226 library_update, decks_update, status_update,
1227 deck, func;
1228 char search[256];
1229 const char *status = BANNER;
1231 SDL_Event event;
1232 SDLKey key;
1233 SDLMod mod;
1234 SDL_TimerID timer;
1235 SDL_Surface *surface;
1237 struct rect_t rworkspace, rplayers, rlibrary, rstatus, rtmp;
1238 struct listing_t *results, *refine, *ltmp, la, lb;
1239 struct player_t *pl;
1241 finished = 0;
1243 listing_init(&la);
1244 listing_init(&lb);
1245 results = &la;
1246 refine = &lb;
1248 meter_scale = DEFAULT_METER_SCALE;
1250 search[0] = '\0';
1251 search_len = 0;
1252 library_lines = 0;
1253 selected = 0;
1254 view_offset = 0;
1256 for(p = 0; p < in->timecoders; p++)
1257 timecoder_monitor_init(in->timecoder[p], SCOPE_SIZE, SCOPE_SCALE);
1259 calculate_spinner_lookup(spinner_angle, NULL, SPINNER_SIZE);
1261 fprintf(stderr, "Initialising SDL...\n");
1263 if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
1264 fprintf(stderr, "%s\n", SDL_GetError());
1265 return -1;
1267 SDL_WM_SetCaption(BANNER, NULL);
1268 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
1269 SDL_EnableUNICODE(1);
1271 /* Initialise the fonts */
1273 if(TTF_Init() == -1) {
1274 fprintf(stderr, "%s\n", TTF_GetError());
1275 return -1;
1278 if(load_fonts() == -1)
1279 return -1;
1281 /* Initialise the screen surface */
1283 surface = set_size(DEFAULT_WIDTH, DEFAULT_HEIGHT, &rworkspace);
1284 if(!surface)
1285 return -1;
1287 decks_update = UPDATE_REDRAW;
1288 status_update = UPDATE_REDRAW;
1289 library_update = RESULTS_EXPAND;
1291 /* The final action is to add the timer which triggers refresh */
1293 timer = SDL_AddTimer(REFRESH, ticker, (void*)in);
1295 while(!finished && SDL_WaitEvent(&event) >= 0) {
1297 switch(event.type) {
1299 case SDL_QUIT:
1300 finished = 1;
1301 break;
1303 case SDL_VIDEORESIZE:
1304 surface = set_size(event.resize.w, event.resize.h, &rworkspace);
1305 if(!surface)
1306 return -1;
1308 if(library_update < UPDATE_REDRAW)
1309 library_update = UPDATE_REDRAW;
1311 if(decks_update < UPDATE_REDRAW)
1312 decks_update = UPDATE_REDRAW;
1314 if(status_update < UPDATE_REDRAW)
1315 status_update = UPDATE_REDRAW;
1317 break;
1319 case SDL_USEREVENT: /* request to update the clocks */
1321 if(decks_update < UPDATE_REDRAW)
1322 decks_update = UPDATE_REDRAW;
1324 break;
1326 case SDL_KEYDOWN:
1327 key = event.key.keysym.sym;
1328 mod = event.key.keysym.mod;
1330 if(isAcceptingKey(&event.key.keysym.unicode))
1332 search[search_len] = utf16_get_utf8_lower_char(&event.key.keysym.unicode);
1333 search[++search_len] = '\0';
1334 library_update = RESULTS_REFINE;
1336 if(key >= SDLK_a && key <= SDLK_z) {
1337 search[search_len] = (key - SDLK_a) + 'a';
1338 search[++search_len] = '\0';
1339 library_update = RESULTS_REFINE;
1341 } else if(key >= SDLK_0 && key <= SDLK_9) {
1342 search[search_len] = (key - SDLK_0) + '0';
1343 search[++search_len] = '\0';
1344 library_update = RESULTS_REFINE;
1346 } else if(key == SDLK_SPACE) {
1347 search[search_len] = ' ';
1348 search[++search_len] = '\0';
1349 library_update = RESULTS_REFINE;
1350 } else if(key == SDLK_BACKSPACE && search_len > 0) {
1351 search[--search_len] = '\0';
1352 library_update = RESULTS_EXPAND;
1354 } else if(key == SDLK_UP) {
1355 selected--;
1357 if(selected < view_offset)
1358 view_offset -= library_lines / 2;
1360 library_update = UPDATE_REDRAW;
1362 } else if(key == SDLK_DOWN) {
1363 selected++;
1365 if(selected > view_offset + library_lines - 1)
1366 view_offset += library_lines / 2;
1368 library_update = UPDATE_REDRAW;
1370 } else if(key == SDLK_PAGEUP) {
1371 view_offset -= library_lines;
1373 if(selected > view_offset + library_lines - 1)
1374 selected = view_offset + library_lines - 1;
1376 library_update = UPDATE_REDRAW;
1378 } else if(key == SDLK_PAGEDOWN) {
1379 view_offset += library_lines;
1381 if(selected < view_offset)
1382 selected = view_offset;
1384 library_update = UPDATE_REDRAW;
1385 } else if(key == SDLK_HOME) {
1386 selected = 0;
1388 library_update = UPDATE_REDRAW;
1390 } else if(key == SDLK_END) {
1391 selected = listing_get_count(results) - 1;
1393 library_update = UPDATE_REDRAW;
1395 } else if(key == SDLK_EQUALS) {
1396 meter_scale--;
1398 if(meter_scale < 0)
1399 meter_scale = 0;
1401 fprintf(stderr, "Meter scale decreased to %d\n", meter_scale);
1403 } else if(key == SDLK_MINUS) {
1404 meter_scale++;
1406 if(meter_scale > MAX_METER_SCALE)
1407 meter_scale = MAX_METER_SCALE;
1409 fprintf(stderr, "Meter scale increased to %d\n", meter_scale);
1411 } else if(key >= SDLK_F1 && key <= SDLK_F12) {
1413 /* Handle the function key press in groups of four --
1414 * F1-F4 (deck 0), F5-F8 (deck 1) etc. */
1416 func = (key - SDLK_F1) % 4;
1417 deck = (key - SDLK_F1) / 4;
1419 if(deck < in->players) {
1420 pl = in->player[deck];
1422 if(mod & KMOD_SHIFT) {
1423 if(func < in->timecoders)
1424 player_connect_timecoder(pl, in->timecoder[func]);
1426 } else switch(func) {
1428 case FUNC_LOAD:
1429 if(selected != -1)
1430 do_loading(pl->track, listing_get_record(results, selected));
1431 break;
1433 case FUNC_RECUE:
1434 player_recue(pl);
1435 break;
1437 case FUNC_DISCONNECT:
1438 player_disconnect_timecoder(pl);
1439 break;
1441 case FUNC_RECONNECT:
1442 player_connect_timecoder(pl, in->timecoder[deck]);
1443 break;
1448 break;
1449 } /* switch(event.type) */
1451 /* Split the display into the various areas. If an area is too
1452 * small, abandon any actions to happen in that area. */
1454 split_bottom(&rworkspace, &rtmp, &rstatus, STATUS_HEIGHT, SPACER);
1455 if(rtmp.h < 128 || rtmp.w < 0) {
1456 rtmp = rworkspace;
1457 status_update = UPDATE_NONE;
1460 split_top(&rtmp, &rplayers, &rlibrary, PLAYER_HEIGHT, SPACER);
1461 if(rlibrary.h < LIBRARY_MIN_HEIGHT || rlibrary.w < LIBRARY_MIN_WIDTH) {
1462 rplayers = rtmp;
1463 library_update = UPDATE_NONE;
1466 if(rplayers.h < 0 || rplayers.w < 0)
1467 decks_update = UPDATE_NONE;
1469 /* Re-search the library for based on the new criteria */
1471 if(library_update == RESULTS_EXPAND) {
1472 listing_blank(results);
1473 listing_match(in->listing, results, search);
1475 } else if(library_update == RESULTS_REFINE) {
1476 listing_blank(refine);
1477 listing_match(results, refine, search);
1479 /* Swap the a and b results lists */
1481 ltmp = results;
1482 results = refine;
1483 refine = ltmp;
1486 /* If there's been a change to the library search results,
1487 * check them over and display them. */
1489 guint entries = listing_get_count(results);
1491 if(library_update >= UPDATE_REDRAW) {
1492 if(selected < 0)
1493 selected = 0;
1495 if(selected >= entries)
1496 selected = entries - 1;
1498 /* After the above, if selected == -1, there is no
1499 * valid item selected; 0 means the first item */
1501 if(view_offset > entries - library_lines)
1502 view_offset = entries - library_lines;
1504 // we need to make sure that the selected line
1505 // is shown
1508 (selected < view_offset)
1509 || (selected > view_offset + library_lines)
1511 view_offset = selected - library_lines + 1;
1513 if(view_offset < 0)
1514 view_offset = 0;
1516 if(selected != -1)
1517 status = record_get_pathname(listing_get_record(results, selected));
1518 else
1519 status = "No search results found";
1520 status_update = UPDATE_REDRAW;
1522 LOCK(surface);
1523 library_lines = draw_library(surface, &rlibrary, search, results,
1524 selected, view_offset);
1525 UNLOCK(surface);
1526 UPDATE(surface, &rlibrary);
1527 library_update = UPDATE_NONE;
1530 /* If there's been a change to status, redisplay it */
1532 if(status_update >= UPDATE_REDRAW) {
1533 LOCK(surface);
1534 draw_status(surface, &rstatus, status);
1535 UNLOCK(surface);
1536 UPDATE(surface, &rstatus);
1537 status_update = UPDATE_NONE;
1540 /* If it's due, redraw the players. This is triggered by the
1541 * timer event. */
1543 if(decks_update >= UPDATE_REDRAW) {
1544 LOCK(surface);
1545 draw_decks(surface, &rplayers, in->players, in->player,
1546 meter_scale);
1547 UNLOCK(surface);
1548 UPDATE(surface, &rplayers);
1549 decks_update = UPDATE_NONE;
1552 /* Enter wait state for any tracks. Do this from the same
1553 * thread as any import processes were launched from. */
1555 for(p = 0; p < in->players; p++)
1556 track_wait(in->player[p]->track);
1558 } /* main loop */
1560 SDL_RemoveTimer(timer);
1562 for(p = 0; p < in->timecoders; p++)
1563 timecoder_monitor_clear(in->timecoder[p]);
1565 listing_clear(&la);
1566 listing_clear(&lb);
1568 clear_fonts();
1570 TTF_Quit();
1571 SDL_Quit();
1573 return 0;