1 /*
2  *  This file is part of the XForms library package.
3  *
4  *  XForms is free software; you can redistribute it and/or modify it
5  *  under the terms of the GNU Lesser General Public License as
6  *  published by the Free Software Foundation; either version 2.1, or
7  *  (at your option) any later version.
8  *
9  *  XForms is distributed in the hope that it will be useful, but
10  *  WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public License
15  *  along with XForms.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "include/forms.h"
24 #include "flinternal.h"
25 #include "private/flvasprintf.h"
26 #include <ctype.h>
27 
28 
29 /***************************************
30  * Local global variables
31  ***************************************/
32 
33 /* Head of linked list of popups */
34 
35 static FL_POPUP *popups = NULL;
36 
37 
38 /* Default policy (i.e. does popup get closed when the user releases
39    the mouse button when not on an active entry or not?) */
40 
41 static int popup_policy;
42 
43 
44 /* Default font styles and sizes for title and entries */
45 
46 static int popup_entry_font_style;
47 static int popup_title_font_style;
48 
49 static int popup_entry_font_size;
50 static int popup_title_font_size;
51 
52 
53 /* Default color of popup */
54 
55 static FL_COLOR popup_bg_color;
56 
57 
58 /* Default color of entry the mouse is over (unless disabled) */
59 
60 static FL_COLOR popup_on_color;
61 
62 
63 /* Default color of title text */
64 
65 static FL_COLOR popup_title_color;
66 
67 
68 /* Default color of normal entrys text */
69 
70 static FL_COLOR popup_text_color;
71 
72 
73 /* Default color of entrys text when mouse is on the entry (unless disabled) */
74 
75 static FL_COLOR popup_text_on_color;
76 
77 
78 /* Default color of disabled entrys text */
79 
80 static FL_COLOR popup_text_off_color;
81 
82 
83 /* Default color of button for radio entries */
84 
85 static FL_COLOR popup_radio_color;
86 
87 
88 /* Default popup border width */
89 
90 static int popup_bw;
91 
92 
93 /* Default cursor type to be used */
94 
95 static int popup_cursor;
96 
97 
98 /***************************************
99  * Local functions           *
100  ***************************************/
101 
102 static FL_POPUP_ENTRY *parse_entries( FL_POPUP *, char *, va_list,
103                                       const char *, int );
104 static FL_POPUP_ENTRY * failed_add( FL_POPUP_ENTRY * );
105 static int check_sub( FL_POPUP_ENTRY * );
106 static void radio_check( FL_POPUP_ENTRY * );
107 static void convert_shortcut( const char *, FL_POPUP_ENTRY * );
108 static void recalc_popup( FL_POPUP * );
109 static void title_dimensions( FL_POPUP *, unsigned int *, unsigned int * );
110 static void entry_text_dimensions( FL_POPUP_ENTRY *, unsigned int *,
111                                    unsigned int * );
112 static void draw_popup( FL_POPUP * );
113 static void draw_title( FL_POPUP * );
114 static void draw_entry( FL_POPUP_ENTRY * );
115 static void calculate_window_position( FL_POPUP * );
116 static void create_popup_window( FL_POPUP * );
117 static void grab( FL_POPUP * );
118 static void close_popup( FL_POPUP *, int );
119 static FL_POPUP_RETURN * popup_interaction( FL_POPUP * );
120 static int is_on_popups( FL_POPUP * pooup, int x, int y );
121 static FL_POPUP_RETURN * handle_selection( FL_POPUP_ENTRY * );
122 static FL_POPUP * handle_motion( FL_POPUP *, int, int );
123 static void motion_shift_window( FL_POPUP *, int *, int * );
124 static FL_POPUP * handle_key( FL_POPUP *, XKeyEvent *, FL_POPUP_ENTRY ** );
125 static void key_shift_window( FL_POPUP *, FL_POPUP_ENTRY * );
126 static FL_POPUP * open_subpopup( FL_POPUP_ENTRY * );
127 static FL_POPUP_ENTRY * handle_shortcut( FL_POPUP *, long, unsigned int );
128 static void enter_leave( FL_POPUP_ENTRY *, int );
129 static FL_POPUP * find_popup( int, int );
130 static FL_POPUP_ENTRY * find_entry( FL_POPUP *, int, int );
131 static void setup_subpopups( FL_POPUP * );
132 static char * cleanup_string( char * );
133 static void set_need_recalc( FL_POPUP * );
134 
135 
136 /***************************************
137  * #defines
138  ***************************************/
139 
140 /* Inner padding is used within the title box of popups only */
141 
142 #define INNER_PADDING_LEFT    3
143 #define INNER_PADDING_RIGHT   3
144 #define INNER_PADDING_TOP     3
145 #define INNER_PADDING_BOTTOM  3
146 
147 
148 /* Spacing around title and entries of a popup */
149 
150 #define OUTER_PADDING_LEFT    3
151 #define OUTER_PADDING_RIGHT   3
152 #define OUTER_PADDING_TOP     0
153 #define OUTER_PADDING_BOTTOM  0
154 
155 /* Vert. spacing between symbols (box, hook, triangle) and text in an entry */
156 
157 #define SYMBOL_PADDING        0
158 
159 
160 /* Height of the box for separation line in popup, must be at least 3 */
161 
162 #define LINE_HEIGHT           4
163 
164 
165 /* Extra offsets added by fl_draw_text() that need to be taken into account
166    (see variables 'xoff' and 'yoff' of 'fli_draw_text_inside()' in xtext.c) */
167 
168 #define STR_OFFSET_X    5
169 #define STR_OFFSET_Y    4
170 
171 
172 /* Amount window is shifted in horizontal direction if the popup doesn't fit on
173    the screen in that direction and delay (in usec) between shifts (in both
174    directions) when the user is pushing the mouse at the screen borders */
175 
176 #define WINDOW_SHIFT        ( fl_scrw / 10 )
177 #define WINDOW_SHIFT_DELAY  100000
178 
179 
180 /* Macro for testing if a popup entry can be made "active" (i.e. if it
181    gets highlighted when under the mouse) */
182 
183 #define IS_ACTIVATABLE( e )                                               \
184     (     ( e )->type != FL_POPUP_LINE                                    \
185       && ! ( ( e ) ->state & ( FL_POPUP_HIDDEN | FL_POPUP_DISABLED ) ) )
186 
187 
188 
189 /***************************************
190  * Create a new popup
191  ***************************************/
192 
193 FL_POPUP *
fl_popup_add(Window win,const char * title)194 fl_popup_add( Window       win,
195               const char * title )
196 {
197     return fli_popup_add( win, title, "fl_popup_add" );
198 }
199 
200 
201 /***************************************
202  * Internal function to create a new popup
203  ***************************************/
204 
205 FL_POPUP *
fli_popup_add(Window win,const char * title,const char * caller)206 fli_popup_add( Window       win,
207                const char * title,
208                const char * caller )
209 {
210     FL_POPUP *p;
211 
212     /* Try to get memory for the popup itself and the optional title string */
213 
214     if ( ( p = fl_malloc( sizeof *p ) ) == NULL )
215     {
216         M_err( caller, "Running out of memory" );
217         return NULL;
218     }
219 
220     if ( ! title || ! *title )
221         p->title = NULL;
222     else if ( ( p->title = fl_strdup( title ) ) == NULL )
223     {
224         fl_free( p );
225         M_err( caller, "Running out of memory" );
226         return NULL;
227     }
228 
229     /* Link the new popup into the list of popups */
230 
231     p->next = NULL;
232     if ( popups == NULL )
233     {
234         popups = p;
235         p->prev = NULL;
236     }
237     else
238     {
239         p->prev = popups;
240         while ( p->prev->next != NULL )
241             p->prev = p->prev->next;
242         p->prev->next = p;
243     }
244 
245     p->parent = NULL;
246     p->top_parent = p;         /* points at itself except for sub-popups */
247 
248     p->win = None;
249     p->parent_win = win != None ? win : fl_root;
250     p->cursor     = fli_get_cursor_byname( popup_cursor );
251 
252     p->entries     = NULL;
253     p->callback    = NULL;
254     p->use_req_pos = 0;
255     p->need_recalc = 1;
256     p->min_width   = 0;
257     p->has_subs    = 0;
258     p->has_boxes   = 0;
259     p->counter     = 0;
260     p->policy      = popup_policy;
261 
262     fl_popup_set_title_font( p, popup_title_font_style, popup_title_font_size );
263     fl_popup_entry_set_font( p, popup_entry_font_style, popup_entry_font_size );
264 
265     p->bw               = popup_bw;
266     p->on_color         = popup_on_color;
267     p->bg_color         = popup_bg_color;
268     p->title_color      = popup_title_color;
269     p->text_color       = popup_text_color;
270     p->text_on_color    = popup_text_on_color;
271     p->text_off_color   = popup_text_off_color;
272     p->radio_color      = popup_radio_color;
273 
274     return p;
275 }
276 
277 
278 /***************************************
279  * Add (append) entries to a popup
280  ***************************************/
281 
282 FL_POPUP_ENTRY *
fl_popup_add_entries(FL_POPUP * popup,const char * entries,...)283 fl_popup_add_entries( FL_POPUP   * popup,
284                       const char * entries,
285                       ... )
286 {
287     FL_POPUP_ENTRY *new_entries;
288     va_list ap;
289 
290     va_start( ap, entries );
291     new_entries = fli_popup_add_entries( popup, entries, ap,
292                                          "fl_popup_add_entries", 0 );
293     va_end( ap );
294 
295     return new_entries;
296 }
297 
298 
299 /***************************************
300  * Internal function to add entries
301  ***************************************/
302 
303 FL_POPUP_ENTRY *
fli_popup_add_entries(FL_POPUP * popup,const char * entries,va_list ap,const char * caller,int simple)304 fli_popup_add_entries( FL_POPUP   * popup,
305                        const char * entries,
306                        va_list      ap,
307                        const char * caller,
308                        int          simple )
309 {
310     FL_POPUP_ENTRY *new_entries,
311                    *e;
312     char *str;
313 
314     /* Calling this function with no string for the entries doesn't make
315        sense */
316 
317     if ( entries == NULL )
318     {
319         M_err( caller, "NULL entries argument" );
320         return NULL;
321     }
322 
323     /* Check if the popup we're supposed to add to does exist at all */
324 
325     if ( fli_check_popup_exists( popup ) )
326     {
327         M_err( caller, "Popup does not exist" );
328         return NULL;
329     }
330 
331     /* Now analyze the string with the information about the entries */
332 
333     if ( ( str = fl_strdup( entries ) ) == NULL )
334     {
335         M_err( caller, "Running out of memory" );
336         return NULL;
337     }
338 
339     new_entries = parse_entries( popup, str, ap, caller, simple );
340     fl_free( str );
341 
342     /* A return value of NULL says something went wrong (warning was altready
343        output) */
344 
345     if ( new_entries == NULL )
346         return NULL;
347 
348     /* Now all left to do is append the list of new entries to the list of
349        already existing ones (if there are any) */
350 
351     if ( popup->entries == NULL )
352         popup->entries = new_entries;
353     else
354     {
355         for ( e = popup->entries; e->next != NULL; e = e->next )
356             /* empty */ ;
357         e->next = new_entries;
358         new_entries->prev = e;
359     }
360 
361     /* Make sure all sub-popus are set up correctly */
362 
363     setup_subpopups( popup );
364 
365     /* Set flag that indicates that the dimension of the popup have to be
366        recalculated */
367 
368     set_need_recalc( popup );
369 
370     /* Return a pointer to the first of the newly added entries */
371 
372     return new_entries;
373 }
374 
375 
376 /***************************************
377  * Insert new entries after an entry (use NULL for inserting at the start)
378  ***************************************/
379 
380 FL_POPUP_ENTRY *
fl_popup_insert_entries(FL_POPUP * popup,FL_POPUP_ENTRY * after,const char * entries,...)381 fl_popup_insert_entries( FL_POPUP       * popup,
382                          FL_POPUP_ENTRY * after,
383                          const char     * entries,
384                          ... )
385 {
386     FL_POPUP_ENTRY *new_entries;
387     va_list ap;
388 
389     va_start( ap, entries );
390     new_entries = fli_popup_insert_entries( popup, after, entries, ap,
391                                             "fl_popup_insert_entries", 0 );
392     va_end( ap );
393 
394     return new_entries;
395 }
396 
397 
398 /***************************************
399  * Internal function to insert new entries after an entry
400  ***************************************/
401 
402 FL_POPUP_ENTRY *
fli_popup_insert_entries(FL_POPUP * popup,FL_POPUP_ENTRY * after,const char * entries,va_list ap,const char * caller,int simple)403 fli_popup_insert_entries( FL_POPUP       * popup,
404                           FL_POPUP_ENTRY * after,
405                           const char     * entries,
406                           va_list          ap,
407                           const char     * caller,
408                           int              simple)
409 {
410     FL_POPUP_ENTRY *new_entries,
411                    *new_last,
412                    *e;
413     char *str;
414 
415     if ( after != NULL )
416     {
417         for ( e = popup->entries; e != NULL; e = e->next )
418             if ( e == after )
419                 break;
420 
421         if ( e == NULL )
422         {
423             M_err( caller, "Invalid 'after' argument" );
424             return NULL;
425         }
426     }
427 
428     /* Calling this function with no string for the entries doesn't make
429        sense */
430 
431     if ( entries == NULL )
432     {
433         M_err( caller, "NULL entries argument" );
434         return NULL;
435     }
436 
437     /* Check if the popup we're supposed to add to does exist at all */
438 
439     if ( fli_check_popup_exists( popup ) )
440     {
441         M_err( caller, "Popup does not exist" );
442         return NULL;
443     }
444 
445     /* Now analyze the string with the information about the entries */
446 
447     if ( ( str = fl_strdup( entries ) ) == NULL )
448     {
449         M_err( caller, "Running out of memory" );
450         return NULL;
451     }
452 
453     new_entries = parse_entries( popup, str, ap, "fl_popup_insert_entries",
454                                  simple );
455     fl_free( str );
456 
457     /* A return value of NULL says something went wrong (warning was already
458        output) */
459 
460     if ( new_entries == NULL )
461         return NULL;
462 
463     /* Now all left to do is insert the list of new entries into the list of
464        already existing ones. 'after' being NULL means at the start of the
465        list. */
466 
467     for ( new_last = new_entries;
468           new_last->next != NULL; new_last = new_last->next )
469         /* empty */ ;
470 
471     if ( after == NULL )
472     {
473         if ( popup->entries != NULL )
474         {
475 
476             new_last->next = popup->entries;
477             popup->entries->prev = new_last;
478         }
479 
480         popup->entries = new_entries;
481     }
482     else
483     {
484         if ( after->next )
485             after->next->prev = new_last;
486 
487         new_last->next = after->next;
488 
489         new_entries->prev = after;
490         after->next = new_entries;
491     }
492 
493     /* Make sure all sub-popus are set up correctly */
494 
495     setup_subpopups( popup );
496 
497     /* Set flag that indicates that the dimension of the popup have to be
498        recalculated */
499 
500     set_need_recalc( popup );
501 
502     /* Return a pointer to the first of the newly added entries */
503 
504     return new_entries;
505 }
506 
507 
508 /***************************************
509  * Internal function for inserting entries into a popup after entry
510  * 'after' (NULL stands for "insert at start") from a list of FL_POPUP_ITEM
511  * structures - they are converted into a string that then gets passed
512  * with the required additional arguments to fl_popup_insert_entries()
513  ***************************************/
514 
515 FL_POPUP_ENTRY *
fli_popup_insert_items(FL_POPUP * popup,FL_POPUP_ENTRY * after,FL_POPUP_ITEM * entries,const char * caller)516 fli_popup_insert_items( FL_POPUP       * popup,
517                         FL_POPUP_ENTRY * after,
518                         FL_POPUP_ITEM  * entries,
519                         const char     * caller)
520 {
521     FL_POPUP_ITEM *e;
522     const char *c;
523     char *txt;
524     size_t cnt;
525     static long val = 0;
526     int level = 0;
527     int need_line = 0;
528     int is_sub = 0;
529     FL_POPUP_ENTRY *entry = NULL;
530     int first = 1;
531 
532     /* Return if the array of items is NULL */
533 
534     if ( entries == NULL )
535         return NULL;
536 
537     /* Check if the popup we're supposed to add to does exist at all */
538 
539     if ( fli_check_popup_exists( popup ) )
540     {
541         M_err( caller, "Popup does not exist" );
542         return NULL;
543     }
544 
545     /* If 'after' isn't NULL (indicating that we have to insert at the
546        start) check that this popup entry exists */
547 
548     if ( after != NULL )
549     {
550         for ( entry = popup->entries; entry != NULL; entry = entry->next )
551             if ( entry == after )
552                 break;
553 
554         if ( entry == NULL )
555         {
556             M_err( caller, "Invalid 'after' argument" );
557             return NULL;
558         }
559     }
560 
561     /* Iterate over all items, inserting each individually */
562 
563     for ( e = entries; e->text != NULL; e++ )
564     {
565         /* Check that the type is ok */
566 
567         if (    e->type != FL_POPUP_NORMAL
568              && e->type != FL_POPUP_TOGGLE
569              && e->type != FL_POPUP_RADIO )
570         {
571             M_err( caller, "Invalid entry type" );
572             return NULL;
573         }
574 
575         c = e->text;
576 
577         /* Check for '/' and '_' at the very start of the text */
578 
579         if ( c[ 0 ] == '_' || ( c[ 0 ] == '/' && c[ 1 ] == '_' ) )
580             need_line = 1;
581 
582         if ( c[ 0 ] == '/' || ( c[ 0 ] == '_' && c[ 1 ]  == '/' ) )
583         {
584             if ( e->type != FL_POPUP_NORMAL )
585             {
586                 M_err( caller, "Entry can't be for a sub-popup "
587                        "and toggle or radio entry at the same time" );
588                 return NULL;
589             }
590             is_sub = 1;
591         }
592 
593         if (    ( *c == '/' && *++c == '_' )
594              || ( *c == '_' && *++c == '/' ) )
595             c++;
596 
597         /* Count the number of '%' in the string (without a directly following
598            'S') since all of them need to be escaped */
599 
600         cnt = 0;
601         for ( txt = strchr( c, '%' ); txt != NULL; txt = strchr( ++txt, '%' ) )
602             if ( txt[ 1 ] != 'S' )
603                 cnt++;
604 
605         /* Get enough memory for the string to pass to
606            fl_popup_insert_entries() */
607 
608         if ( ( txt = fl_malloc( strlen( c ) + cnt + 13 ) ) == NULL )
609         {
610             M_err( caller, "Running out of memory" );
611             return NULL;
612         }
613 
614         /* Copy the original text, doubling all '%'s (except those in
615            front of 'S') */
616 
617         for ( cnt = 0; *c != '\0'; c++, cnt++ )
618         {
619             txt[ cnt ] = *c;
620             if ( c[ 0 ] == '%' && c[ 1 ] != 'S' )
621                 txt[ ++cnt ] = '%';
622         }
623 
624         /* Add special sequences passed in every case */
625 
626         memcpy( txt + cnt, "%x%f%s", 6 );
627         cnt += 6;
628 
629         /* Optionally add those for for disabling or hiding the entry */
630 
631         if ( e->state & FL_POPUP_DISABLED )
632         {
633             memcpy( txt + cnt, "%d", 2 );
634             cnt += 2;
635         }
636 
637         if ( e->state & FL_POPUP_HIDDEN )
638         {
639             memcpy( txt + cnt, "%h", 2 );
640             cnt += 2;
641         }
642 
643         txt[ cnt ] = '\0';
644 
645         /* Now we can start creating the entry. To make sure that the
646            value assigned to the entry is correct even when sub-popus
647            are to be created we need to know the level of recursion */
648 
649         level++;
650 
651         if ( need_line )
652         {
653             if ( ( after = fl_popup_insert_entries( popup, after, "%l" ) )
654                                                                       == NULL )
655             {
656                 if ( --level == 0 )
657                     val = 0;
658                 return NULL;
659             }
660 
661             need_line = 0;
662         }
663 
664         if ( e->type == FL_POPUP_NORMAL && ! is_sub )
665         {
666             if ( ( after = fl_popup_insert_entries( popup, after, txt, val++,
667                                                     e->callback, e->shortcut ) )
668                                                                        == NULL )
669             {
670                 fl_free( txt );
671                 if ( --level == 0 )
672                     val = 0;
673                 return NULL;
674             }
675         }
676         else if ( e->type == FL_POPUP_TOGGLE )
677         {
678             strcat( txt, e->state & FL_POPUP_CHECKED  ? "%T" : "%t" );
679 
680             if ( ( after = fl_popup_insert_entries( popup, after, txt, val++,
681                                                     e->callback, e->shortcut ) )
682                                                                        == NULL )
683             {
684                 fl_free( txt );
685                 if ( --level == 0 )
686                     val = 0;
687                 return NULL;
688             }
689         }
690         else if ( e->type == FL_POPUP_RADIO )
691         {
692             strcat( txt, e->state & FL_POPUP_CHECKED ? "%R" : "%r" );
693 
694             if ( ( after = fl_popup_insert_entries( popup, after, txt, val++,
695                                                     e->callback, e->shortcut,
696                                                     INT_MIN ) ) == NULL )
697             {
698                 fl_free( txt );
699                 if ( --level == 0 )
700                     val = 0;
701                 return NULL;
702             }
703         }
704         else if ( is_sub )
705         {
706             FL_POPUP *sub;
707             long pval = val++;
708 
709             strcat( txt, "%m" );
710 
711             if (    ( sub = fl_popup_create( popup->win, NULL, e + 1 ) ) == NULL
712                  || ( after = fl_popup_insert_entries( popup, after,txt, pval,
713                                                        e->callback, e->shortcut,
714                                                        sub ) ) == NULL )
715             {
716                 fl_free( txt );
717                 if ( ! fli_check_popup_exists( sub ) )
718                     fl_popup_delete( sub );
719                 if ( --level == 0 )
720                     val = 0;
721                 return NULL;
722             }
723         }
724 
725         fl_free( txt );
726 
727         /* Set the entries text member to exactly what the user gave us */
728 
729         fli_safe_free( after->text );
730         if ( ( after->text = fl_strdup( e->text ) ) == NULL )
731         {
732             fl_popup_delete( popup );
733             if ( --level == 0 )
734                 val = 0;
735             return NULL;
736         }
737 
738         /* If this was a sub-popup entry skip items that were for the sub- or
739            sub-sub-popus etc. */
740 
741         if ( is_sub )
742         {
743             cnt = 1;
744             while ( cnt > 0 )
745             {
746                 e++;
747 
748                 if ( e->text == NULL )
749                     cnt--;
750                 else if (    e->text[ 0 ] == '/'
751                           || ( e->text[ 0 ] == '_' && e->text[ 1 ] == '/' ) )
752                     cnt++;
753             }
754 
755             is_sub = 0;
756         }
757 
758         if ( first )
759         {
760             entry = after;
761             first = 0;
762         }
763     }
764 
765     val++;
766     if ( --level == 0 )
767         val = 0;
768 
769     return entry;
770 }
771 
772 
773 /***************************************
774  * Create a popup and populate it from a list of FL_POPUP_ITEM structures
775  ***************************************/
776 
777 FL_POPUP *
fl_popup_create(Window win,const char * title,FL_POPUP_ITEM * entries)778 fl_popup_create( Window          win,
779                  const char    * title,
780                  FL_POPUP_ITEM * entries )
781 {
782     FL_POPUP *popup;
783 
784     if ( ( popup = fl_popup_add( win, title ) ) == NULL )
785         return NULL;
786 
787     if ( fli_popup_insert_items( popup, NULL, entries,
788                                  "fl_popup_create" ) == NULL )
789     {
790         fl_popup_delete( popup );
791         return NULL;
792     }
793 
794     return popup;
795 }
796 
797 
798 /***************************************
799  * Appends entries to a popup from a list of FL_POPUP_ITEM structures
800  ***************************************/
801 
802 FL_POPUP_ENTRY *
fl_popup_add_items(FL_POPUP * popup,FL_POPUP_ITEM * entries)803 fl_popup_add_items( FL_POPUP      * popup,
804                     FL_POPUP_ITEM * entries )
805 {
806     FL_POPUP_ENTRY *after;
807 
808     /* Return if the array of items is NULL */
809 
810     if ( entries == NULL )
811         return NULL;
812 
813     /* Check if the popup we're supposed to add to does exist at all */
814 
815     if ( fli_check_popup_exists( popup ) )
816     {
817         M_err( "fl_popup_add_items", "Popup does not exist" );
818         return NULL;
819     }
820 
821     /* Determine the last existing entry in the popup */
822 
823     after = popup->entries;
824     while ( after != NULL && after->next != NULL )
825         after = after->next;
826 
827     return fli_popup_insert_items( popup, after, entries,
828                                    "fl_popup_add_items" );
829 }
830 
831 
832 /***************************************
833  * Insert entries into a popup from a list of FL_POPUP_ITEM structures
834  * after an entry
835  ***************************************/
836 
837 FL_POPUP_ENTRY *
fl_popup_insert_items(FL_POPUP * popup,FL_POPUP_ENTRY * after,FL_POPUP_ITEM * entries)838 fl_popup_insert_items( FL_POPUP       * popup,
839                        FL_POPUP_ENTRY * after,
840                        FL_POPUP_ITEM  * entries )
841 {
842     return fli_popup_insert_items( popup, after, entries,
843                                    "fl_popup_insert_items" );
844 }
845 
846 
847 /***************************************
848  * Removes a popup, returns 0 on success and -1 on failure.
849  ***************************************/
850 
851 int
fl_popup_delete(FL_POPUP * popup)852 fl_popup_delete( FL_POPUP * popup )
853 {
854     /* Check if the popup we're asked to delete does exist */
855 
856     if ( fli_check_popup_exists( popup ) )
857     {
858         M_err( "fl_popup_delete", "Popup does not exist" );
859         return -1;
860     }
861 
862     /* Don't delete a popup that's currently shown */
863 
864     if ( popup->win != None )
865     {
866         M_err( "fl_popup_delete", "Can't free popup that is still shown" );
867         return -1;
868     }
869 
870     /* Remove all entries (including sub-popups refered to there) */
871 
872     while ( popup->entries )
873         fl_popup_entry_delete( popup->entries );
874 
875     /* Get rid of the title string (if it exists) */
876 
877     fli_safe_free( popup->title );
878 
879     /* Finally unlink it from the list */
880 
881     if ( popup->prev != NULL )
882         popup->prev->next = popup->next;
883     else
884         popups = popup->next;
885 
886     if ( popup->next != NULL )
887         popup->next->prev = popup->prev;
888 
889     fl_free( popup );
890 
891     return 0;
892 }
893 
894 
895 /***************************************
896  * Delete a single popup entry (if it's a sub-popup entry also delete
897  * the popup it refers to)
898  ***************************************/
899 
900 int
fl_popup_entry_delete(FL_POPUP_ENTRY * entry)901 fl_popup_entry_delete( FL_POPUP_ENTRY * entry )
902 {
903     if ( entry == NULL )
904     {
905         M_err( "fl_popup_entry_delete", "Invalid argument" );
906         return -1;
907     }
908 
909     /* We don't remove entries that are shown at the moment */
910 
911     if ( entry->popup->win != None )
912     {
913         M_err( "fl_popup_entry_delete", "Can't free entry of a popup that is "
914                 "shown" );
915         return -1;
916     }
917 
918     /* Remove the entry from the entry list of the popup it belongs to */
919 
920     if ( entry->prev == NULL )
921         entry->popup->entries = entry->next;
922     else
923         entry->prev->next = entry->next;
924 
925     if ( entry->next != NULL )
926         entry->next->prev = entry->prev;
927 
928     entry->popup->need_recalc = 1;
929 
930     /* Free all remaining memory used by entry */
931 
932     fli_safe_free( entry->text );
933     fli_safe_free( entry->label );
934     fli_safe_free( entry->accel );
935     fli_safe_free( entry->shortcut );
936 
937     /* For entries that refer to sub-popups delete the sub-popup */
938 
939     if ( entry->type == FL_POPUP_SUB )
940         fl_popup_delete( entry->sub );
941 
942     return 0;
943 }
944 
945 
946 /***************************************
947  * Do interaction with a popup
948  ***************************************/
949 
950 FL_POPUP_RETURN *
fl_popup_do(FL_POPUP * popup)951 fl_popup_do( FL_POPUP * popup )
952 {
953     /* Check that the popup exists */
954 
955     if ( fli_check_popup_exists( popup ) )
956     {
957         M_err( "fl_popup_do", "Invalid popup" );
958         return NULL;
959     }
960 
961     /* We don't interact directly with sub-popups */
962 
963     if ( popup->parent != NULL )
964     {
965         M_err( "fl_popup_do", "Can't do direct interaction with sub-popup" );
966         return NULL;
967     }
968 
969     draw_popup( popup );
970 
971     return popup_interaction( popup );
972 }
973 
974 
975 /***************************************
976  * Set position where the popup is supposed to appear (if
977  * never called the popup appears at the mouse position)
978  ***************************************/
979 
980 void
fl_popup_set_position(FL_POPUP * popup,int x,int y)981 fl_popup_set_position( FL_POPUP * popup,
982                        int        x,
983                        int        y )
984 {
985     if ( fli_check_popup_exists( popup ) )
986     {
987         M_err( "fl_popup_set_position", "Invalid popup" );
988         return;
989     }
990 
991     popup->use_req_pos = 1;
992     popup->req_x = x;
993     popup->req_y = y;
994 }
995 
996 
997 /***************************************
998  * Returns the policy set for a popup or the default policy if called
999  * with a NULL pointer
1000  ***************************************/
1001 
1002 int
fl_popup_get_policy(FL_POPUP * popup)1003 fl_popup_get_policy( FL_POPUP * popup )
1004 {
1005     if ( popup == NULL )
1006         return popup_policy;
1007 
1008     if ( fli_check_popup_exists( popup ) )
1009     {
1010         M_err( "fl_popup_get_title_font", "Invalid popup" );
1011         return -1;
1012     }
1013 
1014     return popup->top_parent->policy;
1015 }
1016 
1017 
1018 /***************************************
1019  * Set policy of handling the popup (i.e. does it get closed when the
1020  * user releases the mouse button outside an active entry or not?)
1021  ***************************************/
1022 
1023 int
fl_popup_set_policy(FL_POPUP * popup,int policy)1024 fl_popup_set_policy( FL_POPUP * popup,
1025                      int        policy )
1026 {
1027     int old_policy;
1028 
1029     if ( policy < FL_POPUP_NORMAL_SELECT || policy > FL_POPUP_DRAG_SELECT )
1030     {
1031         M_err( "fl_popup_set_policy", "Invalid policy argument" );
1032         return -1;
1033     }
1034 
1035     if ( popup == NULL )
1036     {
1037         old_policy = popup_policy;
1038         popup_policy = policy;
1039         return old_policy;
1040     }
1041 
1042     if ( fli_check_popup_exists( popup ) )
1043     {
1044         M_err( "fl_popup_set_policy", "Invalid popup" );
1045         return -1;
1046     }
1047 
1048     old_policy = popup->policy;
1049     popup->policy = policy;
1050     return old_policy;
1051 }
1052 
1053 
1054 /***************************************
1055  * Set new text for an entry. Returns 0 on succes and -1 on failure.
1056  ***************************************/
1057 
1058 int
fl_popup_entry_set_text(FL_POPUP_ENTRY * entry,const char * text)1059 fl_popup_entry_set_text( FL_POPUP_ENTRY * entry,
1060                          const char     * text )
1061 {
1062     char *t,
1063          *label,
1064          *accel,
1065          sc_str[ 2 ];
1066     int ret = -1;
1067     long *old_sc;
1068 
1069 
1070     if ( fli_check_popup_entry_exists( entry ) )
1071     {
1072         M_err( "fl_popup_entry_set_text", "Invalid entry argument" );
1073         return -1;
1074     }
1075 
1076     /* If the new text is NULL (not very useful;-) we're already done */
1077 
1078     if ( text == NULL )
1079     {
1080         M_err( "fl_popup_entry_set_text", "Invalid text argument" );
1081         return -1;
1082     }
1083 
1084     /* Get rid  of the old text, label and accelerator strings */
1085 
1086     fli_safe_free( entry->text );
1087     fli_safe_free( entry->label );
1088     fli_safe_free( entry->accel );
1089 
1090     /* Make two copies of the text, the first one for storing in the entry,
1091        the second one for creating the label the accelerator string */
1092 
1093     if ( ( t = fl_strdup( text ) ) == NULL )
1094         goto REPLACE_DONE;
1095 
1096     if ( ( entry->text = fl_strdup( text ) ) == NULL )
1097         goto REPLACE_DONE;
1098 
1099     /* Split up the text at the first '%S' */
1100 
1101     label = t;
1102     if ( ( accel = strstr( t, "%S" ) ) != NULL )
1103     {
1104         *accel = '\0';
1105         accel += 2;
1106     }
1107 
1108     /* Remove all backspace characters and replace tabs by spaces in both the
1109        label and the accelerator string */
1110 
1111     cleanup_string( label );
1112     cleanup_string( accel );
1113 
1114     /* Finally set up the label and accel members of the structure */
1115 
1116     if ( ! *label )
1117         entry->label = NULL;
1118     else if ( ( entry->label = fl_strdup( label ) ) == NULL )
1119          goto REPLACE_DONE;
1120 
1121     if ( ! accel || ! *accel )
1122         entry->accel = NULL;
1123     else if ( ( entry->accel = fl_strdup( accel ) ) == NULL )
1124         goto REPLACE_DONE;
1125 
1126     ret = 0;
1127 
1128  REPLACE_DONE:
1129 
1130     fli_safe_free( t );
1131 
1132     if ( ret == -1 )
1133     {
1134         fli_safe_free( entry->text );
1135         fli_safe_free( entry->label );
1136         fli_safe_free( entry->accel );
1137         M_err( "fl_popup_entry_set_text", "Running out of memory" );
1138     }
1139 
1140     for ( old_sc = entry->shortcut; *old_sc != 0; old_sc++ )
1141         if (    ( *old_sc & ~ ( FL_CONTROL_MASK | FL_ALT_MASK ) ) > 0
1142              && ( *old_sc & ~ ( FL_CONTROL_MASK | FL_ALT_MASK ) ) <= 0xFF )
1143         {
1144             sc_str[ 0 ] = *old_sc & ~ ( FL_CONTROL_MASK | FL_ALT_MASK );
1145             sc_str[ 1 ] = '\0';
1146             convert_shortcut( sc_str, entry );
1147             break;
1148         }
1149 
1150     entry->popup->need_recalc = 1;
1151 
1152     return 0;
1153 }
1154 
1155 
1156 /***************************************
1157  * Set new shortcuts for an entry
1158  ***************************************/
1159 
1160 void
fl_popup_entry_set_shortcut(FL_POPUP_ENTRY * entry,const char * sc)1161 fl_popup_entry_set_shortcut( FL_POPUP_ENTRY * entry,
1162                              const char     * sc )
1163 {
1164     if ( fli_check_popup_entry_exists( entry ) )
1165     {
1166         M_err( "fl_popup_entry_set_shortcut", "Invalid entry argument" );
1167         return;
1168     }
1169 
1170     fli_safe_free( entry->shortcut );
1171 
1172     if ( sc == NULL )
1173         entry->ulpos = -1;
1174     else
1175         convert_shortcut( sc, entry );
1176 }
1177 
1178 
1179 /***************************************
1180  * Set callback function for a popup
1181  ***************************************/
1182 
1183 FL_POPUP_CB
fl_popup_set_callback(FL_POPUP * popup,FL_POPUP_CB callback)1184 fl_popup_set_callback( FL_POPUP    * popup,
1185                        FL_POPUP_CB   callback )
1186 {
1187     FL_POPUP_CB old_cb;
1188 
1189     if ( fli_check_popup_exists( popup ) )
1190     {
1191         M_err( "fl_popup_set_callback", "Invalid popup" );
1192         return NULL;
1193     }
1194 
1195     old_cb = popup->callback;
1196     popup->callback = callback;
1197     return old_cb;
1198 }
1199 
1200 
1201 /***************************************
1202  * Set a new title for a popup
1203  ***************************************/
1204 
1205 const char *
fl_popup_get_title(FL_POPUP * popup)1206 fl_popup_get_title( FL_POPUP * popup )
1207 {
1208     if ( fli_check_popup_exists( popup ) )
1209     {
1210         M_err( "fl_popup_set_title", "Invalid popup" );
1211         return NULL;
1212     }
1213 
1214     return popup->title;
1215 }
1216 
1217 
1218 /***************************************
1219  * Set a new title for a popup
1220  ***************************************/
1221 
1222 FL_POPUP *
fl_popup_set_title(FL_POPUP * popup,const char * title)1223 fl_popup_set_title( FL_POPUP   * popup,
1224                     const char * title )
1225 {
1226     if ( fli_check_popup_exists( popup ) )
1227     {
1228         M_err( "fl_popup_set_title", "Invalid popup" );
1229         return NULL;
1230     }
1231 
1232     fli_safe_free( popup->title );
1233 
1234     if ( title && *title )
1235     {
1236         popup->title = fl_strdup( title );
1237         if ( popup->title == NULL )
1238         {
1239             M_err( "fl_popup_set_title", "Running out of memory" );
1240             return NULL;
1241         }
1242     }
1243 
1244     popup->need_recalc = 1;
1245 
1246     return popup;
1247 }
1248 
1249 
1250 /***************************************
1251  * Set a new title for a popup
1252  ***************************************/
1253 
1254 FL_POPUP *
fl_popup_set_title_f(FL_POPUP * popup,const char * fmt,...)1255 fl_popup_set_title_f( FL_POPUP   * popup,
1256                       const char * fmt,
1257                       ... )
1258 {
1259     char *buf;
1260     FL_POPUP *p;
1261 
1262     EXPAND_FORMAT_STRING( buf, fmt );
1263     p = fl_popup_set_title( popup, buf );
1264     fl_free( buf );
1265     return p;
1266 }
1267 
1268 
1269 /***************************************
1270  * Return title font for a popup
1271  ***************************************/
1272 
1273 void
fl_popup_get_title_font(FL_POPUP * popup,int * style,int * size)1274 fl_popup_get_title_font( FL_POPUP * popup,
1275                          int      * style,
1276                          int      * size )
1277 {
1278     if ( popup == NULL )
1279     {
1280         if ( style != NULL )
1281             *style = popup_title_font_style;
1282         if ( size != NULL )
1283             *size  = popup_title_font_size;
1284         return;
1285     }
1286 
1287     if ( fli_check_popup_exists( popup ) )
1288     {
1289         M_err( "fl_popup_get_title_font", "Invalid popup" );
1290         return;
1291     }
1292 
1293     if ( style != NULL )
1294         *style = popup->top_parent->title_font_style;
1295     if ( size != NULL )
1296         *size  = popup->top_parent->title_font_size ;
1297 }
1298 
1299 
1300 /***************************************
1301  * Set title font for a popup (or change the default if called with NULL)
1302  ***************************************/
1303 
1304 void
fl_popup_set_title_font(FL_POPUP * popup,int style,int size)1305 fl_popup_set_title_font( FL_POPUP * popup,
1306                          int        style,
1307                          int        size )
1308 {
1309     if ( popup == NULL )
1310     {
1311         popup_title_font_style = style;
1312         popup_title_font_size  = size;
1313         return;
1314     }
1315 
1316     if ( fli_check_popup_exists( popup ) )
1317     {
1318         M_err( "fl_popup_set_title_font", "Invalid popup" );
1319         return;
1320     }
1321 
1322     popup->title_font_style = style;
1323     popup->title_font_size  = size;
1324 
1325     if ( popup->parent == NULL )
1326         set_need_recalc( popup );
1327 }
1328 
1329 
1330 /***************************************
1331  * Return entry font for a popup
1332  ***************************************/
1333 
1334 void
fl_popup_entry_get_font(FL_POPUP * popup,int * style,int * size)1335 fl_popup_entry_get_font( FL_POPUP * popup,
1336                          int      * style,
1337                          int      * size )
1338 {
1339     if ( popup == NULL )
1340     {
1341         if ( style != NULL )
1342             *style = popup_entry_font_style;
1343         if ( size != NULL )
1344             *size  = popup_entry_font_size;
1345         return;
1346     }
1347 
1348     if ( fli_check_popup_exists( popup ) )
1349     {
1350         M_err( "fl_popup_entry_get_font", "Invalid popup" );
1351         return;
1352     }
1353 
1354     if ( style != NULL )
1355         *style = popup->top_parent->entry_font_style;
1356     if ( size != NULL )
1357         *size  = popup->top_parent->entry_font_size ;
1358 }
1359 
1360 
1361 /***************************************
1362  * Set entry font for a popup (or change the default if called with NULL)
1363  ***************************************/
1364 
1365 void
fl_popup_entry_set_font(FL_POPUP * popup,int style,int size)1366 fl_popup_entry_set_font( FL_POPUP * popup,
1367                          int        style,
1368                          int        size )
1369 {
1370     if ( popup == NULL )
1371     {
1372         popup_entry_font_style = style;
1373         popup_entry_font_size  = size;
1374         return;
1375     }
1376 
1377     if ( fli_check_popup_exists( popup ) )
1378     {
1379         M_err( "fl_popup_entry_set_font", "Invalid popup" );
1380         return;
1381     }
1382 
1383     popup->entry_font_style = style;
1384     popup->entry_font_size  = size;
1385 
1386     if ( popup->parent == NULL )
1387         set_need_recalc( popup );
1388 }
1389 
1390 
1391 /***************************************
1392  * Return the border width of a popup (or the default border width if
1393  * called with NULL or an invalid argument)
1394  ***************************************/
1395 
1396 int
fl_popup_get_bw(FL_POPUP * popup)1397 fl_popup_get_bw( FL_POPUP * popup )
1398 {
1399     if ( popup == NULL )
1400         return popup_bw;
1401 
1402     if ( fli_check_popup_exists( popup ) )
1403     {
1404         M_err( "fl_popup_get_bw", "Invalid argument" );
1405         return popup_bw;
1406     }
1407 
1408     return popup->top_parent->bw;
1409 }
1410 
1411 
1412 /***************************************
1413  * Sets the border width of a popup or the default border width (if called
1414  * with NULL)
1415  ***************************************/
1416 
1417 int
fl_popup_set_bw(FL_POPUP * popup,int bw)1418 fl_popup_set_bw( FL_POPUP * popup,
1419                  int        bw )
1420 {
1421     int old_bw;
1422 
1423     /* Clamp border width to a reasonable range */
1424 
1425     if ( bw == 0 || FL_abs( bw ) > FL_MAX_BW )
1426     {
1427         bw = bw == 0 ? -1 : ( bw > 0 ? FL_MAX_BW : - FL_MAX_BW );
1428         M_warn( "fl_popup_set_bw", "Adjusting invalid border width to %d",
1429                 bw );
1430     }
1431 
1432     if ( popup == NULL )
1433     {
1434         old_bw = popup_bw;
1435         popup_bw = bw;
1436         return old_bw;
1437     }
1438 
1439     if ( fli_check_popup_exists( popup ) )
1440     {
1441         M_err( "fl_popup_set_bw", "Invalid popup argument" );
1442         return INT_MIN;
1443     }
1444 
1445     old_bw = bw;
1446     popup->bw = bw;
1447 
1448     if ( popup->parent == NULL )
1449         set_need_recalc( popup );
1450 
1451     return old_bw;
1452 }
1453 
1454 
1455 /***************************************
1456  * Get one of the colors of the popup (NULL or invalid popup returns the
1457  * default settings)
1458  ***************************************/
1459 
1460 FL_COLOR
fl_popup_get_color(FL_POPUP * popup,int color_type)1461 fl_popup_get_color( FL_POPUP * popup,
1462                     int        color_type )
1463 {
1464     /* Check if the popup exists */
1465 
1466     if ( popup != NULL && fli_check_popup_exists( popup ) )
1467     {
1468         M_err( "fl_popup_get_color", "Invalid popup argument" );
1469         popup = NULL;
1470     }
1471 
1472     /* For sub-popups always return the color of the controlling popup */
1473 
1474     if ( popup != NULL )
1475         popup = popup->top_parent;
1476 
1477     switch ( color_type )
1478     {
1479         case FL_POPUP_BACKGROUND_COLOR :
1480             return popup ? popup->bg_color : popup_bg_color;
1481 
1482         case FL_POPUP_HIGHLIGHT_COLOR :
1483             return popup ? popup->on_color : popup_on_color;
1484 
1485         case FL_POPUP_TITLE_COLOR :
1486             return popup ? popup->title_color : popup_title_color;
1487 
1488         case FL_POPUP_TEXT_COLOR :
1489             return popup ? popup->text_color : popup_text_color;
1490 
1491         case FL_POPUP_HIGHLIGHT_TEXT_COLOR :
1492             return popup ? popup->text_on_color : popup_text_on_color;
1493 
1494         case FL_POPUP_DISABLED_TEXT_COLOR :
1495             return popup ? popup->text_off_color : popup_text_off_color;
1496 
1497         case FL_POPUP_RADIO_COLOR :
1498             return popup ? popup->radio_color : popup_radio_color;
1499 
1500     }
1501 
1502     M_err( "fl_popup_get_color", "Invalid color type argument" );
1503     return FL_BLACK;
1504 }
1505 
1506 
1507 /***************************************
1508  * Set one of the colors of the popup (NULL sets the default)
1509  ***************************************/
1510 
1511 FL_COLOR
fl_popup_set_color(FL_POPUP * popup,int color_type,FL_COLOR color)1512 fl_popup_set_color( FL_POPUP * popup,
1513                     int        color_type,
1514                     FL_COLOR   color )
1515 {
1516     FL_COLOR old_color;
1517 
1518     /* Check if the popup exists */
1519 
1520     if ( popup != NULL && fli_check_popup_exists( popup ) )
1521     {
1522         M_err( "fl_popup_set_color", "Invalid popup argument" );
1523         return FL_MAX_COLORS;
1524     }
1525 
1526     if ( color >= FL_MAX_COLORS )
1527     {
1528         M_err( "fl_popup_set_color", "Invalid color argument" );
1529         return FL_MAX_COLORS;
1530     }
1531 
1532     switch ( color_type )
1533     {
1534         case FL_POPUP_BACKGROUND_COLOR :
1535             if ( popup != NULL )
1536             {
1537                 old_color = popup->bg_color;
1538                 popup->bg_color = color;
1539             }
1540             else
1541             {
1542                 old_color = popup_bg_color;
1543                 popup_bg_color = color;
1544             }
1545             break;
1546 
1547         case FL_POPUP_HIGHLIGHT_COLOR :
1548             if ( popup != NULL )
1549             {
1550                 old_color = popup->on_color;
1551                 popup->on_color = color;
1552             }
1553             else
1554             {
1555                 old_color = popup_on_color;
1556                 popup_on_color = color;
1557             }
1558             break;
1559 
1560         case FL_POPUP_TITLE_COLOR :
1561             if ( popup != NULL )
1562             {
1563                 old_color = popup->title_color;
1564                 popup->title_color = color;
1565             }
1566             else
1567             {
1568                 old_color = popup_title_color;
1569                 popup_title_color = color;
1570             }
1571             break;
1572 
1573         case FL_POPUP_TEXT_COLOR :
1574             if ( popup != NULL )
1575             {
1576                 old_color = popup->text_color;
1577                 popup->text_color = color;
1578             }
1579             else
1580             {
1581                 old_color = popup_text_color;
1582                 popup_text_color = color;
1583             }
1584             break;
1585 
1586         case FL_POPUP_HIGHLIGHT_TEXT_COLOR :
1587             if ( popup != NULL )
1588             {
1589                 old_color = popup->text_on_color;
1590                 popup->text_on_color = color;
1591             }
1592             else
1593             {
1594                 old_color = popup_text_on_color;
1595                 popup_text_on_color = color;
1596             }
1597             break;
1598 
1599         case FL_POPUP_DISABLED_TEXT_COLOR :
1600             if ( popup != NULL )
1601             {
1602                 old_color = popup->text_off_color;
1603                 popup->text_off_color = color;
1604             }
1605             else
1606             {
1607                 old_color = popup_text_off_color;
1608                 popup_text_off_color = color;
1609             }
1610             break;
1611 
1612         case FL_POPUP_RADIO_COLOR :
1613             if ( popup != NULL )
1614             {
1615                 old_color = popup->radio_color;
1616                 popup->radio_color = color;
1617             }
1618             else
1619             {
1620                 old_color = popup_radio_color;
1621                 popup_radio_color = color;
1622             }
1623             break;
1624 
1625         default :
1626             M_err( "fl_popup_set_color", "Invalid color type argument" );
1627             return FL_MAX_COLORS;
1628     }
1629 
1630     return old_color;
1631 }
1632 
1633 
1634 /***************************************
1635  * Sets the cursor of a popup (or change the
1636  * default cursor if called with NULL)
1637  ***************************************/
1638 
1639 void
fl_popup_set_cursor(FL_POPUP * popup,int cursor)1640 fl_popup_set_cursor( FL_POPUP * popup,
1641                      int        cursor )
1642 {
1643     if ( popup == NULL )
1644     {
1645         popup_cursor = fli_get_cursor_byname( cursor );
1646         return;
1647     }
1648 
1649     if ( fli_check_popup_exists( popup ) )
1650     {
1651         M_err( "fl_popup_set_cursor", "Invalid popup argument" );
1652         return;
1653     }
1654 
1655     popup->cursor =  fli_get_cursor_byname( cursor );
1656 
1657     if ( popup->win )
1658         XDefineCursor( flx->display, popup->win, popup->cursor );
1659 }
1660 
1661 
1662 /***************************************
1663  * Set selection callback function for a popup entry
1664  ***************************************/
1665 
1666 FL_POPUP_CB
fl_popup_entry_set_callback(FL_POPUP_ENTRY * entry,FL_POPUP_CB callback)1667 fl_popup_entry_set_callback( FL_POPUP_ENTRY * entry,
1668                              FL_POPUP_CB      callback )
1669 {
1670     FL_POPUP_CB old_cb;
1671 
1672     if ( fli_check_popup_entry_exists( entry ) )
1673     {
1674         M_err( "fl_popup_entry_set_enter_callback", "Invalid entry argument" );
1675         return NULL;
1676     }
1677 
1678     old_cb = entry->callback;
1679     entry->callback = callback;
1680     return old_cb;
1681 }
1682 
1683 
1684 /***************************************
1685  * Set enter callback function for a popup entry
1686  ***************************************/
1687 
1688 FL_POPUP_CB
fl_popup_entry_set_enter_callback(FL_POPUP_ENTRY * entry,FL_POPUP_CB callback)1689 fl_popup_entry_set_enter_callback( FL_POPUP_ENTRY * entry,
1690                                    FL_POPUP_CB      callback )
1691 {
1692     FL_POPUP_CB old_cb;
1693 
1694     if ( fli_check_popup_entry_exists( entry ) )
1695     {
1696         M_err( "fl_popup_entry_set_enter_callback", "Invalid entry argument" );
1697         return NULL;
1698     }
1699 
1700     old_cb = entry->enter_callback;
1701     entry->enter_callback = callback;
1702     return old_cb;
1703 }
1704 
1705 
1706 /***************************************
1707  * Set leave callback function for a popup entry
1708  ***************************************/
1709 
1710 FL_POPUP_CB
fl_popup_entry_set_leave_callback(FL_POPUP_ENTRY * entry,FL_POPUP_CB callback)1711 fl_popup_entry_set_leave_callback( FL_POPUP_ENTRY * entry,
1712                                    FL_POPUP_CB      callback )
1713 {
1714     FL_POPUP_CB old_cb;
1715 
1716     if ( fli_check_popup_entry_exists( entry ) )
1717     {
1718         M_err( "fl_popup_entry_set_leave_callback", "Invalid entry argument" );
1719         return NULL;
1720     }
1721 
1722     old_cb = entry->leave_callback;
1723     entry->leave_callback = callback;
1724     return old_cb;
1725 }
1726 
1727 
1728 /***************************************
1729  * Get state (disabled, hidden, checked) of a popup entry
1730  ***************************************/
1731 
1732 unsigned int
fl_popup_entry_get_state(FL_POPUP_ENTRY * entry)1733 fl_popup_entry_get_state( FL_POPUP_ENTRY * entry )
1734 {
1735     if ( fli_check_popup_entry_exists( entry ) )
1736     {
1737         M_err( "fl_popup_entry_get_state", "Invalid entry argument" );
1738         return UINT_MAX;
1739     }
1740 
1741     return entry->state;
1742 }
1743 
1744 
1745 /***************************************
1746  * Set state (disabled, hidden, checked) of a popup entry
1747  ***************************************/
1748 
1749 unsigned int
fl_popup_entry_set_state(FL_POPUP_ENTRY * entry,unsigned int state)1750 fl_popup_entry_set_state( FL_POPUP_ENTRY * entry,
1751                           unsigned int     state )
1752 {
1753     unsigned int old_state;
1754 
1755     if ( fli_check_popup_entry_exists( entry ) )
1756     {
1757         M_err( "fl_popup_entry_set_state", "Invalid entry argument" );
1758         return UINT_MAX;
1759     }
1760 
1761     /* Nothing to be done if new and old state are identical */
1762 
1763     if ( entry->state == state )
1764         return state;
1765 
1766     old_state = entry->state;
1767     entry->state = state;
1768 
1769     /* If the entry gets disabled or hidden we may have to switch off it's
1770        "activated" state - don't call leave callback in this case */
1771 
1772     if ( entry->state & ( FL_POPUP_DISABLED | FL_POPUP_HIDDEN ) )
1773         entry->is_act = 0;
1774 
1775     /* If a radio entry gets checked uncheck all radio other entries belonging
1776        to the same group in the popup */
1777 
1778     if ( entry->type == FL_POPUP_RADIO && state & FL_POPUP_CHECKED )
1779     {
1780         FL_POPUP_ENTRY *e;
1781 
1782         for ( e = entry->popup->entries; e != NULL; e = e->next )
1783             if (    e->type == FL_POPUP_RADIO
1784                  && entry->group == e->group
1785                  && entry != e )
1786                 e->state &= ~ FL_POPUP_CHECKED;
1787     }
1788 
1789     /* If the entry was hidden or made visible again the dimensions of the
1790        popup need to be recalculated */
1791 
1792     if ( ( old_state & FL_POPUP_HIDDEN ) ^ ( state & FL_POPUP_HIDDEN ) )
1793          entry->popup->need_recalc = 1;
1794 
1795     /* If the popup the entry belongs to is visible redraw the popup */
1796 
1797     if ( entry->popup->win != None )
1798         draw_popup( entry->popup );
1799 
1800     return old_state;
1801 }
1802 
1803 
1804 /***************************************
1805  * Clear certain bits of an entries state
1806  ***************************************/
1807 
1808 unsigned int
fl_popup_entry_clear_state(FL_POPUP_ENTRY * entry,unsigned int what)1809 fl_popup_entry_clear_state( FL_POPUP_ENTRY * entry,
1810                             unsigned int     what )
1811 {
1812     unsigned int old_state;
1813     size_t i;
1814     unsigned int flags[ ] = { FL_POPUP_DISABLED,
1815                               FL_POPUP_HIDDEN,
1816                               FL_POPUP_CHECKED };
1817 
1818     if ( fli_check_popup_entry_exists( entry ) )
1819     {
1820         M_err( "fl_popup_entry_clear_state", "Invalid entry argument" );
1821         return UINT_MAX;
1822     }
1823 
1824     old_state = entry->state;
1825 
1826     for ( i = 0; i < sizeof flags / sizeof *flags; i++ )
1827         if ( what & flags[ i ] )
1828             fl_popup_entry_set_state( entry, entry->state & ~ flags[ i ] );
1829 
1830     return old_state;
1831 }
1832 
1833 
1834 /***************************************
1835  * Set certain bits of an entrys state
1836  ***************************************/
1837 
1838 unsigned int
fl_popup_entry_raise_state(FL_POPUP_ENTRY * entry,unsigned int what)1839 fl_popup_entry_raise_state( FL_POPUP_ENTRY * entry,
1840                             unsigned int     what )
1841 {
1842     unsigned int old_state;
1843     size_t i;
1844     unsigned int flags[ ] = { FL_POPUP_DISABLED,
1845                               FL_POPUP_HIDDEN,
1846                               FL_POPUP_CHECKED };
1847 
1848     if ( fli_check_popup_entry_exists( entry ) )
1849     {
1850         M_err( "fl_popup_entry_raise_state", "Invalid entry argument" );
1851         return UINT_MAX;
1852     }
1853 
1854     old_state = entry->state;
1855 
1856     for ( i = 0; i < sizeof flags / sizeof *flags; i++ )
1857         if ( what & flags[ i ] )
1858             fl_popup_entry_set_state( entry, entry->state | flags[ i ] );
1859 
1860     return old_state;
1861 }
1862 
1863 
1864 /***************************************
1865  * Toggle certain bits of an entries state
1866  ***************************************/
1867 
1868 unsigned int
fl_popup_entry_toggle_state(FL_POPUP_ENTRY * entry,unsigned int what)1869 fl_popup_entry_toggle_state( FL_POPUP_ENTRY * entry,
1870                              unsigned int     what )
1871 {
1872     unsigned int old_state;
1873     size_t i;
1874     unsigned int flags[ ] = { FL_POPUP_DISABLED,
1875                               FL_POPUP_HIDDEN,
1876                               FL_POPUP_CHECKED };
1877 
1878     if ( fli_check_popup_entry_exists( entry ) )
1879     {
1880         M_err( "fl_popup_entry_toggle_state", "Invalid entry argument" );
1881         return UINT_MAX;
1882     }
1883 
1884     old_state = entry->state;
1885 
1886     for ( i = 0; i < sizeof flags / sizeof *flags; i++ )
1887         if ( what & flags[ i ] )
1888             fl_popup_entry_set_state( entry, entry->state ^ flags[ i ] );
1889 
1890     return old_state;
1891 }
1892 
1893 
1894 /***************************************
1895  * Set the value associated with an entry
1896  ***************************************/
1897 
1898 long
fl_popup_entry_set_value(FL_POPUP_ENTRY * entry,long value)1899 fl_popup_entry_set_value( FL_POPUP_ENTRY * entry,
1900                           long             value )
1901 {
1902     long old_val;
1903 
1904     if ( fli_check_popup_entry_exists( entry ) )
1905     {
1906         M_err( "fl_popup_entry_set_value", "Invalid entry argument" );
1907         return INT_MIN;
1908     }
1909 
1910     old_val = entry->val;
1911     entry->val = value;
1912     return old_val;
1913 }
1914 
1915 
1916 /***************************************
1917  * Set the user data pointer associated with an entry
1918  ***************************************/
1919 
1920 void *
fl_popup_entry_set_user_data(FL_POPUP_ENTRY * entry,void * user_data)1921 fl_popup_entry_set_user_data( FL_POPUP_ENTRY * entry,
1922                               void           * user_data )
1923 {
1924     void *old_user_data;
1925 
1926     if ( fli_check_popup_entry_exists( entry ) )
1927     {
1928         M_err( "fl_popup_entry_set_user_data", "Invalid entry argument" );
1929         return NULL;
1930     }
1931 
1932     old_user_data = entry->user_data;
1933     entry->user_data = user_data;
1934     return old_user_data;
1935 }
1936 
1937 
1938 /***************************************
1939  * Returns the group a radio entry belongs to.
1940  * Returns group number on success and INT_MAX on failure.
1941  ***************************************/
1942 
1943 int
fl_popup_entry_get_group(FL_POPUP_ENTRY * entry)1944 fl_popup_entry_get_group( FL_POPUP_ENTRY * entry )
1945 {
1946     if ( fli_check_popup_entry_exists( entry ) )
1947     {
1948         M_err( "fl_popup_entry_get_group", "Invalid entry argument" );
1949         return INT_MAX;
1950     }
1951 
1952     return entry->group;
1953 }
1954 
1955 
1956 /***************************************
1957  * Set a new group a radio entry
1958  * Returns old group number on success and INT_MAX on failure.
1959  ***************************************/
1960 
1961 int
fl_popup_entry_set_group(FL_POPUP_ENTRY * entry,int group)1962 fl_popup_entry_set_group( FL_POPUP_ENTRY * entry,
1963                           int              group )
1964 {
1965     int old_group;
1966     FL_POPUP_ENTRY *e;
1967 
1968     if ( fli_check_popup_entry_exists( entry ) )
1969     {
1970         M_err( "fl_popup_entry_set_group", "Invalid entry argument" );
1971         return INT_MAX;
1972     }
1973 
1974     old_group = entry->group;
1975 
1976     if ( entry->type != FL_POPUP_RADIO )
1977     {
1978         entry->group = group;
1979         return old_group;
1980     }
1981 
1982     if ( old_group == group )
1983         return entry->group;
1984 
1985     for ( e = entry; e != NULL; e = e->next )
1986         if ( e->type == FL_POPUP_RADIO
1987              && e->group == group
1988              && e->state & FL_POPUP_CHECKED )
1989             entry->state &= ~ FL_POPUP_CHECKED;
1990 
1991     entry->group = group;
1992     return old_group;
1993 }
1994 
1995 
1996 /***************************************
1997  * Returns the sub-popup for a sub-popup entry.
1998  * Returns the sub-popups address on success and NULL on failure.
1999  ***************************************/
2000 
2001 FL_POPUP *
fl_popup_entry_get_subpopup(FL_POPUP_ENTRY * entry)2002 fl_popup_entry_get_subpopup( FL_POPUP_ENTRY * entry )
2003 {
2004     if ( fli_check_popup_entry_exists( entry ) )
2005     {
2006         M_err( "fl_popup_entry_get_subpopup", "Invalid entry argument" );
2007         return NULL;
2008     }
2009 
2010     if ( entry->type != FL_POPUP_SUB )
2011     {
2012         M_err( "fl_popup_entry_get_subpopup", "Entry isn't a subpopup entry" );
2013         return NULL;
2014     }
2015 
2016     return entry->sub;
2017 }
2018 
2019 
2020 /***************************************
2021  * Set a new sub-popup for an entry, requires that this a sub-popup entry.
2022  * Returns the new sub-popups address on success and NULL on failure.
2023  ***************************************/
2024 
2025 FL_POPUP *
fl_popup_entry_set_subpopup(FL_POPUP_ENTRY * entry,FL_POPUP * subpopup)2026 fl_popup_entry_set_subpopup( FL_POPUP_ENTRY * entry,
2027                              FL_POPUP       * subpopup )
2028 {
2029     FL_POPUP *old_sub;
2030 
2031     if ( fli_check_popup_entry_exists( entry ) )
2032     {
2033         M_err( "fl_popup_entry_set_subpopup", "Invalid entry argument" );
2034         return NULL;
2035     }
2036 
2037     if ( entry->type != FL_POPUP_SUB )
2038     {
2039         M_err( "fl_popup_entry_set_subpopup", "Entry isn't a subpopup entry" );
2040         return NULL;
2041     }
2042 
2043     if ( entry->sub == subpopup )
2044         return entry->sub;
2045 
2046     if ( entry->sub->win != None || subpopup->win != None )
2047     {
2048         M_err( "fl_popup_entry_set_subpopup", "Can't change sub-popup while "
2049                "entries sub-popup is shown.");
2050         return NULL;
2051     }
2052 
2053     old_sub = entry->sub;
2054     entry->sub = subpopup;
2055     if ( check_sub( entry ) )
2056     {
2057         entry->sub = old_sub;
2058         M_err( "fl_popup_entry_set_subpopup", "Invalid sub-popup argument" );
2059         return NULL;
2060     }
2061 
2062     fl_popup_delete( entry->sub );
2063     return entry->sub = subpopup;
2064 }
2065 
2066 
2067 /***************************************
2068  * Find a popup entry by its position in the popup, starting at 1.
2069  * (Line entries aren't counted, but hidden entries are.)
2070  ***************************************/
2071 
2072 FL_POPUP_ENTRY *
fl_popup_entry_get_by_position(FL_POPUP * popup,int position)2073 fl_popup_entry_get_by_position( FL_POPUP * popup,
2074                                 int        position )
2075 {
2076     FL_POPUP_ENTRY *e;
2077     int i = 0;
2078 
2079     if ( fli_check_popup_exists( popup ) )
2080     {
2081         M_err( "fl_popup_entry_get_by_position", "Invalid popup argument" );
2082         return NULL;
2083     }
2084 
2085     for ( e = popup->entries; e != NULL; e = e->next )
2086     {
2087         if ( e->type == FL_POPUP_LINE )
2088             continue;
2089 
2090         if ( i++ == position )
2091             return e;
2092     }
2093 
2094     return NULL;
2095 }
2096 
2097 
2098 /***************************************
2099  * Find a popup entry by its value
2100  ***************************************/
2101 
2102 FL_POPUP_ENTRY *
fl_popup_entry_get_by_value(FL_POPUP * popup,long val)2103 fl_popup_entry_get_by_value( FL_POPUP * popup,
2104                              long       val )
2105 {
2106     FL_POPUP_ENTRY *e,
2107                    *r;
2108 
2109     if ( fli_check_popup_exists( popup ) )
2110     {
2111         M_err( "fl_popup_entry_get_by_value", "Invalid popup argument" );
2112         return NULL;
2113     }
2114 
2115     for ( e = popup->entries; e != NULL; e = e->next )
2116     {
2117         if ( e->type == FL_POPUP_LINE )
2118             continue;
2119 
2120         if ( e->val == val )
2121             return e;
2122 
2123         if (    e->type == FL_POPUP_SUB
2124              && ( r = fl_popup_entry_get_by_value( e->sub, val ) ) )
2125             return r;
2126     }
2127 
2128     return NULL;
2129 }
2130 
2131 
2132 /***************************************
2133  * Find a popup entry by its user data
2134  ***************************************/
2135 
2136 FL_POPUP_ENTRY *
fl_popup_entry_get_by_user_data(FL_POPUP * popup,void * user_data)2137 fl_popup_entry_get_by_user_data( FL_POPUP * popup,
2138                                  void     * user_data )
2139 {
2140     FL_POPUP_ENTRY *e,
2141                    *r;
2142 
2143     if ( fli_check_popup_exists( popup ) )
2144     {
2145         M_err( "fl_popup_entry_get_by_value", "Invalid popup argument" );
2146         return NULL;
2147     }
2148 
2149     for ( e = popup->entries; e != NULL; e = e->next )
2150     {
2151         if ( e->type == FL_POPUP_LINE )
2152             continue;
2153 
2154         if ( e->user_data == user_data )
2155             return e;
2156 
2157         if (    e->type == FL_POPUP_SUB
2158              && ( r = fl_popup_entry_get_by_user_data( e->sub, user_data ) ) )
2159             return r;
2160     }
2161 
2162     return NULL;
2163 }
2164 
2165 
2166 /***************************************
2167  * Find a popup entry by its text
2168  ***************************************/
2169 
2170 FL_POPUP_ENTRY *
fl_popup_entry_get_by_text(FL_POPUP * popup,const char * text)2171 fl_popup_entry_get_by_text( FL_POPUP   * popup,
2172                             const char * text )
2173 {
2174     FL_POPUP_ENTRY *e,
2175                    *r;
2176 
2177     if ( fli_check_popup_exists( popup ) )
2178     {
2179         M_err( "fl_popup_entry_get_by_text", "Invalid popup argument" );
2180         return NULL;
2181     }
2182 
2183     for ( e = popup->entries; e != NULL; e = e->next )
2184     {
2185         if ( e->type == FL_POPUP_LINE )
2186             continue;
2187 
2188         if ( ! strcmp( e->text, text ) )
2189             return e;
2190 
2191         if (    e->type == FL_POPUP_SUB
2192              && ( r = fl_popup_entry_get_by_text( e->sub, text ) ) )
2193             return r;
2194     }
2195 
2196     return NULL;
2197 }
2198 
2199 
2200 /***************************************
2201  * Find a popup entry by its text
2202  ***************************************/
2203 
2204 FL_POPUP_ENTRY *
fl_popup_entry_get_by_text_f(FL_POPUP * popup,const char * fmt,...)2205 fl_popup_entry_get_by_text_f( FL_POPUP   * popup,
2206                               const char * fmt,
2207                               ... )
2208 {
2209     FL_POPUP_ENTRY *e;
2210     char *buf;
2211 
2212     EXPAND_FORMAT_STRING( buf, fmt );
2213     e = fl_popup_entry_get_by_text( popup, buf );
2214     fl_free( buf );
2215     return e;
2216 }
2217 
2218 
2219 /***************************************
2220  * Find a popup entry by its label
2221  ***************************************/
2222 
2223 FL_POPUP_ENTRY *
fl_popup_entry_get_by_label(FL_POPUP * popup,const char * label)2224 fl_popup_entry_get_by_label( FL_POPUP   * popup,
2225                              const char * label )
2226 {
2227     FL_POPUP_ENTRY *e,
2228                    *r;
2229 
2230     if ( fli_check_popup_exists( popup ) )
2231     {
2232         M_err( "fl_popup_entry_get_by_label", "Invalid popup argument" );
2233         return NULL;
2234     }
2235 
2236     for ( e = popup->entries; e != NULL; e = e->next )
2237     {
2238         if ( e->type == FL_POPUP_LINE || e->label == NULL )
2239             continue;
2240 
2241         if ( ! strcmp( e->label, label ) )
2242             return e;
2243 
2244         if (    e->type == FL_POPUP_SUB
2245              && ( r = fl_popup_entry_get_by_label( e->sub, label ) ) )
2246             return r;
2247     }
2248 
2249     return NULL;
2250 }
2251 
2252 
2253 /***************************************
2254  * Find a popup entry by its label
2255  ***************************************/
2256 
2257 FL_POPUP_ENTRY *
fl_popup_entry_get_by_label_f(FL_POPUP * popup,const char * fmt,...)2258 fl_popup_entry_get_by_label_f( FL_POPUP   * popup,
2259                                const char * fmt,
2260                                ... )
2261 {
2262     FL_POPUP_ENTRY *e;
2263     char *buf;
2264 
2265     EXPAND_FORMAT_STRING( buf, fmt );
2266     e = fl_popup_entry_get_by_label( popup, buf );
2267     fl_free( buf );
2268     return e;
2269 }
2270 
2271 
2272 /***************************************
2273  * Get size of a popup, returns 0 on success and -1 on error
2274  ***************************************/
2275 
2276 int
fl_popup_get_size(FL_POPUP * popup,unsigned int * w,unsigned int * h)2277 fl_popup_get_size( FL_POPUP     * popup,
2278                    unsigned int * w,
2279                    unsigned int * h )
2280 {
2281     if ( fli_check_popup_exists( popup ) )
2282     {
2283         M_err( "fl_popup_get_size", "Invalid popup argument" );
2284         return -1;
2285     }
2286 
2287     if ( popup->need_recalc )
2288         recalc_popup( popup );
2289 
2290     *w = popup->w;
2291     *h = popup->h;
2292 
2293     return 0;
2294 }
2295 
2296 
2297 /***************************************
2298  * Get minimum width of a popup
2299  ***************************************/
2300 
2301 int
fl_popup_get_min_width(FL_POPUP * popup)2302 fl_popup_get_min_width( FL_POPUP * popup )
2303 {
2304     if ( fli_check_popup_exists( popup ) )
2305     {
2306         M_err( "fl_popup_get_size", "Invalid popup argument" );
2307         return -1;
2308     }
2309 
2310     if ( popup->need_recalc )
2311         recalc_popup( popup );
2312 
2313     return popup->min_width;
2314 }
2315 
2316 
2317 /***************************************
2318  * Set minimum width of a popup
2319  ***************************************/
2320 
2321 int
fl_popup_set_min_width(FL_POPUP * popup,int min_width)2322 fl_popup_set_min_width( FL_POPUP * popup,
2323                         int        min_width )
2324 {
2325     int old_min_width;
2326 
2327     if ( fli_check_popup_exists( popup ) )
2328     {
2329         M_err( "fl_popup_get_size", "Invalid popup argument" );
2330         return -1;
2331     }
2332 
2333     old_min_width = popup->min_width;
2334 
2335     if ( min_width < 0 )
2336         min_width = 0;
2337 
2338     popup->min_width = min_width;
2339     popup->need_recalc = 1;
2340 
2341     return old_min_width;
2342 }
2343 
2344 
2345 /***************************************
2346  * Set up a linked list of entries for a popup according to a string
2347  * and optional arguments. The resulting list then can be merged into
2348  * an already existing list of entries of the popup. The 'simple'
2349  * argument, when set, says that the new entry can't be a sub-entry,
2350  * a toggle, line or radio entry.
2351  ***************************************/
2352 
2353 static FL_POPUP_ENTRY *
parse_entries(FL_POPUP * popup,char * str,va_list ap,const char * caller,int simple)2354 parse_entries( FL_POPUP   * popup,
2355                char       * str,
2356                va_list      ap,
2357                const char * caller,
2358                int          simple )
2359 {
2360     FL_POPUP_ENTRY *entry,
2361                    *entry_first = NULL;
2362     char *c,
2363          *s,
2364          *sc = NULL,
2365          *acc = NULL;
2366 
2367     /* Split the string at '|' and create a new entry for each part */
2368 
2369     for ( c = strtok( str, "|" ); c != NULL; c = strtok( NULL, "|" ) )
2370     {
2371         /* Allocate a new entry and append it to the list of new entries */
2372 
2373         if ( ( entry = fl_malloc( sizeof *entry ) ) == NULL )
2374         {
2375             M_err( caller, "Running out of memory" );
2376             return failed_add( entry_first );
2377         }
2378 
2379         if ( entry_first == NULL )
2380         {
2381             entry_first = entry;
2382             entry->prev = NULL;
2383         }
2384         else
2385         {
2386             FL_POPUP_ENTRY *e;
2387 
2388             for ( e = entry_first; e->next != NULL; e = e->next )
2389                 /* empty */;
2390 
2391             e->next = entry;
2392             entry->prev = e;
2393         }
2394 
2395         entry->next = NULL;
2396 
2397         entry->label = NULL;
2398         entry->accel = NULL;
2399 
2400         /* The text field is exactly what the user gave us */
2401 
2402         if ( ( entry->text = fl_strdup( c ) ) == NULL )
2403         {
2404             M_err( caller, "Running out of memory" );
2405             return failed_add( entry_first );
2406         }
2407 
2408         /* Set some default values */
2409 
2410         entry->user_data = NULL;
2411         entry->val = popup->counter++;
2412 
2413         entry->is_act   = 0;
2414         entry->type     = FL_POPUP_NORMAL;
2415         entry->state    = 0;
2416 
2417         entry->shortcut = NULL;
2418         entry->ulpos    = -1;
2419 
2420         entry->callback = NULL;
2421         entry->enter_callback = NULL;
2422         entry->leave_callback = NULL;
2423         entry->sub = NULL;
2424         entry->popup = popup;
2425 
2426         /* Now analyze the string for the entry */
2427 
2428         s = c;
2429         while ( ( s = strchr( s, '%' ) ) != NULL )
2430         {
2431             switch ( s[ 1 ] )
2432             {
2433                 case '%' :
2434                     memmove( s, s + 1, strlen( s ) );
2435                     s++;
2436                     break;
2437 
2438                 case 'x' :           /* set a value */
2439                     entry->val = va_arg( ap, long );
2440                     memmove( s, s + 2, strlen( s + 1 ) );
2441                     break;
2442 
2443                 case 'u' :           /* set pointer to user data */
2444                     entry->user_data = va_arg( ap, void * );
2445                     memmove( s, s + 2, strlen( s + 1 ) );
2446                     break;
2447 
2448                 case 'f' :           /* set callback function */
2449                     entry->callback = va_arg( ap, FL_POPUP_CB );
2450                     memmove( s, s + 2, strlen( s + 1 ) );
2451                     break;
2452 
2453                 case 'E' :           /* set enter callback function */
2454                     entry->enter_callback = va_arg( ap, FL_POPUP_CB );
2455                     memmove( s, s + 2, strlen( s + 1 ) );
2456                     break;
2457 
2458                 case 'L' :           /* set leave callback function */
2459                     entry->leave_callback = va_arg( ap, FL_POPUP_CB );
2460                     memmove( s, s + 2, strlen( s + 1 ) );
2461                     break;
2462 
2463                 case 'm' :           /* set a submenu */
2464                     if ( entry->type != FL_POPUP_NORMAL )
2465                     {
2466                         M_err( caller, "Entry can't be submenu and something "
2467                                "else" );
2468                         return failed_add( entry_first );
2469                     }
2470 
2471                     entry->sub = va_arg( ap, FL_POPUP * );
2472 
2473                     if ( ! simple )
2474                     {
2475                         if ( check_sub( entry ) )
2476                         {
2477                             M_err( caller, "Invalid submenu popup" );
2478                             return failed_add( entry_first );
2479                         }
2480 
2481                         entry->type = FL_POPUP_SUB;
2482                         entry->sub->parent = popup;
2483                     }
2484                     else
2485                         entry->sub = NULL;
2486 
2487                     memmove( s, s + 2, strlen( s + 1 ) );
2488                     break;
2489 
2490                 case 'l' :           /* set up entry as a line entry */
2491                     if ( entry->type != FL_POPUP_NORMAL )
2492                     {
2493                         M_err( caller, "Entry can't be a line marker and "
2494                                "something else" );
2495                         return failed_add( entry_first );
2496                     }
2497 
2498                     entry->type = FL_POPUP_LINE;
2499                     memmove( s, s + 2, strlen( s + 1 ) );
2500                     break;
2501 
2502                 case 'T' :           /* set up as toggle entry */
2503                 case 't' :
2504                     if ( entry->type != FL_POPUP_NORMAL )
2505                     {
2506                         M_err( caller, "Entry can't be a toggle entry and "
2507                                "something else" );
2508                         return failed_add( entry_first );
2509                     }
2510 
2511                     if ( ! simple )
2512                     {
2513                         entry->type = FL_POPUP_TOGGLE;
2514                         if ( s[ 1 ] == 'T' )
2515                             entry->state |= FL_POPUP_CHECKED;
2516                     }
2517                     memmove( s, s + 2, strlen( s + 1 ) );
2518                     break;
2519 
2520                 case 'R' :           /* set up as radio entry */
2521                 case 'r' :           /* set up as radio entry */
2522                     if ( entry->type != FL_POPUP_NORMAL )
2523                     {
2524                         M_err( caller, "Entry can't be radio entry and "
2525                                "something else" );
2526                         return failed_add( entry_first );
2527                     }
2528 
2529                     entry->group = va_arg( ap, int );
2530 
2531                     if ( ! simple )
2532                     {
2533                         entry->type = FL_POPUP_RADIO;
2534                         if ( s[ 1 ] == 'R' )
2535                             entry->state |= FL_POPUP_CHECKED;
2536                     }
2537 
2538                     memmove( s, s + 2, strlen( s + 1 ) );
2539                     break;
2540 
2541                 case 'd' :           /* mark entry as disabled */
2542                     entry->state |= FL_POPUP_DISABLED;
2543                     memmove( s, s + 2, strlen( s + 1 ) );
2544                     break;
2545 
2546                 case 'h' :           /* mark entry as hidden */
2547                     entry->state |= FL_POPUP_HIDDEN;
2548                     memmove( s, s + 2, strlen( s + 1 ) );
2549                     break;
2550 
2551                 case 's' :           /* set a shortcut */
2552                     sc = va_arg( ap, char * );
2553                     memmove( s, s + 2, strlen( s + 1 ) );
2554                     break;
2555 
2556                 case 'S' :
2557                     if ( acc != NULL )
2558                     {
2559                         M_err( caller, "'%%S' sequence found more than once in "
2560                                "entry definition" );
2561                         return failed_add( entry_first );
2562                     }
2563                     *s++ = '\0';
2564                     acc = ++s;
2565                     break;
2566 
2567                 default :
2568                     M_err( caller, "Invalid flag '%%%c'", s[ 1 ] );
2569                     return failed_add( entry_first );
2570             }
2571         }
2572 
2573         /* If we're asked to create simple popup (for a FL_SELECT object)
2574            remove the entry again if it's a line entry */
2575 
2576         if ( simple && entry->type == FL_POPUP_LINE )
2577         {
2578             if ( entry->prev != NULL )
2579                 entry->prev->next = NULL;
2580             else
2581                 entry_first = NULL;
2582             fl_free( entry );
2583 
2584             popup->counter--;
2585 
2586             continue;
2587         }
2588 
2589         /* Now all flags should be removed from text string, so we can set
2590            the entries label and accelerator key text after also removing
2591            backspace characters and replacing tabs by spaces. */
2592 
2593         cleanup_string ( c );
2594 
2595         if ( ! *c )
2596             entry->label = NULL;
2597         else if ( ( entry->label = fl_strdup( c ) ) == NULL )
2598         {
2599             M_err( caller, "Running out of memory" );
2600             return failed_add( entry_first );
2601         }
2602 
2603         acc = cleanup_string( acc );
2604 
2605         if ( ! acc || ! *acc )
2606             entry->accel = NULL;
2607         else if ( ( entry->accel = fl_strdup( acc ) ) == NULL )
2608         {
2609             M_err( caller, "Running out of memory" );
2610             return failed_add( entry_first );
2611         }
2612 
2613         acc = NULL;
2614 
2615         /* Having the text we can set up the shortcuts */
2616 
2617         if ( sc )
2618         {
2619             convert_shortcut( sc, entry );
2620             sc = NULL;
2621         }
2622     }
2623 
2624     /* Make sure the settings for radio entries are consistent, i.e. only
2625        a single one of a group (taking both the already existing as well
2626        as the new ones into account) is set (the last created wins) */
2627 
2628     for ( entry = entry_first; entry != NULL; entry = entry->next )
2629         if ( entry->type == FL_POPUP_RADIO && entry->state * FL_POPUP_CHECKED )
2630             radio_check( entry );
2631 
2632     /* List is complete and the caller can link it into its list */
2633 
2634     return entry_first;
2635 }
2636 
2637 
2638 /***************************************
2639  * Called when adding popup entries fails due to whatever reasons
2640  * to get rid of all already created ones
2641  ***************************************/
2642 
2643 static FL_POPUP_ENTRY *
failed_add(FL_POPUP_ENTRY * first)2644 failed_add( FL_POPUP_ENTRY * first )
2645 {
2646     FL_POPUP_ENTRY *e;
2647 
2648     while ( first )
2649     {
2650         e = first->next;
2651         fl_popup_entry_delete( first );
2652         first = e;
2653     }
2654 
2655     return NULL;
2656 }
2657 
2658 
2659 /***************************************
2660  * Checks if a popup is suitable for use as a sub-popup
2661  ***************************************/
2662 
2663 static int
check_sub(FL_POPUP_ENTRY * entry)2664 check_sub( FL_POPUP_ENTRY * entry )
2665 {
2666     /* Sub-popup can't be NULL */
2667 
2668     if ( entry->sub == NULL )
2669         return 1;
2670 
2671     /* Check if the sub-popup exists */
2672 
2673     if ( fli_check_popup_exists( entry->sub ) )
2674         return 1;
2675 
2676     /* The sub-popup can't be the entries popup itself */
2677 
2678     if ( entry->popup == entry->sub )
2679         return 1;
2680 
2681     /* The sub-popup can not already be a sub-popup for some other entry */
2682 
2683     if ( entry->sub->parent )
2684         return 1;
2685 
2686     return 0;
2687 }
2688 
2689 
2690 /***************************************
2691  * Go through all entries of a popup and resets the checked state of
2692  * all other entries belonging to the same group before the one we
2693  * were called with.
2694  ***************************************/
2695 
2696 static void
radio_check(FL_POPUP_ENTRY * entry)2697 radio_check( FL_POPUP_ENTRY * entry )
2698 {
2699     FL_POPUP_ENTRY *e;
2700 
2701     /* First reset (if necessary) the old ones */
2702 
2703     for ( e = entry->popup->entries; e != NULL; e = e->next )
2704         if (    e->type == FL_POPUP_RADIO
2705              && e != entry
2706              && entry->group == e->group )
2707             e->state &= ~ FL_POPUP_CHECKED;
2708 
2709     /* Also reset all the new ones (except the one we were called for) */
2710 
2711     for ( e = entry->prev; e != NULL; e = e->prev )
2712         if ( e->type == FL_POPUP_RADIO
2713              && e != entry
2714              && entry->group == e->group )
2715             e->state &= ~ FL_POPUP_CHECKED;
2716 }
2717 
2718 
2719 /***************************************
2720  * Makes a shortcut out of an entries text and determines where to underline
2721  ***************************************/
2722 
2723 static void
convert_shortcut(const char * shortcut,FL_POPUP_ENTRY * entry)2724 convert_shortcut( const char     * shortcut,
2725                   FL_POPUP_ENTRY * entry )
2726 {
2727     long sc[ MAX_SHORTCUTS + 1 ];
2728     int cnt;
2729 
2730     if (    entry->label && *entry->label
2731          && ( ! entry->accel || ! *entry->accel ) )
2732         entry->ulpos = fli_get_underline_pos( entry->label, shortcut ) - 1;
2733     else
2734         entry->ulpos = -1;
2735 
2736     cnt = fli_convert_shortcut( shortcut, sc );
2737 
2738     fli_safe_free( entry->shortcut );
2739     entry->shortcut = fl_malloc( ( cnt + 1 ) * sizeof *entry->shortcut );
2740     memcpy( entry->shortcut, sc, ( cnt + 1 ) * sizeof *entry->shortcut );
2741 }
2742 
2743 
2744 /***************************************
2745  * Recalculate the dimensions of a popup and the positions of the entries
2746  ***************************************/
2747 
2748 static void
recalc_popup(FL_POPUP * popup)2749 recalc_popup( FL_POPUP * popup )
2750 {
2751     FL_POPUP_ENTRY *e;
2752     int offset =   FL_abs( popup->top_parent->bw )
2753                  + ( popup->top_parent->bw > 0 ? 1 : 0 );
2754     unsigned int cur_w = 0,
2755                  cur_h = offset,
2756                  w,
2757                  h;
2758 
2759     title_dimensions( popup, &w, &h );
2760 
2761     if ( w > 0 )
2762     {
2763         /* Note: title box should always have a bit of spacing to the top
2764            of the window */
2765 
2766         popup->title_box_x = offset + OUTER_PADDING_LEFT;
2767         popup->title_box_y = offset + FL_max( OUTER_PADDING_TOP, 3 );
2768 
2769         cur_w = w + INNER_PADDING_LEFT + INNER_PADDING_RIGHT;
2770         popup->title_box_h = h + INNER_PADDING_TOP + INNER_PADDING_BOTTOM;
2771         cur_h +=   popup->title_box_h
2772                  + FL_max( OUTER_PADDING_TOP, 3 ) + OUTER_PADDING_BOTTOM + 2;
2773     }
2774 
2775     popup->has_subs = 0;
2776     popup->has_boxes = 0;
2777 
2778     for ( e = popup->entries; e != NULL; e = e->next )
2779     {
2780         if ( e->state & FL_POPUP_HIDDEN )
2781             continue;
2782 
2783         e->box_x = offset + OUTER_PADDING_LEFT;
2784         e->box_y = cur_h;
2785 
2786         entry_text_dimensions( e, &w, &h );
2787 
2788         cur_w = FL_max( cur_w, w );
2789         cur_h += e->box_h = h + OUTER_PADDING_TOP + OUTER_PADDING_BOTTOM;
2790 
2791         if ( e->type == FL_POPUP_TOGGLE || e->type == FL_POPUP_RADIO )
2792             popup->has_boxes = 1;
2793         else if ( e->type == FL_POPUP_SUB )
2794             popup->has_subs = 1;
2795     }
2796 
2797     if ( popup->has_boxes )
2798         cur_w += popup->top_parent->entry_font_size + SYMBOL_PADDING;
2799 
2800     if ( popup->has_subs )
2801         cur_w += SYMBOL_PADDING + popup->top_parent->entry_font_size;
2802 
2803     popup->w = cur_w + 2 * offset + OUTER_PADDING_LEFT + OUTER_PADDING_RIGHT;
2804     popup->h = cur_h + offset + 1;
2805 
2806     popup->w = FL_max( popup->w, ( unsigned int ) popup->min_width );
2807     popup->title_box_w =   popup->w - 2 * offset
2808                          - OUTER_PADDING_LEFT - OUTER_PADDING_RIGHT;
2809 
2810     popup->need_recalc = 0;
2811 }
2812 
2813 
2814 /***************************************
2815  * Calculate the dimensions of the title
2816  ***************************************/
2817 
2818 static void
title_dimensions(FL_POPUP * popup,unsigned int * w,unsigned int * h)2819 title_dimensions( FL_POPUP     * popup,
2820                   unsigned int * w,
2821                   unsigned int * h )
2822 {
2823     FL_POPUP *ptp = popup->top_parent;
2824     char *s,
2825          *c;
2826     int dummy;
2827 
2828     if ( popup->title == NULL )
2829     {
2830         *w = *h = 0;
2831         return;
2832     }
2833 
2834     s = c = fl_strdup( popup->title );
2835 
2836     /* Now calculate the dimensions of the string */
2837 
2838     *w = 0;
2839     *h = 0;
2840 
2841     for ( c = strtok( s, "\n" ); c != NULL; c = strtok( NULL, "\n" ) )
2842     {
2843         *w = FL_max( *w, ( unsigned int )
2844                      fl_get_string_width( ptp->title_font_style,
2845                                           ptp->title_font_size,
2846                                           c, strlen( c ) ) );
2847         *h += fl_get_string_height( ptp->title_font_style,
2848                                     ptp->title_font_size,
2849                                     c, strlen( c ), &dummy, &dummy );
2850     }
2851 
2852     fl_free( s );
2853 
2854     /* Add offsets the string drawing function will add */
2855 
2856     *w += 2 * STR_OFFSET_X;
2857     *h += 2 * STR_OFFSET_Y;
2858 }
2859 
2860 
2861 /***************************************
2862  * Calculate the (minimum) dimensions of an entry
2863  ***************************************/
2864 
2865 static void
entry_text_dimensions(FL_POPUP_ENTRY * entry,unsigned int * w,unsigned int * h)2866 entry_text_dimensions( FL_POPUP_ENTRY * entry,
2867                        unsigned int   * w,
2868                        unsigned int   * h )
2869 {
2870     FL_POPUP *ptp = entry->popup->top_parent;
2871     char *s,
2872          *c;
2873     int ulpos = entry->ulpos;
2874     XRectangle *xr;
2875     int asc;
2876     int dummy;
2877 
2878     *w = *h = 0;
2879 
2880     if ( entry->type == FL_POPUP_LINE )
2881     {
2882         *h = LINE_HEIGHT;
2883         return;
2884     }
2885 
2886     /* Determine length and height of label string */
2887 
2888     if ( entry->label && *entry->label )
2889     {
2890         s = c = fl_strdup( entry->label );
2891 
2892         /* Calculate the dimensions of the label (always use the font of the
2893            top-most parent) */
2894 
2895         for ( c = strtok( s, "\n" ); c != NULL; c = strtok( NULL, "\n" ) )
2896         {
2897             unsigned int old_h = *h;
2898 
2899             *w = FL_max( *w, ( unsigned int )
2900                          fl_get_string_width( ptp->entry_font_style,
2901                                               ptp->entry_font_size,
2902                                               c, strlen( c ) ) );
2903             *h += fl_get_string_height( ptp->entry_font_style,
2904                                         ptp->entry_font_size,
2905                                         c, strlen( c ), &asc, &dummy );
2906 
2907             if ( c == s )
2908                 entry->sl_h = *h;
2909 
2910             /* Not very nice hack to get the underline position */
2911 
2912             if ( ulpos >= 0 )
2913             {
2914                 if ( ulpos < ( int ) strlen( c ) )
2915                 {
2916                     xr = fli_get_underline_rect(
2917                                     fl_get_font_struct( ptp->entry_font_style,
2918                                                         ptp->entry_font_size ),
2919                                     0, asc, c, ulpos );
2920                     entry->ul_x = xr->x + STR_OFFSET_X;
2921                     entry->ul_y = old_h + xr->y + STR_OFFSET_Y;
2922                     entry->ul_w = xr->width;
2923                     entry->ul_h = xr->height;
2924                 }
2925 
2926                 ulpos -= strlen( c ) + 1;
2927             }
2928         }
2929 
2930         fli_safe_free( s );
2931     }
2932 
2933     /* Repeat this for the accelerator key text (minimum spacing between this
2934        and the label is 1.5 times the font size) */
2935 
2936     if ( entry->accel && *entry->accel )
2937     {
2938         unsigned int aw = 0,
2939                      ah = 0;
2940 
2941         *w += 1.5 * ptp->entry_font_size;
2942 
2943         s = c = fl_strdup( entry->accel );
2944 
2945         for ( c = strtok( s, "\n" ); c != NULL; c = strtok( NULL, "\n" ) )
2946         {
2947             aw = FL_max( aw, ( unsigned int )
2948                          fl_get_string_width( ptp->entry_font_style,
2949                                               ptp->entry_font_size,
2950                                               c, strlen( c ) ) );
2951             ah += fl_get_string_height( ptp->entry_font_style,
2952                                         ptp->entry_font_size,
2953                                         c, strlen( c ), &dummy, &dummy );
2954         }
2955 
2956         fli_safe_free( s );
2957 
2958         *w += aw;
2959         *h = FL_max( *h, ah );
2960     }
2961 
2962     *w += 2 * STR_OFFSET_X;
2963     *h += 2 * STR_OFFSET_Y;
2964 }
2965 
2966 
2967 /***************************************
2968  * Draw a popup
2969  ***************************************/
2970 
2971 static void
draw_popup(FL_POPUP * popup)2972 draw_popup( FL_POPUP * popup )
2973 {
2974     FL_POPUP_ENTRY *e;
2975 
2976     /* If necessary recalculate the size of the popup window and,
2977        if it's already shown, resize it */
2978 
2979     if ( popup->need_recalc )
2980     {
2981         unsigned int old_w = popup->w,
2982                      old_h = popup->h;
2983 
2984         recalc_popup( popup );
2985 
2986         if (    popup->win != None
2987              && ( popup->w != old_w || popup->h != old_h ) )
2988             XResizeWindow( flx->display, popup->win, popup->w, popup->h );
2989     }
2990 
2991     /* If necessary create and map the popup window, otherwise just draw it
2992        (no drawing needed when the window gets opened, we will receive an
2993        Expose event that will induce the drawing) */
2994 
2995     if ( popup->win == None )
2996         create_popup_window( popup );
2997     else
2998     {
2999         /* Draw the popup box  */
3000 
3001         fl_draw_box( FL_UP_BOX, 0, 0, popup->w, popup->h, popup->bg_color,
3002                      popup->top_parent->bw );
3003 
3004         /* Draw the title and all entries */
3005 
3006         draw_title( popup );
3007 
3008         for ( e = popup->entries; e != NULL; e = e->next )
3009             draw_entry( e );
3010     }
3011 }
3012 
3013 
3014 /***************************************
3015  * Draws the title of a popup
3016  ***************************************/
3017 
3018 static void
draw_title(FL_POPUP * popup)3019 draw_title( FL_POPUP * popup )
3020 {
3021     FL_POPUP *ptp = popup->top_parent;
3022 
3023     if ( popup->title == NULL )
3024         return;
3025 
3026     /* Draw a box with a frame around the title */
3027 
3028     fl_draw_box( FL_FRAME_BOX, popup->title_box_x - 1, popup->title_box_y - 1,
3029                  popup->title_box_w + 2, popup->title_box_h + 2,
3030                  ptp->bg_color, 1 );
3031 
3032     fl_draw_text( FL_ALIGN_CENTER, popup->title_box_x, popup->title_box_y,
3033                   popup->title_box_w, popup->title_box_h, ptp->title_color,
3034                   ptp->title_font_style, ptp->title_font_size, popup->title );
3035 }
3036 
3037 
3038 /***************************************
3039  * Draws an entry of a popup
3040  ***************************************/
3041 
3042 static void
draw_entry(FL_POPUP_ENTRY * entry)3043 draw_entry( FL_POPUP_ENTRY * entry )
3044 {
3045     FL_POPUP *p = entry->popup,
3046              *ptp = p->top_parent;
3047     FL_COLOR color;
3048     int offset = FL_abs( ptp->bw ) + ( ptp->bw > 0 ? 1 : 0 );
3049     int x;
3050     unsigned int w;
3051 
3052     /* Hidden entries need no further work */
3053 
3054     if ( entry->state & FL_POPUP_HIDDEN )
3055         return;
3056 
3057     /* Calculate the width of the box we're going to be drawning in */
3058 
3059     x = entry->box_x;
3060     w = entry->box_w =   p->w - 2 * offset
3061                        - OUTER_PADDING_LEFT - OUTER_PADDING_RIGHT;
3062 
3063     /* For entries that just stand for separating lines draw that line
3064        and be done with it */
3065 
3066     if ( entry->type == FL_POPUP_LINE )
3067     {
3068         fl_draw_box( FL_DOWN_BOX, x, entry->box_y + OUTER_PADDING_TOP + 1,
3069                      w, LINE_HEIGHT - 1, ptp->bg_color, 1 );
3070         return;
3071     }
3072 
3073     /* Draw the background of the entry */
3074 
3075     fl_rectangle( 1, offset, entry->box_y, p->w - 2 * offset - 1, entry->box_h,
3076                   entry->is_act ? ptp->on_color : ptp->bg_color );
3077 
3078     /* Find out what color the text is to be drawn in */
3079 
3080     if ( entry->state & FL_POPUP_DISABLED )
3081         color = ptp->text_off_color;
3082     else
3083         color = entry->is_act ? ptp->text_on_color : ptp->text_color;
3084 
3085     /* If there are radio/toggle entries extra space at the start is needed.
3086        For checked toggle items a check mark is drawn there and for radio
3087        items a circle */
3088 
3089     if ( p->has_boxes )
3090     {
3091         if ( entry->type == FL_POPUP_RADIO )
3092             fl_draw_box( FL_ROUNDED3D_DOWNBOX, x + 0.2 * entry->sl_h,
3093                          entry->box_y + 0.25 * entry->sl_h + STR_OFFSET_Y,
3094                          0.5 * entry->sl_h, 0.5 * entry->sl_h,
3095                          entry->state & FL_POPUP_CHECKED ?
3096                          ptp->radio_color : ptp->bg_color, 1 );
3097         else if ( entry->state & FL_POPUP_CHECKED )
3098         {
3099             FL_POINT xp[ 3 ];
3100 
3101             xp[ 0 ].x = x + 0.25 * entry->sl_h;
3102             xp[ 0 ].y = entry->box_y + 0.5 * entry->sl_h + STR_OFFSET_Y;
3103             xp[ 1 ].x = xp[ 0 ].x + 0.2 * entry->sl_h;
3104             xp[ 1 ].y = xp[ 0 ].y + 0.25 * entry->sl_h;
3105             xp[ 2 ].x = xp[ 1 ].x + 0.2 * entry->sl_h;
3106             xp[ 2 ].y = xp[ 1 ].y - 0.5 * entry->sl_h;
3107 
3108             fl_lines( xp, 3, color );
3109 
3110             xp[ 2 ].x += 1;
3111 
3112             fl_lines( xp + 1, 2, color );
3113         }
3114 
3115         w -= ptp->entry_font_size + SYMBOL_PADDING;
3116         x += ptp->entry_font_size + SYMBOL_PADDING;;
3117     }
3118 
3119     /* If there are sub-popups we need some extra space at the end of the
3120        entries and for sub-popups entries a triangle at the end */
3121 
3122     if ( p->has_subs )
3123     {
3124         if ( entry->type == FL_POPUP_SUB )
3125         {
3126             FL_POINT xp[ 4 ];
3127 
3128             xp[ 0 ].x = x + w - 0.125 * entry->sl_h;
3129             xp[ 0 ].y = entry->box_y + 0.5 * entry->box_h;
3130             xp[ 1 ].x = xp[ 0 ].x - 0.35355 * entry->sl_h;
3131             xp[ 1 ].y = xp[ 0 ].y - 0.25 * entry->sl_h;
3132             xp[ 2 ].x = xp[ 1 ].x;
3133             xp[ 2 ].y = xp[ 1 ].y + 0.5 * entry->sl_h;
3134 
3135             fl_polygon( 1, xp, 3, color );
3136         }
3137 
3138         w -= ptp->entry_font_size + SYMBOL_PADDING;
3139     }
3140 
3141     /* Finally it's time to draw the label and accelerator. Underlining is not
3142        done via the "normal" functions since they are too much of a mess...*/
3143 
3144     if ( entry->label && *entry->label )
3145     {
3146         fl_draw_text( FL_ALIGN_LEFT_TOP, x, entry->box_y, w, entry->box_h,
3147                       color, ptp->entry_font_style, ptp->entry_font_size,
3148                       entry->label );
3149         if ( entry->ulpos >= 0 )
3150             fl_rectangle( 1, x + entry->ul_x, entry->box_y + entry->ul_y ,
3151                           entry->ul_w, entry->ul_h, color );
3152     }
3153 
3154     if ( entry->accel && *entry->accel )
3155         fl_draw_text( FL_ALIGN_RIGHT_TOP, x, entry->box_y, w, entry->box_h,
3156                       color, ptp->entry_font_style, ptp->entry_font_size,
3157                       entry->accel );
3158 }
3159 
3160 
3161 /***************************************
3162  * Figure out where to draw a popup window
3163  ***************************************/
3164 
3165 static void
calculate_window_position(FL_POPUP * popup)3166 calculate_window_position( FL_POPUP * popup )
3167 {
3168     FL_POPUP_ENTRY *e;
3169     int x, y;
3170     unsigned int dummy;
3171 
3172     /* If no position has been requested use the mouse position, otherwise
3173        the requested coordinates. Negatve positions mean relative to right
3174        edge or botton of the screen. */
3175 
3176     fl_get_mouse( &x, &y, &dummy );
3177 
3178     if ( ! popup->use_req_pos )
3179     {
3180         popup->x = x + 1;
3181         popup->y = y + 1;
3182     }
3183     else
3184     {
3185         if ( popup->req_x >= 0 )
3186             popup->x = popup->req_x;
3187         else
3188             popup->x = - popup->req_x - popup->w;
3189 
3190         if ( popup->req_y >= 0 )
3191             popup->y = popup->req_y;
3192         else
3193             popup->y = - popup->req_y - popup->h;
3194     }
3195 
3196     /* Try to make sure the popup is fully on the screen (for sub-popups
3197        display to the left of the parent popup if necessary) */
3198 
3199     if ( popup->y + ( int ) popup->h > fl_scrh && ! popup->use_req_pos )
3200         popup->y = popup->y - ( int ) popup->h - 1;
3201 
3202     if ( popup->y + ( int ) popup->h > fl_scrh )
3203         popup->y = FL_max( fl_scrh - ( int ) popup->h, 0 );
3204 
3205     if ( popup->y < 0 )
3206         popup->y = 0;
3207 
3208     if ( popup->x + ( int ) popup->w > fl_scrw )
3209     {
3210         if ( popup->parent != NULL )
3211             popup->x = FL_max( popup->parent->x - ( int ) popup->w, 0 );
3212         else
3213             popup->x = FL_max( fl_scrw - ( int ) popup->w, 0 );
3214     }
3215 
3216     if (    ( e = find_entry( popup, x - popup->x, y - popup->y ) ) != NULL
3217          && ! ( e->state & FL_POPUP_DISABLED ) )
3218         enter_leave( e, 1 );
3219 }
3220 
3221 
3222 /***************************************
3223  * Create the popup's window
3224  ***************************************/
3225 
3226 static void
create_popup_window(FL_POPUP * popup)3227 create_popup_window( FL_POPUP * popup )
3228 {
3229     XSetWindowAttributes xswa;
3230     unsigned long vmask;
3231 
3232     /* Figure out where to open the window */
3233 
3234     calculate_window_position( popup );
3235 
3236     /* Create a new window */
3237 
3238     popup->event_mask =   ExposureMask
3239                         | ButtonPressMask
3240                         | ButtonReleaseMask
3241                         | OwnerGrabButtonMask
3242                         | PointerMotionMask
3243                         | PointerMotionHintMask
3244                         | KeyPressMask;
3245 
3246     xswa.event_mask            = popup->event_mask;
3247     xswa.save_under            = True;
3248     xswa.backing_store         = WhenMapped;
3249     xswa.override_redirect     = True;
3250     xswa.cursor                = popup->cursor;
3251     xswa.border_pixel          = 0;
3252     xswa.colormap              = fli_colormap( fl_vmode );
3253     xswa.do_not_propagate_mask = ButtonPress | ButtonRelease | KeyPress;
3254 
3255     vmask =   CWEventMask     | CWSaveUnder        | CWBackingStore
3256             | CWCursor        | CWBorderPixel      | CWColormap
3257             | CWDontPropagate | CWOverrideRedirect;
3258 
3259     popup->win = XCreateWindow( flx->display, fl_root,
3260                                 popup->x, popup->y, popup->w, popup->h, 0,
3261                                 fli_depth( fl_vmode ), InputOutput,
3262                                 fli_visual( fl_vmode ), vmask, &xswa );
3263 
3264     XSetTransientForHint( flx->display, popup->win, fl_root );
3265 
3266     if ( popup->title )
3267         XStoreName( flx->display, popup->win, popup->title );
3268 
3269     /* Special hack for B&W */
3270 
3271     if ( fli_dithered( fl_vmode ) )
3272     {
3273         XGCValues xgcv;
3274         GC gc;
3275 
3276         xgcv.stipple    = FLI_INACTIVE_PATTERN;
3277         vmask           = GCForeground | GCFont | GCStipple;
3278         xgcv.foreground = fl_get_flcolor( popup->text_off_color );
3279         gc              = XCreateGC( flx->display, popup->win, vmask, &xgcv );
3280         XSetFillStyle( flx->display, gc, FillStippled );
3281     }
3282 
3283     XSetWMColormapWindows( flx->display, fl_root, &popup->win, 1 );
3284 
3285     XMapRaised( flx->display, popup->win );
3286     fl_winset( popup->win );
3287 
3288     grab( popup );
3289 }
3290 
3291 
3292 /***************************************
3293  * Grabs both the pointer and the keyboard
3294  ***************************************/
3295 
3296 static void
grab(FL_POPUP * popup)3297 grab( FL_POPUP * popup )
3298 {
3299     unsigned int evmask = popup->event_mask;
3300 
3301     /* Set the window we're using */
3302 
3303     fl_winset( popup->win );
3304 
3305     /* Get rid of all non-pointer events in event_mask */
3306 
3307     evmask &= ~ ( ExposureMask | KeyPressMask );
3308     XSync( flx->display, False );
3309     XChangeActivePointerGrab( flx->display, evmask,
3310                               popup->cursor, CurrentTime );
3311 
3312     /* Do pointer and keyboard grab */
3313 
3314     if ( XGrabPointer( flx->display, popup->win, False, evmask, GrabModeAsync,
3315                        GrabModeAsync, None, popup->cursor, CurrentTime )
3316                                                                 != GrabSuccess )
3317         M_err( "grab", "Can't grab pointer" );
3318     else if ( XGrabKeyboard( flx->display, popup->win, False, GrabModeAsync,
3319                              GrabModeAsync, CurrentTime ) != GrabSuccess )
3320     {
3321         M_err( "grab", "Can't grab keyboard" );
3322         XUngrabPointer( flx->display, CurrentTime );
3323     }
3324 }
3325 
3326 
3327 /***************************************
3328  * Close a popup window
3329  ***************************************/
3330 
3331 static void
close_popup(FL_POPUP * popup,int do_callback)3332 close_popup( FL_POPUP * popup,
3333              int        do_callback )
3334 {
3335     FL_POPUP_ENTRY *e;
3336     XEvent ev;
3337 
3338     /* Change grab to parent popup window (if there's one), delete popup
3339        window and drop all events for it. Sync before waiting for events
3340        to make sure all events are already in the event queue. */
3341 
3342     if ( popup->parent )
3343         grab( popup->parent );
3344 
3345     XDestroyWindow( flx->display, popup->win );
3346 
3347     XSync( flx->display, False );
3348 
3349     while ( XCheckWindowEvent( flx->display, popup->win, AllEventsMask, &ev ) )
3350         /* empty */ ;
3351 
3352     popup->win = None;
3353 
3354     /* We have to redraw forms or popups that received a Expose event due to
3355        the closing of a sub-popup (at least if the Xserver did not save the
3356        content under the popups window). Not needed for top-level popups
3357        since there the normal event loop takes care of this. */
3358 
3359     if (    popup->parent != NULL
3360          && ! DoesSaveUnders( ScreenOfDisplay( flx->display, fl_screen ) ) )
3361     {
3362         FL_FORM *form;
3363         FL_POPUP *p;
3364 
3365         while ( XCheckMaskEvent( flx->display, ExposureMask, &ev ) != False )
3366             if ( ( form = fl_win_to_form( ( ( XAnyEvent * ) &ev )->window ) )
3367                                                                        != NULL )
3368             {
3369                 fl_winset( form->window );
3370                 fl_redraw_form( form );
3371             }
3372             else
3373                 for ( p = popups; p != NULL; p = p->next )
3374                     if ( ( ( XAnyEvent * ) &ev )->window == p->win )
3375                     {
3376                         fl_winset( p->win );
3377                         draw_popup( p );
3378                     }
3379 
3380         fl_winset( popup->parent->win );
3381     }
3382 
3383     /* Run the leave callback for an active entry if we're asked to */
3384 
3385     for ( e = popup->entries; e != NULL; e = e->next )
3386         if ( e->is_act )
3387         {
3388             if ( do_callback )
3389                 enter_leave( e, 0 );
3390             else
3391                 e->is_act = 0;
3392             break;
3393         }
3394 }
3395 
3396 
3397 /***************************************
3398  * Do all the interaction with the popup, also dealing with other
3399  * background tasks (taking over from the main event loop in forms.c
3400  * while the popup is shown)
3401  ***************************************/
3402 
3403 static FL_POPUP_RETURN *
popup_interaction(FL_POPUP * popup)3404 popup_interaction( FL_POPUP * popup )
3405 {
3406     FL_POPUP *p;
3407     FL_POPUP_ENTRY *e = NULL;
3408     XEvent ev;
3409     int timer_cnt = 0;
3410 
3411     ev.xmotion.time = 0;                  /* for fli_handle_idling() */
3412 
3413     while ( 1 )
3414     {
3415         long msec = fli_context->idle_delta;
3416 
3417         if ( fli_context->timeout_rec )
3418             fli_handle_timeouts( &msec );
3419 
3420         /* Check for new event for the popup window, if there's none deal
3421            with idle tasks */
3422 
3423         if ( ! XCheckWindowEvent( flx->display, popup->win, popup->event_mask,
3424                                   &ev ) )
3425         {
3426             if ( timer_cnt++ % 10 == 0 )
3427             {
3428                 timer_cnt = 0;
3429                 fli_handle_idling( &ev, msec, 1 );
3430                 fl_winset( popup->win );
3431             }
3432 
3433             continue;
3434         }
3435 
3436         timer_cnt = 0;
3437         fli_int.query_age++;
3438 
3439         switch ( ev.type )
3440         {
3441             case Expose :
3442                 draw_popup( popup );
3443                 break;
3444 
3445             case MotionNotify :
3446                 fli_int.mousex    = ev.xmotion.x;
3447                 fli_int.mousey    = ev.xmotion.y;
3448                 fli_int.keymask   = ev.xmotion.state;
3449                 fli_int.query_age = 0;
3450 
3451                 fli_compress_event( &ev, PointerMotionMask );
3452                 popup = handle_motion( popup, ev.xmotion.x, ev.xmotion.y );
3453                 break;
3454 
3455             case ButtonRelease :
3456             case ButtonPress :
3457                 fli_int.mousex    = ev.xbutton.x;
3458                 fli_int.mousey    = ev.xbutton.y;
3459                 fli_int.keymask   = ev.xbutton.state;
3460                 fli_int.query_age = 0;
3461 
3462                 /* Don't react to mouse wheel buttons */
3463 
3464                 if (    ev.xbutton.button == Button4
3465                      || ev.xbutton.button == Button5 )
3466                     break;
3467 
3468                 /* Try to find "active" entry */
3469 
3470                 e = find_entry( popup, ev.xmotion.x, ev.xmotion.y );
3471 
3472                 /* Implement policy: in normal select mode don't react to
3473                    button release except when on a selectable item. React
3474                    to button press only when outside of popup(s). */
3475 
3476                 if (    popup->top_parent->policy == FL_POPUP_NORMAL_SELECT
3477                      && ev.type == ButtonRelease
3478                      && ( e == NULL || e->state & FL_POPUP_DISABLED ) )
3479                     break;
3480 
3481                 if (    ev.type == ButtonPress
3482                      && is_on_popups( popup, ev.xmotion.x, ev.xmotion.y ) )
3483                     break;
3484 
3485                 /* Close all popups, invoking the leave callbacks if no
3486                    selection was made */
3487 
3488                 for ( p = popup; p != NULL; p = p->parent )
3489                     close_popup( p, e == NULL );
3490 
3491                 return handle_selection( e );
3492 
3493             case KeyPress :
3494                 fli_int.mousex    = ev.xkey.x;
3495                 fli_int.mousey    = ev.xkey.y;
3496                 fli_int.keymask   = ev.xkey.state;
3497                 fli_int.query_age = 0;
3498 
3499                 if ( ( p = handle_key( popup, ( XKeyEvent * ) &ev, &e ) ) )
3500                     popup = p;
3501                 else
3502                     return handle_selection( e );
3503         }
3504     }
3505 
3506     return NULL;
3507 }
3508 
3509 
3510 /***************************************
3511  * Returns if the mouse is within a popup window
3512  ***************************************/
3513 
3514 static int
is_on_popups(FL_POPUP * popup,int x,int y)3515 is_on_popups( FL_POPUP * popup,
3516               int        x,
3517               int        y )
3518 {
3519     do
3520     {
3521         if (    x >= 0 && x < ( int ) popup->w
3522              && y >= 0 && y < ( int ) popup->h )
3523             return 1;
3524 
3525         if ( popup->parent == NULL )
3526             break;;
3527 
3528         x += popup->x - popup->parent->x;
3529         y += popup->y - popup->parent->y;
3530     } while ( ( popup = popup->parent ) != NULL );
3531 
3532     return 0;
3533 }
3534 
3535 
3536 /***************************************
3537  * Deals with everything to be done once a selection has been made
3538  ***************************************/
3539 
3540 static FL_POPUP_RETURN *
handle_selection(FL_POPUP_ENTRY * entry)3541 handle_selection( FL_POPUP_ENTRY * entry )
3542 {
3543     FL_POPUP *p;
3544     int cb_result = 1;
3545 
3546     /* If there wasn't a selection or the selected entry is disabled report
3547        "failure" */
3548 
3549     if (    entry == NULL || entry->state & FL_POPUP_DISABLED )
3550         return NULL;
3551 
3552     /* Toggle entries must change state */
3553 
3554     if ( entry->type == FL_POPUP_TOGGLE )
3555     {
3556         if ( entry->state & FL_POPUP_CHECKED )
3557             entry->state &= ~ FL_POPUP_CHECKED;
3558         else
3559             entry->state |= FL_POPUP_CHECKED;
3560     }
3561 
3562     /* For a radio entry that wasn't already set the other radio entries for
3563        the same group must be unset before the selected one becomes set */
3564 
3565     if (    entry->type == FL_POPUP_RADIO
3566          && ! ( entry->state & FL_POPUP_CHECKED ) )
3567     {
3568         FL_POPUP_ENTRY *e;
3569 
3570         for ( e = entry->popup->entries; e != NULL; e = e->next )
3571             if (    e->type == FL_POPUP_RADIO
3572                  && e->group == entry->group )
3573                 e->state &= ~ FL_POPUP_CHECKED;
3574         entry->state |= FL_POPUP_CHECKED;
3575     }
3576 
3577     /* Set up the structure to be returned and call the entries callback
3578        function */
3579 
3580     fli_set_popup_return( entry );
3581 
3582     if ( entry->callback )
3583         cb_result = entry->callback( &entry->popup->top_parent->ret );
3584 
3585     /* Call all popup callback functions (if the selected entry is in a
3586        sub-popup call that of the sub-popup first, then that of the parent,
3587        grand-parent etc.). Interrupt chain of callbacks if one of them
3588        returns FL_IGNORE. */
3589 
3590     for ( p = entry->popup; p && cb_result != FL_IGNORE; p = p->parent )
3591         if ( p->callback )
3592         {
3593             entry->popup->top_parent->ret.popup = p;
3594             cb_result = p->callback( &entry->popup->top_parent->ret );
3595         }
3596 
3597     return ( cb_result != FL_IGNORE && entry->popup ) ?
3598                                         &entry->popup->top_parent->ret : NULL;
3599 }
3600 
3601 
3602 /***************************************
3603  * Deal with motion of the mouse
3604  * Note: the coordinates received are relative to the current popup.
3605  ***************************************/
3606 
3607 static FL_POPUP *
handle_motion(FL_POPUP * popup,int x,int y)3608 handle_motion( FL_POPUP * popup,
3609                int        x,
3610                int        y )
3611 {
3612     FL_POPUP_ENTRY *e,
3613                    *ce;
3614 
3615     /* First deal with the situation where the mouse isn't on the popup */
3616 
3617     if (    x < 0 || x >= ( int ) popup->w
3618          || y < 0 || y >= ( int ) popup->h )
3619     {
3620         FL_POPUP *p;
3621 
3622         /* If there was an active entry make it inactive and call its
3623            leave callback */
3624 
3625         for ( e = popup->entries; e != NULL; e = e->next )
3626             if ( e->is_act )
3627             {
3628                 enter_leave( e, 0 );
3629                 break;
3630             }
3631 
3632         /* Check if we're on a different popup and return if we aren't.
3633            Coordinates must be transformed to relative to the root window. */
3634 
3635         if ( ( p = find_popup( x + popup->x, y + popup->y ) ) == NULL )
3636             return popup;
3637 
3638         /* Otherwise first check if we need to shift the window - coordinates
3639          must now be relative to the new popups window and might be changed by
3640          the routine */
3641 
3642         x += popup->x - p->x;
3643         y += popup->y - p->y;
3644 
3645         motion_shift_window( p, &x, &y );
3646 
3647         /* Also check if we're on the entry for the sub-entry we were on
3648            before. In that case nothing needs to be done yet. Take care:
3649            find_entry() needs to be called with the coordinates transformed to
3650            those relative to the other popup. */
3651 
3652         e = find_entry( p, x, y );
3653 
3654         if ( e != NULL && e->type == FL_POPUP_SUB && e->sub == popup )
3655             return popup;
3656 
3657         /* Otherwise close the current sub-popup, invoking the leave callback
3658            for an activate entry */
3659 
3660         close_popup( popup, 1 );
3661 
3662         /* We might not have ended up on the parent popup but a parent of
3663            the parent, in which case also the parent popup must be closed.
3664            Note that the coordinates must again be transformed so that they
3665            are now relative to that of the parents window. */
3666 
3667         return handle_motion( popup->parent, x + p->x - popup->parent->x,
3668                               y + p->y - popup->parent->y );
3669     }
3670 
3671     /* Check if we need to shift the window */
3672 
3673     motion_shift_window( popup, &x, &y );
3674 
3675     /* Test if the mouse is on a new entry*/
3676 
3677     ce = find_entry( popup, x, y );
3678 
3679     /* If we're still on the same entry as we were on the last call do nothing
3680        (except for the case that we're on a entry for a sub-popup, in that case
3681        we've got onto it via the keyboard and the sub-popup isn't open yet) */
3682 
3683     if ( ce != NULL && ce->is_act )
3684         return ce->type == FL_POPUP_SUB ? open_subpopup( ce ) : popup;
3685 
3686     /* Redraw former active entry as inactive and call its leave callback*/
3687 
3688     for ( e = popup->entries; e != NULL; e = e->next )
3689         if ( e->is_act )
3690         {
3691             enter_leave( e, 0 );
3692             break;
3693         }
3694 
3695     /* If we are on a new, non-disabled entry mark it as active and call its
3696        enter callback. If the new entry is a sub-entry open the corresponding
3697        sub-popup (per default at the same height as the entry and to the
3698        right of it.*/
3699 
3700     if ( ce != NULL && ! ( ce->state & FL_POPUP_DISABLED ) )
3701     {
3702         enter_leave( ce, 1 );
3703 
3704         if ( ce->type == FL_POPUP_SUB )
3705             return open_subpopup( ce );
3706     }
3707 
3708     return popup;
3709 }
3710 
3711 
3712 /***************************************
3713  * Shift popup window if it doesn't fit completely on the screen and the
3714  * user is trying to move the mouse in the direction of the non-visible
3715  * parts of the popups window.
3716  ***************************************/
3717 
3718 static void
motion_shift_window(FL_POPUP * popup,int * x,int * y)3719 motion_shift_window( FL_POPUP * popup,
3720                      int      * x,
3721                      int      * y )
3722 {
3723     FL_POPUP_ENTRY *e;
3724     static long sec   = 0,
3725                 usec  = 0;
3726     long now_sec,
3727          now_usec;
3728     int xr = *x + popup->x,        /* coordinates relative to root window */
3729         yr = *y + popup->y;
3730     int old_x_pos,
3731         old_y_pos;
3732 
3733     /* First check if parts of the popup window are off-screen and the user
3734        is tryng to move the mouse out of the screen into a direction where
3735        non-visible parts are. If not we're done. */
3736 
3737     if (    ( xr > 0           || popup->x >= 0 )
3738          && ( xr < fl_scrw - 1 || popup->x + ( int ) popup->w <= fl_scrw )
3739          && ( yr > 0           || popup->y >= 0 )
3740          && ( yr < fl_scrh - 1 || popup->y + ( int ) popup->h <= fl_scrh ) )
3741         return;
3742 
3743     /* Check if the minimum time delay since last shift is over */
3744 
3745     fl_gettime( &now_sec, &now_usec );
3746 
3747     if ( 1000000 * ( now_sec - sec ) + now_usec - usec < WINDOW_SHIFT_DELAY )
3748         return;
3749 
3750     /* Shift window left/right by a fixed amout of pixels*/
3751 
3752     old_x_pos = popup->x;
3753     if ( xr == fl_scrw - 1 && popup->x + ( int ) popup->w > fl_scrw )
3754         popup->x = FL_max( popup->x - WINDOW_SHIFT,
3755                            fl_scrw - ( int ) popup->w );
3756     else if ( xr == 0 && popup->x < 0 )
3757         popup->x = FL_min( popup->x + WINDOW_SHIFT, 0 );
3758     *x -= popup->x - old_x_pos;
3759 
3760     /* Shift window up/down by one entry*/
3761 
3762     old_y_pos = popup->y;
3763     if ( yr == fl_scrh - 1 && popup->y + ( int ) popup->h > fl_scrh)
3764     {
3765         /* Find first entry that extends below the bottom of the screen
3766            and set the windows y-position so that it is shown completely */
3767 
3768         for ( e = popup->entries; e != NULL; e = e->next )
3769             if (    e->type != FL_POPUP_LINE
3770                  && ! (  e->state & FL_POPUP_HIDDEN )
3771                  && e->box_y + ( int ) e->box_h - 1 > *y )
3772                 break;
3773 
3774         if ( e != NULL )
3775             popup->y = fl_scrh - e->box_y - ( int ) e->box_h;
3776     }
3777     else if ( yr == 0 && popup->y < 0 )
3778     {
3779         /* Find first entry that's at least one pixel below the upper screen
3780            border */
3781 
3782         for ( e = popup->entries; e != NULL; e = e->next )
3783             if (    ! (  e->state & FL_POPUP_HIDDEN )
3784                     && e->box_y >= *y )
3785                 break;
3786 
3787         /* Go to the one before that which isn't a line or hidden and make it
3788            the one at the very top of the screen */
3789 
3790         if ( e != NULL )
3791             do
3792                 e = e->prev;
3793             while (    e != NULL
3794                     && ( e->type == FL_POPUP_LINE
3795                          || e->state & FL_POPUP_HIDDEN ) );
3796 
3797         if ( e == NULL )
3798             popup->y = 0;
3799         else
3800             popup->y = - e->box_y;
3801     }
3802     *y -= popup->y - old_y_pos;
3803 
3804     /* Move the window to the new position */
3805 
3806     if ( popup->x != old_x_pos || popup->y != old_y_pos )
3807     {
3808         XMoveWindow( flx->display, popup->win, popup->x, popup->y );
3809 
3810         sec  = now_sec;
3811         usec = now_usec;
3812     }
3813 }
3814 
3815 
3816 /***************************************
3817  * Handle keyboard input. If NULL gets returned either a selection was
3818  * made (in that case 'entry' points to the selected entry) or dealing
3819  * with the popup is to be stopped. If non-NULL is returned it's the
3820  * popup we now have to deal with.
3821  ***************************************/
3822 
3823 static FL_POPUP *
handle_key(FL_POPUP * popup,XKeyEvent * ev,FL_POPUP_ENTRY ** entry)3824 handle_key( FL_POPUP        * popup,
3825             XKeyEvent       * ev,
3826             FL_POPUP_ENTRY ** entry )
3827 {
3828     KeySym keysym = NoSymbol;
3829     char buf[ 16 ];
3830     FL_POPUP_ENTRY *e,
3831                    *ce;
3832 
3833     XLookupString( ev, buf, sizeof buf, &keysym, 0 );
3834 
3835     /* Start of with checking for shortcut keys, they may have overridden some
3836        of the keys normally used */
3837 
3838     if ( ( e = handle_shortcut( popup, keysym, ev->state ) ) != NULL )
3839     {
3840         /* Special handling for sub-popup entries: shortcut key doesn't
3841            result in a selection but in the corresponding sub-popup being
3842            opened (if it isn't already shown) */
3843 
3844         if ( e->type == FL_POPUP_SUB )
3845         {
3846             if ( e->sub->win != None )
3847                 return popup;
3848 
3849             for ( ce = popup->entries; ce != NULL; ce = ce->next )
3850                 if ( ce->is_act )
3851                 {
3852                     if ( ce != e )
3853                         enter_leave( ce, 0 );
3854                     break;
3855                 }
3856 
3857             if ( ce != e )
3858                 enter_leave( e, 1 );
3859 
3860             return open_subpopup( e );
3861         }
3862 
3863         /* Close all popups. Leave callbacks are not invoked since a
3864            selection was made */
3865 
3866         while ( popup )
3867         {
3868             close_popup( popup, 0 );
3869             popup = popup->parent;
3870         }
3871 
3872         *entry = e;
3873         return NULL;
3874     }
3875 
3876     /* <Esc> and <Cancel> close the current popup, invoking leave callback */
3877 
3878     if ( keysym == XK_Escape || keysym == XK_Cancel )
3879     {
3880         close_popup( popup, 1 );
3881         return popup->parent;
3882     }
3883 
3884     /* Try to find the "active" entry (there may not exist one) */
3885 
3886     for ( ce = popup->entries; ce != NULL; ce = ce->next )
3887         if ( ce->is_act )
3888             break;
3889 
3890     /* <Return> does nothing (returning the original popup) if there isn't
3891        an active entry. If we're on a sub-entry open the sup-popup. Otherwise
3892        return indicating that the active entry was selected after closing all
3893        popups. */
3894 
3895     if ( keysym == XK_Return )
3896     {
3897         if ( ce == NULL )
3898             return popup;
3899 
3900         /* If we're on a sun-popup entry open the corresponding sub-pupop */
3901 
3902         if ( ce->type == FL_POPUP_SUB )
3903             return open_subpopup( ce );
3904 
3905         /* Otherwise we got a selection, so close all popups, invoke no leave
3906            callbacks (since selection was made) and return the selected entry */
3907 
3908         while ( popup )
3909         {
3910             close_popup( popup, 0 );
3911             popup = popup->parent;
3912         }
3913 
3914         *entry = ce;
3915         return NULL;
3916     }
3917 
3918     /* The <Right> key only does something when we're on a sub-entry. In
3919        this case the sub-popup is opened and the top-most usable entry is
3920        activated. */
3921 
3922     if ( IsRight( keysym ) )
3923     {
3924         if ( ce == NULL || ce->type != FL_POPUP_SUB )
3925             return popup;
3926 
3927         for ( e = ce->sub->entries; e != NULL; e = e->next )
3928             if ( IS_ACTIVATABLE( e ) )
3929                 break;
3930 
3931         if ( e != NULL )
3932             enter_leave( e, 1 );
3933 
3934         return open_subpopup( ce );
3935     }
3936 
3937     /* The <Left> key only has a meaning if we're in a sub-popup. In this
3938        case the sub-popup gets closed (leave callbacks get invoked). */
3939 
3940     if ( IsLeft( keysym ) )
3941     {
3942         if ( popup->parent == NULL )
3943             return popup;
3944 
3945         close_popup( popup, 1 );
3946         return popup->parent;
3947     }
3948 
3949     /* The <Down> key moves down to the next "activatable" entry (with
3950        wrap-around). If no entry was active yet the first in the popup
3951        is activated. */
3952 
3953     if ( IsDown( keysym ) )
3954     {
3955         e = NULL;
3956 
3957         if ( ce != NULL )
3958             for ( e = ce->next; e != NULL; e = e->next )
3959                 if ( IS_ACTIVATABLE( e ) )
3960                     break;
3961 
3962         if ( e == NULL )
3963             for ( e = popup->entries; e != ce; e = e->next )
3964                 if ( IS_ACTIVATABLE( e ) )
3965                     break;
3966 
3967         if ( e != NULL )
3968             key_shift_window( popup, e );
3969 
3970         if ( e != ce )
3971         {
3972             if ( ce != NULL )
3973                 enter_leave( ce, 0 );
3974 
3975             enter_leave( e, 1 );
3976         }
3977 
3978         return popup;
3979     }
3980 
3981     /* The <Up> key moves up to the previous "activatable" entry (with
3982        wrap-around). If no entry was active yet the last in the popup
3983        is activated. */
3984 
3985     if ( IsUp( keysym ) )
3986     {
3987         FL_POPUP_ENTRY * ei;
3988 
3989         for ( e = NULL, ei = popup->entries; ei != ce; ei = ei->next )
3990             if ( IS_ACTIVATABLE( ei ) )
3991                 e = ei;
3992 
3993         if ( e == NULL && ce != NULL )
3994             for ( ei = ce->next; ei != NULL; ei = ei->next )
3995                 if ( IS_ACTIVATABLE( ei ) )
3996                     e = ei;
3997 
3998         if ( ce != NULL && e != NULL )
3999         {
4000             key_shift_window( popup, e );
4001             enter_leave( ce, 0 );
4002         }
4003 
4004         if ( e != NULL )
4005             enter_leave( e, 1 );
4006 
4007         return popup;
4008     }
4009 
4010     /* The <End> key moves to the last "activatable" in the popup */
4011 
4012     if ( IsEnd( keysym ) )
4013     {
4014         FL_POPUP_ENTRY *ei;
4015 
4016         for ( e = NULL, ei = ce != NULL ? ce->next : popup->entries; ei != NULL;
4017               ei = ei->next )
4018             if ( IS_ACTIVATABLE( ei ) )
4019                 e = ei;
4020 
4021         if ( ce != NULL && e != NULL )
4022             enter_leave( ce, 0 );
4023 
4024         if ( e != NULL )
4025         {
4026             key_shift_window( popup, e );
4027             enter_leave( e, 1 );
4028         }
4029 
4030         return popup;
4031     }
4032 
4033     /* The <Home> key moves to the first "activatable" in the popup */
4034 
4035     if ( IsHome( keysym ) )
4036     {
4037         for ( e = popup->entries; e != ce; e = e->next )
4038             if ( IS_ACTIVATABLE( e ) )
4039                 break;
4040 
4041         if ( ce != NULL && e != ce )
4042             enter_leave( ce, 0 );
4043 
4044         if ( e != ce )
4045         {
4046             key_shift_window( popup, e );
4047             enter_leave( e, 1 );
4048         }
4049 
4050         return popup;
4051     }
4052 
4053     /* All other keys do nothing, returning the original popup indicates that */
4054 
4055     return popup;
4056 }
4057 
4058 
4059 /***************************************
4060  * Shift popup window if it doesn't fit completely on the screen and the
4061  * user pressed a key that takes us to a non-visible entry of the window.
4062  ***************************************/
4063 
4064 static void
key_shift_window(FL_POPUP * popup,FL_POPUP_ENTRY * entry)4065 key_shift_window( FL_POPUP       * popup,
4066                   FL_POPUP_ENTRY * entry )
4067 {
4068     /* Test if the window is only partially visible on the window (in vertical
4069        direction) and the entry we're supposed to go to isn't in the visible
4070        part of the window. If not we're done.*/
4071 
4072     if (    ( popup->y >= 0 && popup->y + ( int ) popup->h < fl_scrh )
4073          || (    popup->y + entry->box_y >= 0
4074               && popup->y + entry->box_y + ( int ) entry->box_h < fl_scrh ) )
4075         return;
4076 
4077     /* Shift window up/down */
4078 
4079     if ( popup->y + entry->box_y < 0 )
4080         popup->y = - entry->box_y + 1;
4081     else
4082         popup->y = fl_scrh - entry->box_y - ( int ) entry->box_h - 1;
4083 
4084     XMoveWindow( flx->display, popup->win, popup->x, popup->y );
4085 }
4086 
4087 
4088 /***************************************
4089  * Open a sub-popup
4090  ***************************************/
4091 
4092 static FL_POPUP *
open_subpopup(FL_POPUP_ENTRY * entry)4093 open_subpopup( FL_POPUP_ENTRY * entry )
4094 {
4095     FL_POPUP *popup = entry->popup;
4096     int offset =   FL_abs( popup->top_parent->bw )
4097                  + ( popup->top_parent->bw > 0 ? 1 : 0 ) + OUTER_PADDING_TOP;
4098 
4099     /* Set the position of the new sub-popup. Normally show it to the right of
4100        the parent popup, but if this is a sub-popup of a sub-popup and the
4101        parent sub-pupop is to the left of its parent (because there wasn't
4102        enough room on the right side) position it also to the left. Vertically
4103        put it at the same height as the entry its opened up from. But the
4104        function for opening the window for the sub-popup may overrule these
4105        settings if there isn't enough room on the screen. */
4106 
4107     if (    popup->parent == NULL
4108          || popup->x > popup->parent->x )
4109         fl_popup_set_position( entry->sub, popup->x + popup->w,
4110                                popup->y + entry->box_y - offset );
4111     else
4112     {
4113         if ( entry->sub->need_recalc )
4114             recalc_popup( entry->sub );
4115 
4116         fl_popup_set_position( entry->sub, popup->x - entry->sub->w,
4117                                popup->y + entry->box_y - offset );
4118     }
4119 
4120     draw_popup( entry->sub );
4121     return entry->sub;
4122 }
4123 
4124 
4125 /***************************************
4126  * Check popup entries for a shortcut identical to a pressed key
4127  ***************************************/
4128 
4129 static FL_POPUP_ENTRY *
handle_shortcut(FL_POPUP * popup,long keysym,unsigned int keymask)4130 handle_shortcut( FL_POPUP     * popup,
4131                  long           keysym,
4132                  unsigned int   keymask )
4133 {
4134     FL_POPUP_ENTRY *e;
4135     long *sc;
4136 
4137     if ( controlkey_down( keymask ) && keysym >= 'a' && keysym <= 'z' )
4138         keysym = toupper( keysym );
4139     keysym +=   ( controlkey_down( keymask ) ? FL_CONTROL_MASK : 0 )
4140               + ( metakey_down( keymask )    ? FL_ALT_MASK     : 0 );
4141 
4142     /* Look at the shortcuts for all of the entries of the current popup,
4143        then those of the parent etc. */
4144 
4145     while ( popup != NULL )
4146     {
4147         for ( e = popup->entries; e != NULL; e = e->next )
4148             if ( IS_ACTIVATABLE( e ) && e->shortcut != NULL )
4149                 for ( sc = e->shortcut; *sc != 0; sc++ )
4150                     if ( *sc == keysym )
4151                         return e;
4152         popup = popup->parent;
4153     }
4154 
4155     return NULL;
4156 }
4157 
4158 
4159 /***************************************
4160  * Handle entering (act = 1) or leaving (act = 0) of an entry
4161  ***************************************/
4162 
4163 static void
enter_leave(FL_POPUP_ENTRY * entry,int act)4164 enter_leave( FL_POPUP_ENTRY * entry,
4165              int              act )
4166 {
4167     /* Mark entry as entered or left */
4168 
4169     entry->is_act = act;
4170 
4171     /* Redraw the entry (at least while popup is shown) */
4172 
4173     if ( entry->popup->win != None )
4174         draw_entry( entry );
4175 
4176     /* If a callback for the situation exists invoke it */
4177 
4178     if (    (   act && entry->enter_callback == NULL )
4179          || ( ! act && entry->leave_callback == NULL ) )
4180         return;
4181 
4182     fli_set_popup_return( entry );
4183 
4184     if ( act )
4185         entry->enter_callback( &entry->popup->top_parent->ret );
4186     else
4187         entry->leave_callback( &entry->popup->top_parent->ret );
4188 }
4189 
4190 
4191 /***************************************
4192  * Try to find a (shown) popup from its position (coordinates must be
4193  * relative to the root window)
4194  ***************************************/
4195 
4196 static FL_POPUP *
find_popup(int x,int y)4197 find_popup( int x,
4198             int y )
4199 {
4200     FL_POPUP *p;
4201 
4202     for ( p = popups; p != NULL; p = p->next )
4203     {
4204         if ( p->win == None )
4205             continue;
4206 
4207         if (    x >= p->x && x < p->x + ( int ) p->w
4208              && y >= p->y && y < p->y + ( int ) p->h )
4209             return p;
4210     }
4211 
4212     return NULL;
4213 }
4214 
4215 
4216 /***************************************
4217  * Try to find an entry in a popup from its position (coordinates must be
4218  * relative to the popup's window)
4219  ***************************************/
4220 
4221 static FL_POPUP_ENTRY *
find_entry(FL_POPUP * popup,int x,int y)4222 find_entry( FL_POPUP * popup,
4223             int        x,
4224             int        y )
4225 {
4226     FL_POPUP_ENTRY *e;
4227 
4228     for ( e = popup->entries; e != NULL; e = e->next )
4229     {
4230         if ( e->type == FL_POPUP_LINE || e->state & FL_POPUP_HIDDEN )
4231             continue;
4232 
4233         if (    x >= 0        && x < ( int ) popup->w
4234              && y >= e->box_y && y < e->box_y + ( int ) e->box_h )
4235             return e;
4236     }
4237 
4238     return NULL;
4239 }
4240 
4241 
4242 /***************************************
4243  * Makes sure the top-parent pointers in sub-popups are set correctly
4244  ***************************************/
4245 
4246 static void
setup_subpopups(FL_POPUP * popup)4247 setup_subpopups( FL_POPUP * popup )
4248 {
4249     FL_POPUP *p;
4250     FL_POPUP_ENTRY *e;
4251 
4252     /* The top-parent of sub-popups must be set to the top-most  popup since
4253        it gets all its drawing properties etc. from that one. In normal popups
4254        the top_parent entry just points back to the popup itself. */
4255 
4256     if ( ( p = popup->parent ) != NULL )
4257     {
4258         while ( p->parent != NULL )
4259             p = p->parent;
4260         popup->top_parent = p;
4261     }
4262     else
4263         popup->top_parent = popup;
4264 
4265     for ( e = popup->entries; e != NULL; e = e->next )
4266         if ( e->type == FL_POPUP_SUB )
4267             setup_subpopups( e->sub );
4268 }
4269 
4270 
4271 /***************************************
4272  * Removes all backspace characters from a string
4273  * and replaces all tabs by spaces.
4274  ***************************************/
4275 
4276 static char *
cleanup_string(char * s)4277 cleanup_string( char *s )
4278 {
4279     char *c;
4280 
4281     if ( ! s || ! *s )
4282         return s;
4283 
4284     /* Remove all backspace charscters */
4285 
4286     c = s;
4287     while ( ( c = strchr( c, '\b' ) ) )
4288         memmove( c, c + 1, strlen( c ) );
4289 
4290     /* Replace tabs by single blanks */
4291 
4292     c = s;
4293     while ( ( c = strchr( c, '\t' ) ) )
4294         *c++ = ' ';
4295 
4296     return s;
4297 }
4298 
4299 
4300 /***************************************
4301  * Set need_recalc flag for a popup and all its sub-popups
4302  ***************************************/
4303 
4304 static void
set_need_recalc(FL_POPUP * popup)4305 set_need_recalc( FL_POPUP * popup )
4306 {
4307     FL_POPUP_ENTRY *e;
4308 
4309     popup->need_recalc = 1;
4310 
4311     for ( e = popup->entries; e != NULL; e = e->next )
4312         if ( e->type == FL_POPUP_SUB )
4313             set_need_recalc( e->sub );
4314 }
4315 
4316 
4317 /***************************************
4318  * Function called by fl_initialize() to set up defaults
4319  ***************************************/
4320 
4321 void
fli_popup_init(void)4322 fli_popup_init( void )
4323 {
4324     fli_popup_finish( );      /* just to make sure... */
4325 
4326     popup_entry_font_style = FL_NORMAL_STYLE;
4327     popup_title_font_style = FL_EMBOSSED_STYLE;
4328 
4329 #ifdef __sgi
4330     popup_entry_font_size  = FL_SMALL_SIZE,
4331     popup_title_font_size  = FL_SMALL_SIZE;
4332 #else
4333     popup_entry_font_size  = FL_NORMAL_SIZE;
4334     popup_title_font_size  = FL_NORMAL_SIZE;
4335 #endif
4336 
4337     popup_bg_color       = FL_MCOL;
4338     popup_on_color       = FL_BOTTOM_BCOL;
4339     popup_title_color    = FL_BLACK;
4340     popup_text_color     = FL_BLACK;
4341     popup_text_on_color  = FL_WHITE;
4342     popup_text_off_color = FL_INACTIVE_COL;
4343     popup_radio_color    = FL_BLUE;
4344 
4345     popup_bw = (    fli_cntl.borderWidth
4346                  && FL_abs( fli_cntl.borderWidth ) <= FL_MAX_BW ) ?
4347                fli_cntl.borderWidth : FL_BOUND_WIDTH;
4348     popup_cursor = XC_sb_right_arrow;
4349 
4350     popup_policy = FL_POPUP_NORMAL_SELECT;
4351 }
4352 
4353 
4354 /***************************************
4355  * Function called by fl_finish() to release all memory used by popups.
4356  ***************************************/
4357 
4358 void
fli_popup_finish(void)4359 fli_popup_finish( void )
4360 {
4361     FL_POPUP *p;
4362 
4363     /* Delete all top-level popups, sub-popus get taken care of automatically */
4364 
4365     while ( popups )
4366         for ( p = popups; p != NULL; p = p->next )
4367             if ( p->parent == NULL )
4368             {
4369                 fl_popup_delete( p );
4370                 break;
4371             }
4372 }
4373 
4374 
4375 /***************************************
4376  * Checks if a popup exists, returns 0 if it does, 1 otherwise.
4377  ***************************************/
4378 
4379 int
fli_check_popup_exists(FL_POPUP * popup)4380 fli_check_popup_exists( FL_POPUP * popup )
4381 {
4382     FL_POPUP *p;
4383 
4384     for ( p = popups; p != NULL; p = p->next )
4385         if ( popup == p )
4386             return 0;
4387 
4388     return 1;
4389 }
4390 
4391 
4392 /***************************************
4393  * Checks if an entry exists, returns 0 if it does, 1 otherwise.
4394  ***************************************/
4395 
4396 int
fli_check_popup_entry_exists(FL_POPUP_ENTRY * entry)4397 fli_check_popup_entry_exists( FL_POPUP_ENTRY * entry )
4398 {
4399     FL_POPUP_ENTRY *e;
4400 
4401     if ( entry == NULL )
4402         return 1;
4403 
4404     if ( fli_check_popup_exists( entry->popup ) )
4405         return 1;
4406 
4407     for ( e = entry->popup->entries; e != NULL; e = e->next )
4408         if ( entry == e )
4409             return 0;
4410 
4411     return 1;
4412 }
4413 
4414 
4415 /***************************************
4416  * Set up the return structure of a popup for a certain entry
4417  ***************************************/
4418 
4419 FL_POPUP_RETURN *
fli_set_popup_return(FL_POPUP_ENTRY * entry)4420 fli_set_popup_return( FL_POPUP_ENTRY * entry )
4421 {
4422     entry->popup->top_parent->ret.text      = entry->text;
4423     entry->popup->top_parent->ret.label     = entry->label;
4424     entry->popup->top_parent->ret.accel     = entry->accel;
4425     entry->popup->top_parent->ret.val       = entry->val;
4426     entry->popup->top_parent->ret.user_data = entry->user_data;
4427     entry->popup->top_parent->ret.entry     = entry;
4428     entry->popup->top_parent->ret.popup     = entry->popup;
4429 
4430     return &entry->popup->top_parent->ret;
4431 }
4432 
4433 
4434 /***************************************
4435  * Reset the popups counter to 0
4436  ***************************************/
4437 
4438 void
fli_popup_reset_counter(FL_POPUP * popup)4439 fli_popup_reset_counter( FL_POPUP *popup )
4440 {
4441     popup->counter = 0;
4442 }
4443 
4444 
4445 /*
4446  * Local variables:
4447  * tab-width: 4
4448  * indent-tabs-mode: nil
4449  * End:
4450  */
4451