This commit was manufactured by cvs2svn to create branch 'XEmacs-21_4'.
[chise/xemacs-chise.git-] / src / toolbar-gtk.c
1 /* toolbar implementation -- X interface.
2    Copyright (C) 1995 Board of Trustees, University of Illinois.
3    Copyright (C) 1995 Sun Microsystems, Inc.
4    Copyright (C) 1995, 1996 Ben Wing.
5    Copyright (C) 1996 Chuck Thompson.
6
7 This file is part of XEmacs.
8
9 XEmacs is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by the
11 Free Software Foundation; either version 2, or (at your option) any
12 later version.
13
14 XEmacs is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17 for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with XEmacs; see the file COPYING.  If not, write to
21 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.  */
23
24 /* Synched up with: Not in FSF. */
25
26 #include <config.h>
27 #include "lisp.h"
28
29 #include "console-gtk.h"
30 #include "glyphs-gtk.h"
31 #include "objects-gtk.h"
32 #include "gtk-xemacs.h"
33 #include "gccache-gtk.h"
34
35 #include "faces.h"
36 #include "frame.h"
37 #include "toolbar.h"
38 #include "window.h"
39
40 extern GdkGC *gtk_get_gc (struct device *d, Lisp_Object font, Lisp_Object fg, Lisp_Object bg,
41                           Lisp_Object bg_pmap, Lisp_Object lwidth);
42
43 static GdkGC *get_toolbar_gc (struct frame *f)
44 {
45   Lisp_Object fg, bg;
46   Lisp_Object frame;
47
48   XSETFRAME (frame, f);
49
50   fg = Fspecifier_instance (Fget (Vtoolbar_face, Qforeground, Qnil), frame, Qnil, Qnil);
51   bg = Fspecifier_instance (Fget (Vtoolbar_face, Qbackground, Qnil), frame, Qnil, Qnil);
52                                    
53   /* Need to swap the foreground/background here or most themes look bug ugly */
54   return (gtk_get_gc (XDEVICE (FRAME_DEVICE (f)), Qnil, bg, fg, Qnil, Qnil));
55 }
56
57 static void
58 gtk_draw_blank_toolbar_button (struct frame *f, int x, int y, int width,
59                                int height, int threed, int border_width,
60                                int vertical)
61 {
62   GtkXEmacs *ef = GTK_XEMACS (FRAME_GTK_TEXT_WIDGET (f));
63   int sx = x, sy = y, swidth = width, sheight = height;
64   GdkWindow *x_win = GTK_WIDGET (ef)->window;
65   GdkGC *background_gc = get_toolbar_gc (f);
66
67   if (vertical)
68     {
69       sx += border_width;
70       swidth -= 2 * border_width;
71     }
72   else
73     {
74       sy += border_width;
75       sheight -= 2 * border_width;
76     }
77
78   /* Blank the entire area. */
79   gdk_draw_rectangle (x_win, background_gc, TRUE, sx, sy, swidth, sheight);
80
81   /* Draw the outline. */
82   if (threed)
83     gtk_output_shadows (f, sx, sy, swidth, sheight, 2);
84
85   /* Do the border */
86   gdk_draw_rectangle (x_win, background_gc, TRUE, x, y,
87                       (vertical ? border_width : width),
88                       (vertical ? height : border_width));
89   gdk_draw_rectangle (x_win, background_gc, TRUE,
90                       (vertical ? sx + swidth : x),
91                       (vertical ? y : sy + sheight),
92                       (vertical ? border_width : width),
93                       (vertical ? height : border_width));
94 }
95
96 static void
97 gtk_output_toolbar_button (struct frame *f, Lisp_Object button)
98 {
99   int shadow_thickness = 2;
100   int x_adj, y_adj, width_adj, height_adj;
101   GdkWindow *x_win = FRAME_GTK_TEXT_WIDGET (f)->window;
102   GdkGC *background_gc = get_toolbar_gc (f);
103   Lisp_Object instance, frame, window, glyph;
104   struct toolbar_button *tb = XTOOLBAR_BUTTON (button);
105   struct Lisp_Image_Instance *p;
106   struct window *w;
107   int vertical = tb->vertical;
108   int border_width = tb->border_width;
109
110   if (vertical)
111     {
112       x_adj = border_width;
113       width_adj = - 2 * border_width;
114       y_adj = 0;
115       height_adj = 0;
116     }
117   else
118     {
119       x_adj = 0;
120       width_adj = 0;
121       y_adj = border_width;
122       height_adj = - 2 * border_width;
123     }
124
125   XSETFRAME (frame, f);
126   window = FRAME_LAST_NONMINIBUF_WINDOW (f);
127   w = XWINDOW (window);
128
129   glyph = get_toolbar_button_glyph (w, tb);
130
131   if (tb->enabled)
132     {
133       if (tb->down)
134         {
135           shadow_thickness = -2;
136         }
137       else
138         {
139           shadow_thickness = 2;
140         }
141     }
142   else
143     {
144       shadow_thickness = 0;
145     }
146
147   background_gc = get_toolbar_gc (f);
148
149   /* Clear the entire area. */
150   gdk_draw_rectangle (x_win, background_gc, TRUE,
151                       tb->x + x_adj,
152                       tb->y + y_adj,
153                       tb->width + width_adj,
154                       tb->height + height_adj);
155
156   /* Draw the outline. */
157   if (shadow_thickness)
158     gtk_output_shadows (f, tb->x + x_adj, tb->y + y_adj,
159                         tb->width + width_adj, tb->height + height_adj,
160                         shadow_thickness);
161
162   /* Do the border. */
163   gdk_draw_rectangle (x_win, background_gc, TRUE, tb->x, tb->y,
164                       (vertical ? border_width : tb->width),
165                       (vertical ? tb->height : border_width));
166
167   gdk_draw_rectangle (x_win, background_gc, TRUE,
168                       (vertical ? tb->x + tb->width - border_width : tb->x),
169                       (vertical ? tb->y : tb->y + tb->height - border_width),
170                       (vertical ? border_width : tb->width),
171                       (vertical ? tb->height : border_width));
172
173   background_gc = get_toolbar_gc (f);
174
175   /* #### It is currently possible for users to trash us by directly
176      changing the toolbar glyphs.  Avoid crashing in that case. */
177   if (GLYPHP (glyph))
178     instance = glyph_image_instance (glyph, window, ERROR_ME_NOT, 1);
179   else
180     instance = Qnil;
181
182   if (IMAGE_INSTANCEP (instance))
183     {
184       int width = tb->width + width_adj - shadow_thickness * 2;
185       int height = tb->height + height_adj - shadow_thickness * 2;
186       int x_offset = x_adj + shadow_thickness;
187       int y_offset = y_adj + shadow_thickness;
188
189       p = XIMAGE_INSTANCE (instance);
190
191       if (IMAGE_INSTANCE_PIXMAP_TYPE_P (p))
192         {
193           if (width > (int) IMAGE_INSTANCE_PIXMAP_WIDTH (p))
194             {
195               x_offset += ((int) (width - IMAGE_INSTANCE_PIXMAP_WIDTH (p))
196                            / 2);
197               width = IMAGE_INSTANCE_PIXMAP_WIDTH (p);
198             }
199           if (height > (int) IMAGE_INSTANCE_PIXMAP_HEIGHT (p))
200             {
201               y_offset += ((int) (height - IMAGE_INSTANCE_PIXMAP_HEIGHT (p))
202                            / 2);
203               height = IMAGE_INSTANCE_PIXMAP_HEIGHT (p);
204             }
205
206           gtk_output_gdk_pixmap (f, XIMAGE_INSTANCE (instance), tb->x + x_offset,
207                                  tb->y + y_offset, 0, 0, 0, 0, width, height,
208                                  0, 0, 0, background_gc);
209         }
210       else if (IMAGE_INSTANCE_TYPE (p) == IMAGE_TEXT)
211         {
212           /* #### We need to make the face used configurable. */
213           struct face_cachel *cachel =
214             WINDOW_FACE_CACHEL (w, DEFAULT_INDEX);
215           struct display_line dl;
216           Lisp_Object string = IMAGE_INSTANCE_TEXT_STRING (p);
217           unsigned char charsets[NUM_LEADING_BYTES];
218           Emchar_dynarr *buf;
219           struct font_metric_info fm;
220
221           /* This could be true if we were called via the Expose event
222              handler.  Mark the button as dirty and return
223              immediately. */
224           if (f->window_face_cache_reset)
225             {
226               tb->dirty = 1;
227               MARK_TOOLBAR_CHANGED;
228               return;
229             }
230           buf = Dynarr_new (Emchar);
231           convert_bufbyte_string_into_emchar_dynarr
232             (XSTRING_DATA (string), XSTRING_LENGTH (string), buf);
233           find_charsets_in_emchar_string (charsets, Dynarr_atp (buf, 0),
234                                           Dynarr_length (buf));
235           ensure_face_cachel_complete (cachel, window, charsets);
236           face_cachel_charset_font_metric_info (cachel, charsets, &fm);
237
238           dl.ascent = fm.ascent;
239           dl.descent = fm.descent;
240           dl.ypos = tb->y + y_offset + fm.ascent;
241
242           if (fm.ascent + fm.descent <= height)
243             {
244               dl.ypos += (height - fm.ascent - fm.descent) / 2;
245               dl.clip = 0;
246             }
247           else
248             {
249               dl.clip = fm.ascent + fm.descent - height;
250             }
251
252           gtk_output_string (w, &dl, buf, tb->x + x_offset, 0, 0, width,
253                              DEFAULT_INDEX, 0, 0, 0, 0);
254           Dynarr_free (buf);
255         }
256
257       /* We silently ignore the image if it isn't a pixmap or text. */
258     }
259
260   tb->dirty = 0;
261 }
262
263 static int
264 gtk_get_button_size (struct frame *f, Lisp_Object window,
265                      struct toolbar_button *tb, int vert, int pos)
266 {
267   int shadow_thickness = 2;
268   int size;
269
270   if (tb->blank)
271     {
272       if (!NILP (tb->down_glyph))
273         size = XINT (tb->down_glyph);
274       else
275         size = DEFAULT_TOOLBAR_BLANK_SIZE;
276     }
277   else
278     {
279       struct window *w = XWINDOW (window);
280       Lisp_Object glyph = get_toolbar_button_glyph (w, tb);
281
282       /* Unless, of course, the user has done something stupid like
283          change the glyph out from under us.  Use a blank placeholder
284          in that case. */
285       if (NILP (glyph))
286         return XINT (f->toolbar_size[pos]);
287
288       if (vert)
289         size = glyph_height (glyph, window);
290       else
291         size = glyph_width (glyph, window);
292     }
293
294   if (!size)
295     {
296       /* If the glyph doesn't have a size we'll insert a blank
297          placeholder instead. */
298       return XINT (f->toolbar_size[pos]);
299     }
300
301   size += shadow_thickness * 2;
302
303   return (size);
304 }
305
306 #define GTK_OUTPUT_BUTTONS_LOOP(left)                                   \
307   do {                                                                  \
308     while (!NILP (button))                                              \
309       {                                                                 \
310         struct toolbar_button *tb = XTOOLBAR_BUTTON (button);           \
311         int size, height, width;                                        \
312                                                                         \
313         if (left && tb->pushright)                                      \
314           break;                                                        \
315                                                                         \
316         size = gtk_get_button_size (f, window, tb, vert, pos);          \
317                                                                         \
318         if (vert)                                                       \
319           {                                                             \
320             width = bar_width;                                          \
321             if (y + size > max_pixpos)                                  \
322               height = max_pixpos - y;                                  \
323             else                                                        \
324               height = size;                                            \
325           }                                                             \
326         else                                                            \
327           {                                                             \
328             if (x + size > max_pixpos)                                  \
329               width = max_pixpos - x;                                   \
330             else                                                        \
331               width = size;                                             \
332             height = bar_height;                                        \
333           }                                                             \
334                                                                         \
335         if (tb->x != x                                                  \
336             || tb->y != y                                               \
337             || tb->width != width                                       \
338             || tb->height != height                                     \
339             || tb->dirty)                                               \
340           {                                                             \
341             if (width && height)                                        \
342               {                                                         \
343                 tb->x = x;                                              \
344                 tb->y = y;                                              \
345                 tb->width = width;                                      \
346                 tb->height = height;                                    \
347                 tb->border_width = border_width;                        \
348                 tb->vertical = vert;                                    \
349                                                                         \
350                 if (tb->blank || NILP (tb->up_glyph))                   \
351                   {                                                     \
352                     int threed = (EQ (Qt, tb->up_glyph) ? 1 : 0);       \
353                     gtk_draw_blank_toolbar_button (f, x, y, width,      \
354                                                  height, threed,        \
355                                                  border_width, vert);   \
356                   }                                                     \
357                 else                                                    \
358                   gtk_output_toolbar_button (f, button);                \
359               }                                                         \
360           }                                                             \
361                                                                         \
362         if (vert)                                                       \
363           y += height;                                                  \
364         else                                                            \
365           x += width;                                                   \
366                                                                         \
367         if ((vert && y == max_pixpos) || (!vert && x == max_pixpos))    \
368           button = Qnil;                                                \
369         else                                                            \
370           button = tb->next;                                            \
371       }                                                                 \
372   } while (0)
373
374 #define SET_TOOLBAR_WAS_VISIBLE_FLAG(frame, pos, flag)                  \
375   do {                                                                  \
376     switch (pos)                                                        \
377       {                                                                 \
378       case TOP_TOOLBAR:                                                 \
379         (frame)->top_toolbar_was_visible = flag;                        \
380         break;                                                          \
381       case BOTTOM_TOOLBAR:                                              \
382         (frame)->bottom_toolbar_was_visible = flag;                     \
383         break;                                                          \
384       case LEFT_TOOLBAR:                                                \
385         (frame)->left_toolbar_was_visible = flag;                       \
386         break;                                                          \
387       case RIGHT_TOOLBAR:                                               \
388         (frame)->right_toolbar_was_visible = flag;                      \
389         break;                                                          \
390       default:                                                          \
391         abort ();                                                       \
392       }                                                                 \
393   } while (0)
394
395 static void
396 gtk_output_toolbar (struct frame *f, enum toolbar_pos pos)
397 {
398   int x, y, bar_width, bar_height, vert;
399   int max_pixpos, right_size, right_start, blank_size;
400   int border_width = FRAME_REAL_TOOLBAR_BORDER_WIDTH (f, pos);
401   Lisp_Object button, window;
402   GdkWindow *x_win = FRAME_GTK_TEXT_WIDGET (f)->window;
403   GdkGC *background_gc = get_toolbar_gc (f);
404
405   get_toolbar_coords (f, pos, &x, &y, &bar_width, &bar_height, &vert, 1);
406   window = FRAME_LAST_NONMINIBUF_WINDOW (f);
407
408   /* Do the border */
409   gdk_draw_rectangle (x_win, background_gc, TRUE, x, y,
410                       (vert ? bar_width : border_width),
411                       (vert ? border_width : bar_height));
412   gdk_draw_rectangle (x_win, background_gc, TRUE,
413                       (vert ? x : x + bar_width - border_width),
414                       (vert ? y + bar_height - border_width : y),
415                       (vert ? bar_width : border_width),
416                       (vert ? border_width : bar_height));
417
418   if (vert)
419     {
420       max_pixpos = y + bar_height - border_width;
421       y += border_width;
422     }
423   else
424     {
425       max_pixpos = x + bar_width - border_width;
426       x += border_width;
427     }
428
429   button = FRAME_TOOLBAR_BUTTONS (f, pos);
430   right_size = 0;
431
432   /* First loop over all of the buttons to determine how much room we
433      need for left hand and right hand buttons.  This loop will also
434      make sure that all instances are instantiated so when we actually
435      output them they will come up immediately. */
436   while (!NILP (button))
437     {
438       struct toolbar_button *tb = XTOOLBAR_BUTTON (button);
439       int size = gtk_get_button_size (f, window, tb, vert, pos);
440
441       if (tb->pushright)
442         right_size += size;
443
444       button = tb->next;
445     }
446
447   button = FRAME_TOOLBAR_BUTTONS (f, pos);
448
449   /* Loop over the left buttons, updating and outputting them. */
450   GTK_OUTPUT_BUTTONS_LOOP (1);
451
452   /* Now determine where the right buttons start. */
453   right_start = max_pixpos - right_size;
454   if (right_start < (vert ? y : x))
455     right_start = (vert ? y : x);
456
457   /* Output the blank which goes from the end of the left buttons to
458      the start of the right. */
459   blank_size = right_start - (vert ? y : x);
460   if (blank_size)
461     {
462       int height, width;
463
464       if (vert)
465         {
466           width = bar_width;
467           height = blank_size;
468         }
469       else
470         {
471           width = blank_size;
472           height = bar_height;
473         }
474
475       /*
476        * Use a 3D pushright separator only if there isn't a toolbar
477        * border.  A flat separator meshes with the border and looks
478        * better.
479        */
480       gtk_draw_blank_toolbar_button (f, x, y, width, height, !border_width,
481                                      border_width, vert);
482
483       if (vert)
484         y += height;
485       else
486         x += width;
487     }
488
489   /* Loop over the right buttons, updating and outputting them. */
490   GTK_OUTPUT_BUTTONS_LOOP (0);
491
492   if (!vert)
493     {
494       Lisp_Object frame;
495
496       XSETFRAME (frame, f);
497       redisplay_clear_region (frame,
498                               DEFAULT_INDEX, FRAME_PIXWIDTH (f) - 1, y, 1,
499                               bar_height);
500     }
501
502   SET_TOOLBAR_WAS_VISIBLE_FLAG (f, pos, 1);
503
504   gdk_flush ();
505 }
506
507 static void
508 gtk_clear_toolbar (struct frame *f, enum toolbar_pos pos, int thickness_change)
509 {
510   Lisp_Object frame;
511   int x, y, width, height, vert;
512
513   get_toolbar_coords (f, pos, &x, &y, &width, &height, &vert, 1);
514   XSETFRAME (frame, f);
515
516   /* The thickness_change parameter is used by the toolbar resize routines
517      to clear any excess toolbar if the size shrinks. */
518   if (thickness_change < 0)
519     {
520       if (pos == LEFT_TOOLBAR || pos == RIGHT_TOOLBAR)
521         {
522           x = x + width + thickness_change;
523           width = -thickness_change;
524         }
525       else
526         {
527           y = y + height + thickness_change;
528           height = -thickness_change;
529         }
530     }
531
532   SET_TOOLBAR_WAS_VISIBLE_FLAG (f, pos, 0);
533
534   redisplay_clear_region (frame, DEFAULT_INDEX, x, y, width, height);
535   gdk_flush ();
536 }
537
538 static void
539 gtk_output_frame_toolbars (struct frame *f)
540 {
541   assert (FRAME_GTK_P (f));
542
543   if (FRAME_REAL_TOP_TOOLBAR_VISIBLE (f))
544     gtk_output_toolbar (f, TOP_TOOLBAR);
545   else if (f->top_toolbar_was_visible)
546     gtk_clear_toolbar (f, TOP_TOOLBAR, 0);
547
548   if (FRAME_REAL_BOTTOM_TOOLBAR_VISIBLE (f))
549     gtk_output_toolbar (f, BOTTOM_TOOLBAR);
550   else if (f->bottom_toolbar_was_visible)
551     gtk_clear_toolbar (f, BOTTOM_TOOLBAR, 0);
552
553   if (FRAME_REAL_LEFT_TOOLBAR_VISIBLE (f))
554     gtk_output_toolbar (f, LEFT_TOOLBAR);
555   else if (f->left_toolbar_was_visible)
556     gtk_clear_toolbar (f, LEFT_TOOLBAR, 0);
557
558   if (FRAME_REAL_RIGHT_TOOLBAR_VISIBLE (f))
559     gtk_output_toolbar (f, RIGHT_TOOLBAR);
560   else if (f->right_toolbar_was_visible)
561     gtk_clear_toolbar (f, RIGHT_TOOLBAR, 0);
562 }
563
564 static void
565 gtk_redraw_exposed_toolbar (struct frame *f, enum toolbar_pos pos, int x, int y,
566                             int width, int height)
567 {
568   int bar_x, bar_y, bar_width, bar_height, vert;
569   Lisp_Object button = FRAME_TOOLBAR_BUTTONS (f, pos);
570
571   get_toolbar_coords (f, pos, &bar_x, &bar_y, &bar_width, &bar_height,
572                       &vert, 1);
573
574   if (((y + height) < bar_y) || (y > (bar_y + bar_height)))
575     return;
576   if (((x + width) < bar_x) || (x > (bar_x + bar_width)))
577     return;
578
579   while (!NILP (button))
580     {
581       struct toolbar_button *tb = XTOOLBAR_BUTTON (button);
582
583       if (vert)
584         {
585           if (((tb->y + tb->height) > y) && (tb->y < (y + height)))
586             tb->dirty = 1;
587
588           /* If this is true we have gone past the exposed region. */
589           if (tb->y > (y + height))
590             break;
591         }
592       else
593         {
594           if (((tb->x + tb->width) > x) && (tb->x < (x + width)))
595             tb->dirty = 1;
596
597           /* If this is true we have gone past the exposed region. */
598           if (tb->x > (x + width))
599             break;
600         }
601
602       button = tb->next;
603     }
604
605   /* Even if none of the buttons is in the area, the blank region at
606      the very least must be because the first thing we did is verify
607      that some portion of the toolbar is in the exposed region. */
608   gtk_output_toolbar (f, pos);
609 }
610
611 static void
612 gtk_redraw_exposed_toolbars (struct frame *f, int x, int y, int width,
613                              int height)
614 {
615   assert (FRAME_GTK_P (f));
616
617   if (FRAME_REAL_TOP_TOOLBAR_VISIBLE (f))
618     gtk_redraw_exposed_toolbar (f, TOP_TOOLBAR, x, y, width, height);
619
620   if (FRAME_REAL_BOTTOM_TOOLBAR_VISIBLE (f))
621     gtk_redraw_exposed_toolbar (f, BOTTOM_TOOLBAR, x, y, width, height);
622
623   if (FRAME_REAL_LEFT_TOOLBAR_VISIBLE (f))
624     gtk_redraw_exposed_toolbar (f, LEFT_TOOLBAR, x, y, width, height);
625
626   if (FRAME_REAL_RIGHT_TOOLBAR_VISIBLE (f))
627     gtk_redraw_exposed_toolbar (f, RIGHT_TOOLBAR, x, y, width, height);
628 }
629
630 static void
631 gtk_redraw_frame_toolbars (struct frame *f)
632 {
633   /* There are certain startup paths that lead to update_EmacsFrame in
634      faces.c being called before a new frame is fully initialized.  In
635      particular before we have actually mapped it.  That routine can
636      call this one.  So, we need to make sure that the frame is
637      actually ready before we try and draw all over it. */
638
639   if (GTK_WIDGET_REALIZED (FRAME_GTK_TEXT_WIDGET (f)))
640     gtk_redraw_exposed_toolbars (f, 0, 0, FRAME_PIXWIDTH (f),
641                                  FRAME_PIXHEIGHT (f));
642 }
643
644 \f
645 static void
646 gtk_initialize_frame_toolbars (struct frame *f)
647 {
648 }
649
650 /* This only calls one function but we go ahead and create this in
651    case we ever do decide that we need to do more work. */
652 static void
653 gtk_free_frame_toolbars (struct frame *f)
654 {
655 }
656
657 \f
658 /************************************************************************/
659 /*                            initialization                            */
660 /************************************************************************/
661
662 void
663 console_type_create_toolbar_gtk (void)
664 {
665   CONSOLE_HAS_METHOD (gtk, output_frame_toolbars);
666   CONSOLE_HAS_METHOD (gtk, initialize_frame_toolbars);
667   CONSOLE_HAS_METHOD (gtk, free_frame_toolbars);
668   CONSOLE_HAS_METHOD (gtk, output_toolbar_button);
669   CONSOLE_HAS_METHOD (gtk, redraw_exposed_toolbars);
670   CONSOLE_HAS_METHOD (gtk, redraw_frame_toolbars);
671 }