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.
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,
28 #include <sys/types.h>
35 #include "interface.h"
39 #include "timecoder.h"
45 /* Screen refresh time in milliseconds */
50 /* Font definitions */
52 #define FONT "Vera.ttf"
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 */
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)
98 #define SCROLLBAR_SIZE 10
101 /* Function key (F1-F12) definitions */
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",
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
];
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
)
174 upper
->x
= source
->x
;
175 upper
->y
= source
->y
;
176 upper
->w
= source
->w
;
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
)
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
;
242 minutes
= (t
/ 60 / 1000) % (60*60);
243 seconds
= (t
/ 1000) % 60;
249 sprintf(buf
, "%02d:%02d.", minutes
, seconds
);
250 sprintf(deci
, "%03d", frac
);
254 static int calculate_spinner_lookup(int *angle
, int *distance
, int size
)
259 for(r
= 0; r
< size
; r
++) {
262 for(c
= 0; c
< size
; c
++) {
275 rat
= (float)(nc
) / -nr
;
286 = ((int)(theta
* 1024 / (M_PI
* 2)) + 1024) % 1024;
289 distance
[r
* size
+ c
] = sqrt(SQ(nc
) + SQ(nr
));
297 /* Open a font, given the leafname. This scans the available font
298 * directories for the file, to account for different software
301 static TTF_Font
* open_font(const char *name
, int size
) {
303 char buf
[256], **dir
;
311 sprintf(buf
, "%s/%s", *dir
, name
);
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
);
320 fputs("Font error: ", stderr
);
321 fputs(TTF_GetError(), stderr
);
324 return font
; /* or NULL */
327 if(errno
!= ENOENT
) {
336 fprintf(stderr
, "Font '%s' cannot be found in", name
);
351 static int load_fonts(void)
353 clock_font
= open_font(CLOCK_FONT
, CLOCK_FONT_SIZE
);
357 deci_font
= open_font(DECI_FONT
, DECI_FONT_SIZE
);
361 font
= open_font(FONT
, FONT_SIZE
);
365 em_font
= open_font(EM_FONT
, FONT_SIZE
);
369 detail_font
= open_font(DETAIL_FONT
, DETAIL_FONT_SIZE
);
377 static void clear_fonts(void)
379 TTF_CloseFont(deci_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
;
408 } else if(buf
[0] == '\0') { /* SDL_ttf fails for empty string */
413 rendered
= render_font(font
, buf
, fg
, bg
);
417 src
.w
= MIN(w
, rendered
->w
);
418 src
.h
= MIN(h
, rendered
->h
);
423 SDL_BlitSurface(rendered
, &src
, sf
, &dst
);
424 SDL_FreeSurface(rendered
);
427 /* Complete the remaining space with a blank rectangle */
434 SDL_FillRect(sf
, &fill
, palette(sf
, &bg
));
440 fill
.w
= src
.w
; /* the x-fill rectangle does the corner */
442 SDL_FillRect(sf
, &fill
, palette(sf
, &bg
));
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
,
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
)
464 struct rect_t top
, bottom
;
466 split_top(rect
, &top
, &bottom
, FONT_SPACE
, 0);
473 draw_font_rect(surface
, &top
, s
, font
, text_col
, background_col
);
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
,
489 char hms
[8], deci
[8];
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;
501 sr
.y
= rect
->y
+ offset
;
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
512 static void draw_scope(SDL_Surface
*surface
, const struct rect_t
*rect
,
513 struct timecoder_t
*tc
)
518 mid
= tc
->mon_size
/ 2;
520 for(r
= 0; r
< tc
->mon_size
; r
++) {
521 for(c
= 0; c
< tc
->mon_size
; c
++) {
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)
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
,
545 int x
, y
, r
, c
, rangle
, pangle
, position
;
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
566 pangle
= spinner_angle
[r
* SPINNER_SIZE
+ c
];
568 if(position
< 0 || position
>= track_get_sample_count(pl
->track
))
573 if((rangle
- pangle
+ 1024) % 1024 < 512) {
579 /* Calculate the final pixel location and set it */
581 p
= rp
+ (x
+ c
) * surface
->format
->BytesPerPixel
;
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
,
596 int elapse
, remain
, pos
;
597 struct rect_t upper
, lower
;
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
;
614 draw_clock(surface
, &upper
, elapse
, col
);
621 if(pl
->track
->status
== TRACK_STATUS_IMPORTING
) {
627 draw_clock(surface
, &lower
, remain
, col
);
631 /* Draw the high-level overview meter which shows the whole length
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
;
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;
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)
672 if(tr
->status
== TRACK_STATUS_IMPORTING
)
675 if(track_get_sample_count(tr
) && c
<= (long long)position
* w
/ track_get_sample_count(tr
))
682 /* Store a pointer to this column of the framebuffer */
684 p
= pixels
+ y
* pitch
+ (x
+ c
) * bytes_per_pixel
;
705 /* Draw the close-up meter, which can be zoomed to a level set by
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
;
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;
736 /* Select the appropriate colour */
746 /* Get a pointer to the top of the column, and increment
749 p
= pixels
+ y
* pitch
+ (x
+ c
) * bytes_per_pixel
;
753 p
[0] = col
.b
>> fade
;
754 p
[1] = col
.g
>> fade
;
755 p
[2] = col
.r
>> fade
;
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
);
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
,
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
);
805 draw_deck_clocks(surface
, &clocks
, pl
);
807 split_right(&right
, &left
, &spinner
, SPINNER_SIZE
, SPACER
);
810 split_bottom(&spinner
, NULL
, &spinner
, SPINNER_SIZE
, 0);
811 draw_spinner(surface
, &spinner
, pl
);
813 split_right(&left
, &clocks
, &scope
, SCOPE_SIZE
, SPACER
);
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
,
831 if(pl
->timecoder
&& (tc
=timecoder_get_position(pl
->timecoder
, NULL
)) != -1)
832 sprintf(buf
, "timecode:%d ", tc
);
834 sprintf(buf
, "timecode: ");
836 sprintf(buf
+ 17, "pitch:%+0.2f (sync %0.2f %+4.0f = %+0.2f) %s",
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
)
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);
862 draw_track_summary(surface
, &track
, pl
->track
);
864 split_top(&rest
, &top
, &lower
, CLOCK_FONT_SIZE
* 2, SPACER
);
868 draw_deck_top(surface
, &top
, pl
);
870 split_bottom(&lower
, &meters
, &status
, FONT_SPACE
, SPACER
);
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
,
887 struct rect_t single
;
889 deck_width
= (rect
->w
- BORDER
* (ndecks
- 1)) / ndecks
;
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
,
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
)
925 if(search
[0] != '\0')
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
);
941 cursor
.w
= CURSOR_WIDTH
;
942 cursor
.h
= FONT_SPACE
;
944 SDL_FillRect(surface
, &cursor
, palette(surface
, &cursor_col
));
947 sprintf(cm
, "%d matches", entries
);
949 sprintf(cm
, "1 match");
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
)
969 SDL_FillRect(surface
, &box
, palette(surface
, &col
));
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
);
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
,
991 int x
, y
, w
, h
, n
, r
, ox
;
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
)
1012 x
+= SCROLLBAR_SIZE
+ SPACER
;
1014 r
= y
+ n
* FONT_SPACE
;
1016 if(n
+ view_offset
== selected
)
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,
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;
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 */
1054 box
.y
= y
+ n
* FONT_SPACE
;
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
1072 box
.w
= SCROLLBAR_SIZE
;
1075 SDL_FillRect(surface
, &box
, palette(surface
, &col
));
1077 guint entries
= listing_get_count(ls
);
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
));
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
)
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
);
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);
1120 track
->artist
= NULL
;
1122 if(!isempty(record_get_field(record
, 1)))
1123 track
->title
= record_get_field(record
, 1);
1125 track
->title
= NULL
;
1127 track
->name
= record_get_name(record
);
1129 fprintf(stderr
, "Loading '%s'.\n", record_get_name(record
));
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());
1147 rect
->w
= w
- 2 * BORDER
;
1148 rect
->h
= h
- 2 * BORDER
;
1150 fprintf(stderr
, "New interface size is %dx%d.\n", w
, h
);
1156 static Uint32
ticker(Uint32 interval
, void *p
)
1160 if(!SDL_PeepEvents(&event
, 1, SDL_PEEKEVENT
, SDL_EVENTMASK(SDL_USEREVENT
)))
1162 event
.type
= SDL_USEREVENT
;
1163 SDL_PushEvent(&event
);
1170 void interface_init(struct interface_t
*in
)
1174 for(n
= 0; n
< MAX_PLAYERS
; n
++)
1175 in
->player
[n
] = 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
)
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
);
1209 gboolean
isAcceptingKey(gunichar2
* string
)
1211 gchar
* character
= utf16_get_utf8_search_string(string
);
1221 int interface_run(struct interface_t
*in
)
1223 int selected
, view_offset
, meter_scale
,
1224 library_lines
, p
, search_len
,
1226 library_update
, decks_update
, status_update
,
1229 const char *status
= BANNER
;
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
;
1248 meter_scale
= DEFAULT_METER_SCALE
;
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());
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());
1278 if(load_fonts() == -1)
1281 /* Initialise the screen surface */
1283 surface
= set_size(DEFAULT_WIDTH
, DEFAULT_HEIGHT
, &rworkspace
);
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
) {
1303 case SDL_VIDEORESIZE
:
1304 surface
= set_size(event
.resize
.w
, event
.resize
.h
, &rworkspace
);
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
;
1319 case SDL_USEREVENT
: /* request to update the clocks */
1321 if(decks_update
< UPDATE_REDRAW
)
1322 decks_update
= UPDATE_REDRAW
;
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
) {
1357 if(selected
< view_offset
)
1358 view_offset
-= library_lines
/ 2;
1360 library_update
= UPDATE_REDRAW
;
1362 } else if(key
== SDLK_DOWN
) {
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
) {
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
) {
1401 fprintf(stderr
, "Meter scale decreased to %d\n", meter_scale
);
1403 } else if(key
== SDLK_MINUS
) {
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
) {
1430 do_loading(pl
->track
, listing_get_record(results
, selected
));
1437 case FUNC_DISCONNECT
:
1438 player_disconnect_timecoder(pl
);
1441 case FUNC_RECONNECT
:
1442 player_connect_timecoder(pl
, in
->timecoder
[deck
]);
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) {
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
) {
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 */
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
) {
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
1508 (selected
< view_offset
)
1509 || (selected
> view_offset
+ library_lines
)
1511 view_offset
= selected
- library_lines
+ 1;
1517 status
= record_get_pathname(listing_get_record(results
, selected
));
1519 status
= "No search results found";
1520 status_update
= UPDATE_REDRAW
;
1523 library_lines
= draw_library(surface
, &rlibrary
, search
, results
,
1524 selected
, view_offset
);
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
) {
1534 draw_status(surface
, &rstatus
, status
);
1536 UPDATE(surface
, &rstatus
);
1537 status_update
= UPDATE_NONE
;
1540 /* If it's due, redraw the players. This is triggered by the
1543 if(decks_update
>= UPDATE_REDRAW
) {
1545 draw_decks(surface
, &rplayers
, in
->players
, in
->player
,
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
);
1560 SDL_RemoveTimer(timer
);
1562 for(p
= 0; p
< in
->timecoders
; p
++)
1563 timecoder_monitor_clear(in
->timecoder
[p
]);