File: client\qmenu.c
1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
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 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 #include <string.h>
21 #include <ctype.h>
22
23 #include "client.h"
24 #include "qmenu.h"
25
26 static void Action_DoEnter( menuaction_s *a );
27 static void Action_Draw( menuaction_s *a );
28 static void Menu_DrawStatusBar( const char *string );
29 static void Menulist_DoEnter( menulist_s *l );
30 static void MenuList_Draw( menulist_s *l );
31 static void Separator_Draw( menuseparator_s *s );
32 static void Slider_DoSlide( menuslider_s *s, int dir );
33 static void Slider_Draw( menuslider_s *s );
34 static void SpinControl_DoEnter( menulist_s *s );
35 static void SpinControl_Draw( menulist_s *s );
36 static void SpinControl_DoSlide( menulist_s *s, int dir );
37
38 #define RCOLUMN_OFFSET 16
39 #define LCOLUMN_OFFSET -16
40
41 extern refexport_t re;
42 extern viddef_t viddef;
43
44 #define VID_WIDTH viddef.width
45 #define VID_HEIGHT viddef.height
46
47 #define Draw_Char re.DrawChar
48 #define Draw_Fill re.DrawFill
49
50 void Action_DoEnter( menuaction_s *a )
51 {
52 if ( a->generic.callback )
53 a->generic.callback( a );
54 }
55
56 void Action_Draw( menuaction_s *a )
57 {
58 if ( a->generic.flags & QMF_LEFT_JUSTIFY )
59 {
60 if ( a->generic.flags & QMF_GRAYED )
61 Menu_DrawStringDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
62 else
63 Menu_DrawString( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
64 }
65 else
66 {
67 if ( a->generic.flags & QMF_GRAYED )
68 Menu_DrawStringR2LDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
69 else
70 Menu_DrawStringR2L( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
71 }
72 if ( a->generic.ownerdraw )
73 a->generic.ownerdraw( a );
74 }
75
76 qboolean Field_DoEnter( menufield_s *f )
77 {
78 if ( f->generic.callback )
79 {
80 f->generic.callback( f );
81 return true;
82 }
83 return false;
84 }
85
86 void Field_Draw( menufield_s *f )
87 {
88 int i;
89 char tempbuffer[128]="";
90
91 if ( f->generic.name )
92 Menu_DrawStringR2LDark( f->generic.x + f->generic.parent->x + LCOLUMN_OFFSET, f->generic.y + f->generic.parent->y, f->generic.name );
93
94 strncpy( tempbuffer, f->buffer + f->visible_offset, f->visible_length );
95
96 Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y - 4, 18 );
97 Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y + 4, 24 );
98
99 Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y - 4, 20 );
100 Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y + 4, 26 );
101
102 for ( i = 0; i < f->visible_length; i++ )
103 {
104 Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y - 4, 19 );
105 Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y + 4, 25 );
106 }
107
108 Menu_DrawString( f->generic.x + f->generic.parent->x + 24, f->generic.y + f->generic.parent->y, tempbuffer );
109
110 if ( Menu_ItemAtCursor( f->generic.parent ) == f )
111 {
112 int offset;
113
114 if ( f->visible_offset )
115 offset = f->visible_length;
116 else
117 offset = f->cursor;
118
119 if ( ( ( int ) ( Sys_Milliseconds() / 250 ) ) & 1 )
120 {
121 Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
122 f->generic.y + f->generic.parent->y,
123 11 );
124 }
125 else
126 {
127 Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
128 f->generic.y + f->generic.parent->y,
129 ' ' );
130 }
131 }
132 }
133
134 qboolean Field_Key( menufield_s *f, int key )
135 {
136 extern int keydown[];
137
138 switch ( key )
139 {
140 case K_KP_SLASH:
141 key = '/';
142 break;
143 case K_KP_MINUS:
144 key = '-';
145 break;
146 case K_KP_PLUS:
147 key = '+';
148 break;
149 case K_KP_HOME:
150 key = '7';
151 break;
152 case K_KP_UPARROW:
153 key = '8';
154 break;
155 case K_KP_PGUP:
156 key = '9';
157 break;
158 case K_KP_LEFTARROW:
159 key = '4';
160 break;
161 case K_KP_5:
162 key = '5';
163 break;
164 case K_KP_RIGHTARROW:
165 key = '6';
166 break;
167 case K_KP_END:
168 key = '1';
169 break;
170 case K_KP_DOWNARROW:
171 key = '2';
172 break;
173 case K_KP_PGDN:
174 key = '3';
175 break;
176 case K_KP_INS:
177 key = '0';
178 break;
179 case K_KP_DEL:
180 key = '.';
181 break;
182 }
183
184 if ( key > 127 )
185 {
186 switch ( key )
187 {
188 case K_DEL:
189 default:
190 return false;
191 }
192 }
193
194 /*
195 ** support pasting from the clipboard
196 */
197 if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) ||
198 ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) )
199 {
200 char *cbd;
201
202 if ( ( cbd = Sys_GetClipboardData() ) != 0 )
203 {
204 strtok( cbd, "\n\r\b" );
205
206 strncpy( f->buffer, cbd, f->length - 1 );
207 f->cursor = strlen( f->buffer );
208 f->visible_offset = f->cursor - f->visible_length;
209 if ( f->visible_offset < 0 )
210 f->visible_offset = 0;
211
212 free( cbd );
213 }
214 return true;
215 }
216
217 switch ( key )
218 {
219 case K_KP_LEFTARROW:
220 case K_LEFTARROW:
221 case K_BACKSPACE:
222 if ( f->cursor > 0 )
223 {
224 memmove( &f->buffer[f->cursor-1], &f->buffer[f->cursor], strlen( &f->buffer[f->cursor] ) + 1 );
225 f->cursor--;
226
227 if ( f->visible_offset )
228 {
229 f->visible_offset--;
230 }
231 }
232 break;
233
234 case K_KP_DEL:
235 case K_DEL:
236 memmove( &f->buffer[f->cursor], &f->buffer[f->cursor+1], strlen( &f->buffer[f->cursor+1] ) + 1 );
237 break;
238
239 case K_KP_ENTER:
240 case K_ENTER:
241 case K_ESCAPE:
242 case K_TAB:
243 return false;
244
245 case K_SPACE:
246 default:
247 if ( !isdigit( key ) && ( f->generic.flags & QMF_NUMBERSONLY ) )
248 return false;
249
250 if ( f->cursor < f->length )
251 {
252 f->buffer[f->cursor++] = key;
253 f->buffer[f->cursor] = 0;
254
255 if ( f->cursor > f->visible_length )
256 {
257 f->visible_offset++;
258 }
259 }
260 }
261
262 return true;
263 }
264
265 void Menu_AddItem( menuframework_s *menu, void *item )
266 {
267 if ( menu->nitems == 0 )
268 menu->nslots = 0;
269
270 if ( menu->nitems < MAXMENUITEMS )
271 {
272 menu->items[menu->nitems] = item;
273 ( ( menucommon_s * ) menu->items[menu->nitems] )->parent = menu;
274 menu->nitems++;
275 }
276
277 menu->nslots = Menu_TallySlots( menu );
278 }
279
280 /*
281 ** Menu_AdjustCursor
282 **
283 ** This function takes the given menu, the direction, and attempts
284 ** to adjust the menu's cursor so that it's at the next available
285 ** slot.
286 */
287 void Menu_AdjustCursor( menuframework_s *m, int dir )
288 {
289 menucommon_s *citem;
290
291 /*
292 ** see if it's in a valid spot
293 */
294 if ( m->cursor >= 0 && m->cursor < m->nitems )
295 {
296 if ( ( citem = Menu_ItemAtCursor( m ) ) != 0 )
297 {
298 if ( citem->type != MTYPE_SEPARATOR )
299 return;
300 }
301 }
302
303 /*
304 ** it's not in a valid spot, so crawl in the direction indicated until we
305 ** find a valid spot
306 */
307 if ( dir == 1 )
308 {
309 while ( 1 )
310 {
311 citem = Menu_ItemAtCursor( m );
312 if ( citem )
313 if ( citem->type != MTYPE_SEPARATOR )
314 break;
315 m->cursor += dir;
316 if ( m->cursor >= m->nitems )
317 m->cursor = 0;
318 }
319 }
320 else
321 {
322 while ( 1 )
323 {
324 citem = Menu_ItemAtCursor( m );
325 if ( citem )
326 if ( citem->type != MTYPE_SEPARATOR )
327 break;
328 m->cursor += dir;
329 if ( m->cursor < 0 )
330 m->cursor = m->nitems - 1;
331 }
332 }
333 }
334
335 void Menu_Center( menuframework_s *menu )
336 {
337 int height;
338
339 height = ( ( menucommon_s * ) menu->items[menu->nitems-1])->y;
340 height += 10;
341
342 menu->y = ( VID_HEIGHT - height ) / 2;
343 }
344
345 void Menu_Draw( menuframework_s *menu )
346 {
347 int i;
348 menucommon_s *item;
349
350 /*
351 ** draw contents
352 */
353 for ( i = 0; i < menu->nitems; i++ )
354 {
355 switch ( ( ( menucommon_s * ) menu->items[i] )->type )
356 {
357 case MTYPE_FIELD:
358 Field_Draw( ( menufield_s * ) menu->items[i] );
359 break;
360 case MTYPE_SLIDER:
361 Slider_Draw( ( menuslider_s * ) menu->items[i] );
362 break;
363 case MTYPE_LIST:
364 MenuList_Draw( ( menulist_s * ) menu->items[i] );
365 break;
366 case MTYPE_SPINCONTROL:
367 SpinControl_Draw( ( menulist_s * ) menu->items[i] );
368 break;
369 case MTYPE_ACTION:
370 Action_Draw( ( menuaction_s * ) menu->items[i] );
371 break;
372 case MTYPE_SEPARATOR:
373 Separator_Draw( ( menuseparator_s * ) menu->items[i] );
374 break;
375 }
376 }
377
378 item = Menu_ItemAtCursor( menu );
379
380 if ( item && item->cursordraw )
381 {
382 item->cursordraw( item );
383 }
384 else if ( menu->cursordraw )
385 {
386 menu->cursordraw( menu );
387 }
388 else if ( item && item->type != MTYPE_FIELD )
389 {
390 if ( item->flags & QMF_LEFT_JUSTIFY )
391 {
392 Draw_Char( menu->x + item->x - 24 + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );
393 }
394 else
395 {
396 Draw_Char( menu->x + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );
397 }
398 }
399
400 if ( item )
401 {
402 if ( item->statusbarfunc )
403 item->statusbarfunc( ( void * ) item );
404 else if ( item->statusbar )
405 Menu_DrawStatusBar( item->statusbar );
406 else
407 Menu_DrawStatusBar( menu->statusbar );
408
409 }
410 else
411 {
412 Menu_DrawStatusBar( menu->statusbar );
413 }
414 }
415
416 void Menu_DrawStatusBar( const char *string )
417 {
418 if ( string )
419 {
420 int l = strlen( string );
421 int maxrow = VID_HEIGHT / 8;
422 int maxcol = VID_WIDTH / 8;
423 int col = maxcol / 2 - l / 2;
424
425 Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 4 );
426 Menu_DrawString( col*8, VID_HEIGHT - 8, string );
427 }
428 else
429 {
430 Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 0 );
431 }
432 }
433
434 void Menu_DrawString( int x, int y, const char *string )
435 {
436 unsigned i;
437
438 for ( i = 0; i < strlen( string ); i++ )
439 {
440 Draw_Char( ( x + i*8 ), y, string[i] );
441 }
442 }
443
444 void Menu_DrawStringDark( int x, int y, const char *string )
445 {
446 unsigned i;
447
448 for ( i = 0; i < strlen( string ); i++ )
449 {
450 Draw_Char( ( x + i*8 ), y, string[i] + 128 );
451 }
452 }
453
454 void Menu_DrawStringR2L( int x, int y, const char *string )
455 {
456 unsigned i;
457
458 for ( i = 0; i < strlen( string ); i++ )
459 {
460 Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1] );
461 }
462 }
463
464 void Menu_DrawStringR2LDark( int x, int y, const char *string )
465 {
466 unsigned i;
467
468 for ( i = 0; i < strlen( string ); i++ )
469 {
470 Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1]+128 );
471 }
472 }
473
474 void *Menu_ItemAtCursor( menuframework_s *m )
475 {
476 if ( m->cursor < 0 || m->cursor >= m->nitems )
477 return 0;
478
479 return m->items[m->cursor];
480 }
481
482 qboolean Menu_SelectItem( menuframework_s *s )
483 {
484 menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );
485
486 if ( item )
487 {
488 switch ( item->type )
489 {
490 case MTYPE_FIELD:
491 return Field_DoEnter( ( menufield_s * ) item ) ;
492 case MTYPE_ACTION:
493 Action_DoEnter( ( menuaction_s * ) item );
494 return true;
495 case MTYPE_LIST:
496 // Menulist_DoEnter( ( menulist_s * ) item );
497 return false;
498 case MTYPE_SPINCONTROL:
499 // SpinControl_DoEnter( ( menulist_s * ) item );
500 return false;
501 }
502 }
503 return false;
504 }
505
506 void Menu_SetStatusBar( menuframework_s *m, const char *string )
507 {
508 m->statusbar = string;
509 }
510
511 void Menu_SlideItem( menuframework_s *s, int dir )
512 {
513 menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );
514
515 if ( item )
516 {
517 switch ( item->type )
518 {
519 case MTYPE_SLIDER:
520 Slider_DoSlide( ( menuslider_s * ) item, dir );
521 break;
522 case MTYPE_SPINCONTROL:
523 SpinControl_DoSlide( ( menulist_s * ) item, dir );
524 break;
525 }
526 }
527 }
528
529 int Menu_TallySlots( menuframework_s *menu )
530 {
531 int i;
532 int total = 0;
533
534 for ( i = 0; i < menu->nitems; i++ )
535 {
536 if ( ( ( menucommon_s * ) menu->items[i] )->type == MTYPE_LIST )
537 {
538 int nitems = 0;
539 const char **n = ( ( menulist_s * ) menu->items[i] )->itemnames;
540
541 while (*n)
542 nitems++, n++;
543
544 total += nitems;
545 }
546 else
547 {
548 total++;
549 }
550 }
551
552 return total;
553 }
554
555 void Menulist_DoEnter( menulist_s *l )
556 {
557 int start;
558
559 start = l->generic.y / 10 + 1;
560
561 l->curvalue = l->generic.parent->cursor - start;
562
563 if ( l->generic.callback )
564 l->generic.callback( l );
565 }
566
567 void MenuList_Draw( menulist_s *l )
568 {
569 const char **n;
570 int y = 0;
571
572 Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y, l->generic.name );
573
574 n = l->itemnames;
575
576 Draw_Fill( l->generic.x - 112 + l->generic.parent->x, l->generic.parent->y + l->generic.y + l->curvalue*10 + 10, 128, 10, 16 );
577 while ( *n )
578 {
579 Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y + y + 10, *n );
580
581 n++;
582 y += 10;
583 }
584 }
585
586 void Separator_Draw( menuseparator_s *s )
587 {
588 if ( s->generic.name )
589 Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->generic.name );
590 }
591
592 void Slider_DoSlide( menuslider_s *s, int dir )
593 {
594 s->curvalue += dir;
595
596 if ( s->curvalue > s->maxvalue )
597 s->curvalue = s->maxvalue;
598 else if ( s->curvalue < s->minvalue )
599 s->curvalue = s->minvalue;
600
601 if ( s->generic.callback )
602 s->generic.callback( s );
603 }
604
605 #define SLIDER_RANGE 10
606
607 void Slider_Draw( menuslider_s *s )
608 {
609 int i;
610
611 Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET,
612 s->generic.y + s->generic.parent->y,
613 s->generic.name );
614
615 s->range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue );
616
617 if ( s->range < 0)
618 s->range = 0;
619 if ( s->range > 1)
620 s->range = 1;
621 Draw_Char( s->generic.x + s->generic.parent->x + RCOLUMN_OFFSET, s->generic.y + s->generic.parent->y, 128);
622 for ( i = 0; i < SLIDER_RANGE; i++ )
623 Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 129);
624 Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 130);
625 Draw_Char( ( int ) ( 8 + RCOLUMN_OFFSET + s->generic.parent->x + s->generic.x + (SLIDER_RANGE-1)*8 * s->range ), s->generic.y + s->generic.parent->y, 131);
626 }
627
628 void SpinControl_DoEnter( menulist_s *s )
629 {
630 s->curvalue++;
631 if ( s->itemnames[s->curvalue] == 0 )
632 s->curvalue = 0;
633
634 if ( s->generic.callback )
635 s->generic.callback( s );
636 }
637
638 void SpinControl_DoSlide( menulist_s *s, int dir )
639 {
640 s->curvalue += dir;
641
642 if ( s->curvalue < 0 )
643 s->curvalue = 0;
644 else if ( s->itemnames[s->curvalue] == 0 )
645 s->curvalue--;
646
647 if ( s->generic.callback )
648 s->generic.callback( s );
649 }
650
651 void SpinControl_Draw( menulist_s *s )
652 {
653 char buffer[100];
654
655 if ( s->generic.name )
656 {
657 Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET,
658 s->generic.y + s->generic.parent->y,
659 s->generic.name );
660 }
661 if ( !strchr( s->itemnames[s->curvalue], '\n' ) )
662 {
663 Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->itemnames[s->curvalue] );
664 }
665 else
666 {
667 strcpy( buffer, s->itemnames[s->curvalue] );
668 *strchr( buffer, '\n' ) = 0;
669 Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, buffer );
670 strcpy( buffer, strchr( s->itemnames[s->curvalue], '\n' ) + 1 );
671 Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y + 10, buffer );
672 }
673 }
674
675