Updated TODO.
[mp-5.x.git] / mpv_gtk.c
blobd3a547ba9fe88f00040bcbfa26c25c9a6e165a83
1 /*
3 Minimum Profit - Programmer Text Editor
5 GTK driver.
7 Copyright (C) 1991-2010 Angel Ortega <angel@triptico.com>
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 http://www.triptico.com
27 #include "config.h"
29 #ifdef CONFOPT_GTK
31 #include <stdio.h>
32 #include <wchar.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <ctype.h>
36 #include <unistd.h>
37 #include <sys/stat.h>
38 #include <gtk/gtk.h>
39 #include <gdk/gdkkeysyms.h>
41 #include "mpdm.h"
42 #include "mpsl.h"
44 #include "mp.h"
46 #include "mp.xpm"
48 /** data **/
50 /* global data */
52 static GtkWidget *window = NULL;
53 static GtkWidget *file_tabs = NULL;
54 static GtkWidget *area = NULL;
55 static GtkWidget *scrollbar = NULL;
56 static GtkWidget *status = NULL;
57 static GtkWidget *menu_bar = NULL;
58 static GdkGC *gc = NULL;
59 static GtkIMContext *im = NULL;
60 static GdkPixmap *pixmap = NULL;
62 /* character read from the keyboard */
63 static wchar_t im_char[2];
65 /* font information */
66 static int font_width = 0;
67 static int font_height = 0;
68 static PangoFontDescription *font = NULL;
70 /* the attributes */
71 static GdkColor *inks = NULL;
72 static GdkColor *papers = NULL;
73 static int *underlines = NULL;
75 /* true if the selection is ours */
76 static int got_selection = 0;
78 /* hack for active waiting for the selection */
79 static int wait_for_selection = 0;
81 /* global modal status */
82 /* code for the 'normal' attribute */
83 static int normal_attr = 0;
85 /* mp.drv.form() controls */
87 static GtkWidget **form_widgets = NULL;
88 static mpdm_t form_args = NULL;
89 static mpdm_t form_values = NULL;
91 /* mouse down flag */
92 static int mouse_down = 0;
94 /* timer function */
95 static mpdm_t timer_func = NULL;
97 /* maximize wanted? */
98 static int maximize = 0;
100 /** code **/
102 /** support functions **/
104 #define LL(m) (m)
106 static char *wcs_to_utf8(const wchar_t * wptr)
107 /* converts a wcs to utf-8 */
109 char *ptr;
110 gsize i, o;
112 i = wcslen(wptr);
114 /* do the conversion */
115 ptr = g_convert((const gchar *) wptr, (i + 1) * sizeof(wchar_t),
116 "UTF-8", "WCHAR_T", NULL, &o, NULL);
118 return ptr;
122 static char *v_to_utf8(mpdm_t v)
124 char *ptr = NULL;
126 if (v != NULL) {
127 mpdm_ref(v);
128 ptr = wcs_to_utf8(mpdm_string(v));
129 mpdm_unref(v);
132 return ptr;
135 static wchar_t *utf8_to_wcs(const char *ptr)
136 /* converts utf-8 to wcs */
138 wchar_t *wptr;
139 gsize i, o;
141 i = strlen(ptr);
143 /* do the conversion */
144 wptr = (wchar_t *) g_convert((gchar *) ptr, i + 1,
145 "WCHAR_T", "UTF-8", NULL, &o, NULL);
147 return wptr;
151 static void update_window_size(void)
152 /* updates the viewport size in characters */
154 PangoLayout *pa;
155 int tx, ty;
156 mpdm_t v;
158 /* get font metrics */
159 pa = gtk_widget_create_pango_layout(area, "m");
160 pango_layout_set_font_description(pa, font);
161 pango_layout_get_pixel_size(pa, &font_width, &font_height);
162 g_object_unref(pa);
164 /* calculate the size in chars */
165 tx = (area->allocation.width / font_width);
166 ty = (area->allocation.height / font_height) + 1;
168 /* store the 'window' size */
169 v = mpdm_hget_s(mp, L"window");
170 mpdm_hset_s(v, L"tx", MPDM_I(tx));
171 mpdm_hset_s(v, L"ty", MPDM_I(ty));
173 /* rebuild the pixmap for the double buffer */
174 if (pixmap != NULL)
175 gdk_pixmap_unref(pixmap);
177 pixmap = gdk_pixmap_new(area->window,
178 area->allocation.width, font_height, -1);
182 static void build_fonts(void)
183 /* builds the fonts */
185 char tmp[128];
186 int font_size = 12;
187 const char *font_face = "Mono";
188 mpdm_t c;
189 mpdm_t w = NULL;
191 if (font != NULL)
192 pango_font_description_free(font);
194 /* get current configuration */
195 if ((c = mpdm_hget_s(mp, L"config")) != NULL) {
196 mpdm_t v;
198 if ((v = mpdm_hget_s(c, L"font_size")) != NULL)
199 font_size = mpdm_ival(v);
200 else
201 mpdm_hset_s(c, L"font_size", MPDM_I(font_size));
203 if ((v = mpdm_hget_s(c, L"font_face")) != NULL) {
204 w = mpdm_ref(MPDM_2MBS(v->data));
205 font_face = w->data;
207 else
208 mpdm_hset_s(c, L"font_face", MPDM_MBS(font_face));
211 snprintf(tmp, sizeof(tmp) - 1, "%s %d", font_face, font_size);
212 tmp[sizeof(tmp) - 1] = '\0';
214 font = pango_font_description_from_string(tmp);
215 update_window_size();
217 mpdm_unref(w);
221 static void build_color(GdkColor * gdkcolor, int rgb)
222 /* builds a color */
224 gdkcolor->pixel = 0;
225 gdkcolor->blue = (rgb & 0x000000ff) << 8;
226 gdkcolor->green = (rgb & 0x0000ff00);
227 gdkcolor->red = (rgb & 0x00ff0000) >> 8;
228 gdk_colormap_alloc_color(gdk_colormap_get_system(), gdkcolor, FALSE,
229 TRUE);
233 static void build_colors(void)
234 /* builds the colors */
236 mpdm_t colors;
237 mpdm_t l;
238 mpdm_t c;
239 int n, s;
241 /* gets the color definitions and attribute names */
242 colors = mpdm_hget_s(mp, L"colors");
243 l = mpdm_ref(mpdm_keys(colors));
244 s = mpdm_size(l);
246 /* redim the structures */
247 inks = realloc(inks, sizeof(GdkColor) * s);
248 papers = realloc(papers, sizeof(GdkColor) * s);
249 underlines = realloc(underlines, sizeof(int) * s);
251 /* loop the colors */
252 for (n = 0; n < s && (c = mpdm_aget(l, n)) != NULL; n++) {
253 mpdm_t d = mpdm_hget(colors, c);
254 mpdm_t v = mpdm_hget_s(d, L"gui");
256 /* store the 'normal' attribute */
257 if (wcscmp(mpdm_string(c), L"normal") == 0)
258 normal_attr = n;
260 /* store the attr */
261 mpdm_hset_s(d, L"attr", MPDM_I(n));
263 build_color(&inks[n], mpdm_ival(mpdm_aget(v, 0)));
264 build_color(&papers[n], mpdm_ival(mpdm_aget(v, 1)));
266 /* flags */
267 v = mpdm_hget_s(d, L"flags");
268 underlines[n] = mpdm_seek_s(v, L"underline", 1) != -1 ? 1 : 0;
270 if (mpdm_seek_s(v, L"reverse", 1) != -1) {
271 GdkColor t;
273 t = inks[n];
274 inks[n] = papers[n];
275 papers[n] = t;
279 mpdm_unref(l);
283 /** menu functions **/
285 static void redraw(void);
287 static void menu_item_callback(mpdm_t action)
288 /* menu click callback */
290 mp_process_action(action);
291 redraw();
293 if (mp_exit_requested)
294 gtk_main_quit();
298 static void build_submenu(GtkWidget * menu, mpdm_t labels)
299 /* build a submenu */
301 int n;
302 GtkWidget *menu_item;
304 mpdm_ref(labels);
306 for (n = 0; n < mpdm_size(labels); n++) {
307 /* get the action */
308 mpdm_t v = mpdm_aget(labels, n);
310 /* if the action is a separator... */
311 if (*((wchar_t *) v->data) == L'-')
312 menu_item = gtk_menu_item_new();
313 else {
314 char *ptr;
316 ptr = v_to_utf8(mp_menu_label(v));
317 menu_item = gtk_menu_item_new_with_label(ptr);
318 g_free(ptr);
321 gtk_menu_append(GTK_MENU(menu), menu_item);
322 g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
323 G_CALLBACK(menu_item_callback), v);
324 gtk_widget_show(menu_item);
327 mpdm_unref(labels);
331 static void build_menu(void)
332 /* builds the menu */
334 static mpdm_t prev_menu = NULL;
335 int n;
336 mpdm_t m;
338 /* gets the current menu */
339 if ((m = mpdm_hget_s(mp, L"menu")) == NULL)
340 return;
342 /* if it's the same, do nothing */
343 if (mpdm_cmp(m, prev_menu) == 0)
344 return;
346 /* create a new menu */
347 menu_bar = gtk_menu_bar_new();
349 for (n = 0; n < mpdm_size(m); n++) {
350 char *ptr;
351 mpdm_t mi;
352 mpdm_t v;
353 GtkWidget *menu;
354 GtkWidget *menu_item;
355 int i;
357 /* get the label and the items */
358 mi = mpdm_aget(m, n);
359 v = mpdm_aget(mi, 0);
361 if ((ptr = v_to_utf8(mpdm_gettext(v))) == NULL)
362 continue;
364 /* change the & by _ for the mnemonic */
365 for (i = 0; ptr[i]; i++)
366 if (ptr[i] == '&')
367 ptr[i] = '_';
369 /* add the menu and the label */
370 menu = gtk_menu_new();
371 menu_item = gtk_menu_item_new_with_mnemonic(ptr);
372 g_free(ptr);
374 gtk_widget_show(menu_item);
375 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu);
376 gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), menu_item);
378 /* now loop the items */
379 build_submenu(menu, mpdm_aget(mi, 1));
384 /** main area drawing functions **/
386 static void switch_page(GtkNotebook * notebook, GtkNotebookPage * page,
387 gint pg_num, gpointer data)
388 /* 'switch_page' handler (filetabs) */
390 /* sets the active one */
391 mpdm_hset_s(mp, L"active_i", MPDM_I(pg_num));
393 gtk_widget_grab_focus(area);
394 redraw();
398 static void draw_filetabs(void)
399 /* draws the filetabs */
401 static mpdm_t last = NULL;
402 mpdm_t names;
403 int n;
405 names = mpdm_ref(mp_get_doc_names());
407 /* disconnect redraw signal to avoid infinite loops */
408 g_signal_handlers_disconnect_by_func(G_OBJECT(file_tabs),
409 G_CALLBACK(switch_page), NULL);
411 /* is the list different from the previous one? */
412 if (mpdm_cmp(names, last) != 0) {
414 /* delete the current tabs */
415 for (n = 0; n < mpdm_size(last); n++)
416 gtk_notebook_remove_page(GTK_NOTEBOOK(file_tabs), 0);
418 /* create the new ones */
419 for (n = 0; n < mpdm_size(names); n++) {
420 GtkWidget *p;
421 GtkWidget *f;
422 char *ptr;
423 mpdm_t v = mpdm_aget(names, n);
425 if ((ptr = v_to_utf8(v)) != NULL) {
426 p = gtk_label_new(ptr);
427 gtk_widget_show(p);
429 f = gtk_frame_new(NULL);
430 gtk_widget_show(f);
432 gtk_notebook_append_page(GTK_NOTEBOOK(file_tabs), f, p);
434 g_free(ptr);
438 /* store for the next time */
439 mpdm_unref(last);
440 last = mpdm_ref(names);
443 mpdm_unref(names);
445 /* set the active one */
446 gtk_notebook_set_page(GTK_NOTEBOOK(file_tabs),
447 mpdm_ival(mpdm_hget_s(mp, L"active_i")));
449 /* reconnect signal */
450 g_signal_connect(G_OBJECT(file_tabs), "switch_page",
451 G_CALLBACK(switch_page), NULL);
453 gtk_widget_grab_focus(area);
457 static void draw_status(void)
458 /* draws the status line */
460 char *ptr;
462 if ((ptr = v_to_utf8(mp_build_status_line())) != NULL) {
463 gtk_label_set_text(GTK_LABEL(status), ptr);
464 g_free(ptr);
469 static gint scroll_event(GtkWidget * widget, GdkEventScroll * event)
470 /* 'scroll_event' handler (mouse wheel) */
472 wchar_t *ptr = NULL;
474 switch (event->direction) {
475 case GDK_SCROLL_UP:
476 ptr = L"mouse-wheel-up";
477 break;
478 case GDK_SCROLL_DOWN:
479 ptr = L"mouse-wheel-down";
480 break;
481 case GDK_SCROLL_LEFT:
482 ptr = L"mouse-wheel-left";
483 break;
484 case GDK_SCROLL_RIGHT:
485 ptr = L"mouse-wheel-right";
486 break;
489 if (ptr != NULL)
490 mp_process_event(MPDM_S(ptr));
492 redraw();
494 return 0;
498 static void value_changed(GtkAdjustment * adj, gpointer * data)
499 /* 'value_changed' handler (scrollbar) */
501 int i = (int) adj->value;
502 mpdm_t doc;
503 mpdm_t txt;
504 int y;
506 /* get current y position */
507 doc = mp_active();
508 txt = mpdm_hget_s(doc, L"txt");
509 y = mpdm_ival(mpdm_hget_s(txt, L"y"));
511 /* if it's different, set and redraw */
512 if (y != i) {
513 mp_set_y(doc, i);
514 mpdm_hset_s(txt, L"vy", MPDM_I(i));
515 redraw();
520 static void draw_scrollbar(void)
521 /* updates the scrollbar */
523 GtkAdjustment *adjustment;
524 mpdm_t d;
525 mpdm_t v;
526 int pos, size, max;
528 /* gets the active document */
529 if ((d = mp_active()) == NULL)
530 return;
532 /* get the coordinates */
533 v = mpdm_hget_s(d, L"txt");
534 pos = mpdm_ival(mpdm_hget_s(v, L"vy"));
535 max = mpdm_size(mpdm_hget_s(v, L"lines"));
537 v = mpdm_hget_s(mp, L"window");
538 size = mpdm_ival(mpdm_hget_s(v, L"ty"));
540 adjustment = gtk_range_get_adjustment(GTK_RANGE(scrollbar));
542 /* disconnect to avoid infinite loops */
543 g_signal_handlers_disconnect_by_func(G_OBJECT(adjustment),
544 G_CALLBACK(value_changed), NULL);
546 adjustment->step_increment = (gfloat) 1;
547 adjustment->upper = (gfloat) max;
548 adjustment->page_size = (gfloat) size;
549 adjustment->page_increment = (gfloat) size;
550 adjustment->value = (gfloat) pos;
552 gtk_range_set_adjustment(GTK_RANGE(scrollbar), adjustment);
554 gtk_adjustment_changed(adjustment);
555 gtk_adjustment_value_changed(adjustment);
557 /* reattach again */
558 g_signal_connect(G_OBJECT
559 (gtk_range_get_adjustment(GTK_RANGE(scrollbar))),
560 "value_changed", G_CALLBACK(value_changed), NULL);
564 static void gtk_drv_paint(mpdm_t doc, int optimize)
565 /* GTK document draw function */
567 GdkRectangle gr;
568 mpdm_t d = NULL;
569 int n, m;
571 if (maximize)
572 gtk_window_maximize(GTK_WINDOW(window));
574 /* no gc? create it */
575 if (gc == NULL)
576 gc = gdk_gc_new(area->window);
578 if (font == NULL)
579 build_fonts();
581 if ((d = mp_draw(doc, optimize)) == NULL)
582 return;
584 mpdm_ref(d);
586 gr.x = 0;
587 gr.y = 0;
588 gr.width = area->allocation.width;
589 gr.height = font_height;
591 for (n = 0; n < mpdm_size(d); n++) {
592 PangoLayout *pl;
593 PangoAttrList *pal;
594 mpdm_t l = mpdm_aget(d, n);
595 char *str = NULL;
596 int u, p;
598 if (l == NULL)
599 continue;
601 /* create the pango stuff */
602 pl = gtk_widget_create_pango_layout(area, NULL);
603 pango_layout_set_font_description(pl, font);
604 pal = pango_attr_list_new();
606 for (m = u = p = 0; m < mpdm_size(l); m++, u = p) {
607 PangoAttribute *pa;
608 int attr;
609 mpdm_t s;
610 char *ptr;
612 /* get the attribute and the string */
613 attr = mpdm_ival(mpdm_aget(l, m++));
614 s = mpdm_aget(l, m);
616 /* convert the string to utf8 */
617 ptr = v_to_utf8(s);
619 /* add to the full line */
620 str = mpdm_poke(str, &p, ptr, strlen(ptr), 1);
622 g_free(ptr);
624 /* create the background if it's
625 different from the default */
626 if (papers[attr].red != papers[normal_attr].red ||
627 papers[attr].green != papers[normal_attr].green ||
628 papers[attr].blue != papers[normal_attr].blue) {
629 pa = pango_attr_background_new(papers[attr].red,
630 papers[attr].green,
631 papers[attr].blue);
633 pa->start_index = u;
634 pa->end_index = p;
636 pango_attr_list_insert(pal, pa);
639 /* underline? */
640 if (underlines[attr]) {
641 pa = pango_attr_underline_new(TRUE);
643 pa->start_index = u;
644 pa->end_index = p;
646 pango_attr_list_insert(pal, pa);
649 /* foreground color */
650 pa = pango_attr_foreground_new(inks[attr].red,
651 inks[attr].green,
652 inks[attr].blue);
654 pa->start_index = u;
655 pa->end_index = p;
657 pango_attr_list_insert(pal, pa);
660 /* store the attributes */
661 pango_layout_set_attributes(pl, pal);
662 pango_attr_list_unref(pal);
664 /* store and free the text */
665 pango_layout_set_text(pl, str, p);
666 free(str);
668 /* draw the background */
669 gdk_gc_set_foreground(gc, &papers[normal_attr]);
670 gdk_draw_rectangle(pixmap, gc, TRUE, 0, 0, gr.width, gr.height);
672 /* draw the text */
673 gtk_paint_layout(area->style, pixmap,
674 GTK_STATE_NORMAL, TRUE, &gr, area, "", 2, 0, pl);
676 /* dump the pixmap */
677 gdk_draw_pixmap(area->window, gc, pixmap,
678 0, 0, 0, n * font_height, gr.width, gr.height);
680 g_object_unref(pl);
683 mpdm_unref(d);
685 draw_filetabs();
686 draw_scrollbar();
687 draw_status();
691 static void redraw(void)
693 if (mpdm_size(mpdm_hget_s(mp, L"docs")))
694 gtk_drv_paint(mp_active(), 0);
698 static gint delete_event(GtkWidget * w, GdkEvent * e, gpointer data)
699 /* 'delete_event' handler */
701 mp_process_event(MPDM_LS(L"close-window"));
703 return mp_exit_requested ? FALSE : TRUE;
707 static void destroy(GtkWidget * w, gpointer data)
708 /* 'destroy' handler */
710 gtk_main_quit();
714 static gint key_release_event(GtkWidget * widget, GdkEventKey * event,
715 gpointer data)
716 /* 'key_release_event' handler */
718 if (mp_keypress_throttle(0))
719 gtk_drv_paint(mp_active(), 0);
721 return 0;
725 static gint key_press_event(GtkWidget * widget, GdkEventKey * event,
726 gpointer data)
727 /* 'key_press_event' handler */
729 wchar_t *ptr = NULL;
731 gtk_im_context_filter_keypress(im, event);
733 /* set mp.shift_pressed */
734 if (event->state & (GDK_SHIFT_MASK))
735 mpdm_hset_s(mp, L"shift_pressed", MPDM_I(1));
737 /* reserve alt for menu mnemonics */
738 /* if (GDK_MOD1_MASK & event->state)
739 return(0);*/
741 if (event->state & (GDK_CONTROL_MASK)) {
742 switch (event->keyval) {
743 case GDK_Up:
744 ptr = L"ctrl-cursor-up";
745 break;
746 case GDK_Down:
747 ptr = L"ctrl-cursor-down";
748 break;
749 case GDK_Left:
750 ptr = L"ctrl-cursor-left";
751 break;
752 case GDK_Right:
753 ptr = L"ctrl-cursor-right";
754 break;
755 case GDK_Prior:
756 ptr = L"ctrl-page-up";
757 break;
758 case GDK_Next:
759 ptr = L"ctrl-page-down";
760 break;
761 case GDK_Home:
762 ptr = L"ctrl-home";
763 break;
764 case GDK_End:
765 ptr = L"ctrl-end";
766 break;
767 case GDK_space:
768 ptr = L"ctrl-space";
769 break;
770 case GDK_KP_Add:
771 ptr = L"ctrl-kp-plus";
772 break;
773 case GDK_KP_Subtract:
774 ptr = L"ctrl-kp-minus";
775 break;
776 case GDK_KP_Multiply:
777 ptr = L"ctrl-kp-multiply";
778 break;
779 case GDK_KP_Divide:
780 ptr = L"ctrl-kp-divide";
781 break;
782 case GDK_F1:
783 ptr = L"ctrl-f1";
784 break;
785 case GDK_F2:
786 ptr = L"ctrl-f2";
787 break;
788 case GDK_F3:
789 ptr = L"ctrl-f3";
790 break;
791 case GDK_F4:
792 ptr = L"ctrl-f4";
793 break;
794 case GDK_F5:
795 ptr = L"ctrl-f5";
796 break;
797 case GDK_F6:
798 ptr = L"ctrl-f6";
799 break;
800 case GDK_F7:
801 ptr = L"ctrl-f7";
802 break;
803 case GDK_F8:
804 ptr = L"ctrl-f8";
805 break;
806 case GDK_F9:
807 ptr = L"ctrl-f9";
808 break;
809 case GDK_F10:
810 ptr = L"ctrl-f10";
811 break;
812 case GDK_F11:
813 ptr = L"ctrl-f11";
814 break;
815 case GDK_F12:
816 ptr = L"ctrl-f12";
817 break;
818 case GDK_KP_Enter:
819 case GDK_Return:
820 ptr = L"ctrl-enter";
821 break;
822 case GDK_Cyrillic_ve:
823 ptr = L"ctrl-d";
824 break;
825 case GDK_Cyrillic_a:
826 ptr = L"ctrl-f";
827 break;
828 case GDK_Cyrillic_tse:
829 ptr = L"ctrl-w";
830 break;
831 case GDK_Cyrillic_de:
832 ptr = L"ctrl-l";
833 break;
834 case GDK_Cyrillic_ie:
835 ptr = L"ctrl-t";
836 break;
837 case GDK_Cyrillic_ef:
838 ptr = L"ctrl-a";
839 break;
840 case GDK_Cyrillic_ghe:
841 ptr = L"ctrl-u";
842 break;
843 case GDK_Cyrillic_i:
844 ptr = L"ctrl-b";
845 break;
846 case GDK_Cyrillic_shorti:
847 ptr = L"ctrl-q";
848 break;
849 case GDK_Cyrillic_ka:
850 ptr = L"ctrl-r";
851 break;
852 case GDK_Cyrillic_el:
853 ptr = L"ctrl-k";
854 break;
855 case GDK_Cyrillic_em:
856 ptr = L"ctrl-v";
857 break;
858 case GDK_Cyrillic_en:
859 ptr = L"ctrl-y";
860 break;
861 case GDK_Cyrillic_o:
862 ptr = L"ctrl-j";
863 break;
864 case GDK_Cyrillic_pe:
865 ptr = L"ctrl-g";
866 break;
867 case GDK_Cyrillic_ya:
868 ptr = L"ctrl-z";
869 break;
870 case GDK_Cyrillic_er:
871 ptr = L"ctrl-h";
872 break;
873 case GDK_Cyrillic_es:
874 ptr = L"ctrl-c";
875 break;
876 case GDK_Cyrillic_te:
877 ptr = L"ctrl-n";
878 break;
879 case GDK_Cyrillic_softsign:
880 ptr = L"ctrl-m";
881 break;
882 case GDK_Cyrillic_yeru:
883 ptr = L"ctrl-s";
884 break;
885 case GDK_Cyrillic_ze:
886 ptr = L"ctrl-p";
887 break;
888 case GDK_Cyrillic_sha:
889 ptr = L"ctrl-i";
890 break;
891 case GDK_Cyrillic_e:
892 ptr = L"ctrl-t";
893 break;
894 case GDK_Cyrillic_shcha:
895 ptr = L"ctrl-o";
896 break;
897 case GDK_Cyrillic_che:
898 ptr = L"ctrl-x";
899 break;
902 if (ptr == NULL) {
903 char c = event->keyval & 0xdf;
905 switch (c) {
906 case 'A':
907 ptr = L"ctrl-a";
908 break;
909 case 'B':
910 ptr = L"ctrl-b";
911 break;
912 case 'C':
913 ptr = L"ctrl-c";
914 break;
915 case 'D':
916 ptr = L"ctrl-d";
917 break;
918 case 'E':
919 ptr = L"ctrl-e";
920 break;
921 case 'F':
922 ptr = L"ctrl-f";
923 break;
924 case 'G':
925 ptr = L"ctrl-g";
926 break;
927 case 'H':
928 ptr = L"ctrl-h";
929 break;
930 case 'I':
931 ptr = L"ctrl-i";
932 break;
933 case 'J':
934 ptr = L"ctrl-j";
935 break;
936 case 'K':
937 ptr = L"ctrl-k";
938 break;
939 case 'L':
940 ptr = L"ctrl-l";
941 break;
942 case 'M':
943 ptr = L"ctrl-m";
944 break;
945 case 'N':
946 ptr = L"ctrl-n";
947 break;
948 case 'O':
949 ptr = L"ctrl-o";
950 break;
951 case 'P':
952 ptr = L"ctrl-p";
953 break;
954 case 'Q':
955 ptr = L"ctrl-q";
956 break;
957 case 'R':
958 ptr = L"ctrl-r";
959 break;
960 case 'S':
961 ptr = L"ctrl-s";
962 break;
963 case 'T':
964 ptr = L"ctrl-t";
965 break;
966 case 'U':
967 ptr = L"ctrl-u";
968 break;
969 case 'V':
970 ptr = L"ctrl-v";
971 break;
972 case 'W':
973 ptr = L"ctrl-w";
974 break;
975 case 'X':
976 ptr = L"ctrl-x";
977 break;
978 case 'Y':
979 ptr = L"ctrl-y";
980 break;
981 case 'Z':
982 ptr = L"ctrl-z";
983 break;
987 else
988 if (event->state & (GDK_MOD1_MASK)) {
989 switch (event->keyval) {
990 case GDK_Up:
991 ptr = L"alt-cursor-up";
992 break;
993 case GDK_Down:
994 ptr = L"alt-cursor-down";
995 break;
996 case GDK_Left:
997 ptr = L"alt-cursor-left";
998 break;
999 case GDK_Right:
1000 ptr = L"alt-cursor-right";
1001 break;
1002 case GDK_Prior:
1003 ptr = L"alt-page-up";
1004 break;
1005 case GDK_Next:
1006 ptr = L"alt-page-down";
1007 break;
1008 case GDK_Home:
1009 ptr = L"alt-home";
1010 break;
1011 case GDK_End:
1012 ptr = L"alt-end";
1013 break;
1014 case GDK_space:
1015 ptr = L"alt-space";
1016 break;
1017 case GDK_KP_Add:
1018 ptr = L"alt-kp-plus";
1019 break;
1020 case GDK_KP_Subtract:
1021 ptr = L"alt-kp-minus";
1022 break;
1023 case GDK_KP_Multiply:
1024 ptr = L"alt-kp-multiply";
1025 break;
1026 case GDK_KP_Divide:
1027 ptr = L"alt-kp-divide";
1028 break;
1029 case GDK_F1:
1030 ptr = L"alt-f1";
1031 break;
1032 case GDK_F2:
1033 ptr = L"alt-f2";
1034 break;
1035 case GDK_F3:
1036 ptr = L"alt-f3";
1037 break;
1038 case GDK_F4:
1039 ptr = L"alt-f4";
1040 break;
1041 case GDK_F5:
1042 ptr = L"alt-f5";
1043 break;
1044 case GDK_F6:
1045 ptr = L"alt-f6";
1046 break;
1047 case GDK_F7:
1048 ptr = L"alt-f7";
1049 break;
1050 case GDK_F8:
1051 ptr = L"alt-f8";
1052 break;
1053 case GDK_F9:
1054 ptr = L"alt-f9";
1055 break;
1056 case GDK_F10:
1057 ptr = L"alt-f10";
1058 break;
1059 case GDK_F11:
1060 ptr = L"alt-f11";
1061 break;
1062 case GDK_F12:
1063 ptr = L"alt-f12";
1064 break;
1065 case GDK_KP_Enter:
1066 case GDK_Return:
1067 ptr = L"alt-enter";
1068 break;
1069 case GDK_Cyrillic_ve:
1070 ptr = L"alt-d";
1071 break;
1072 case GDK_Cyrillic_a:
1073 ptr = L"alt-f";
1074 break;
1075 case GDK_Cyrillic_tse:
1076 ptr = L"alt-w";
1077 break;
1078 case GDK_Cyrillic_de:
1079 ptr = L"alt-l";
1080 break;
1081 case GDK_Cyrillic_ie:
1082 ptr = L"alt-t";
1083 break;
1084 case GDK_Cyrillic_ef:
1085 ptr = L"alt-a";
1086 break;
1087 case GDK_Cyrillic_ghe:
1088 ptr = L"alt-u";
1089 break;
1090 case GDK_Cyrillic_i:
1091 ptr = L"alt-b";
1092 break;
1093 case GDK_Cyrillic_shorti:
1094 ptr = L"alt-q";
1095 break;
1096 case GDK_Cyrillic_ka:
1097 ptr = L"alt-r";
1098 break;
1099 case GDK_Cyrillic_el:
1100 ptr = L"alt-k";
1101 break;
1102 case GDK_Cyrillic_em:
1103 ptr = L"alt-v";
1104 break;
1105 case GDK_Cyrillic_en:
1106 ptr = L"alt-y";
1107 break;
1108 case GDK_Cyrillic_o:
1109 ptr = L"alt-j";
1110 break;
1111 case GDK_Cyrillic_pe:
1112 ptr = L"alt-g";
1113 break;
1114 case GDK_Cyrillic_ya:
1115 ptr = L"alt-z";
1116 break;
1117 case GDK_Cyrillic_er:
1118 ptr = L"alt-h";
1119 break;
1120 case GDK_Cyrillic_es:
1121 ptr = L"alt-c";
1122 break;
1123 case GDK_Cyrillic_te:
1124 ptr = L"alt-n";
1125 break;
1126 case GDK_Cyrillic_softsign:
1127 ptr = L"alt-m";
1128 break;
1129 case GDK_Cyrillic_yeru:
1130 ptr = L"alt-s";
1131 break;
1132 case GDK_Cyrillic_ze:
1133 ptr = L"alt-p";
1134 break;
1135 case GDK_Cyrillic_sha:
1136 ptr = L"alt-i";
1137 break;
1138 case GDK_Cyrillic_e:
1139 ptr = L"alt-t";
1140 break;
1141 case GDK_Cyrillic_shcha:
1142 ptr = L"alt-o";
1143 break;
1144 case GDK_Cyrillic_che:
1145 ptr = L"alt-x";
1146 break;
1149 if (ptr == NULL) {
1150 char c = event->keyval & 0xdf;
1152 switch (c) {
1153 case 'A':
1154 ptr = L"alt-a";
1155 break;
1156 case 'B':
1157 ptr = L"alt-b";
1158 break;
1159 case 'C':
1160 ptr = L"alt-c";
1161 break;
1162 case 'D':
1163 ptr = L"alt-d";
1164 break;
1165 case 'E':
1166 ptr = L"alt-e";
1167 break;
1168 case 'F':
1169 ptr = L"alt-f";
1170 break;
1171 case 'G':
1172 ptr = L"alt-g";
1173 break;
1174 case 'H':
1175 ptr = L"alt-h";
1176 break;
1177 case 'I':
1178 ptr = L"alt-i";
1179 break;
1180 case 'J':
1181 ptr = L"alt-j";
1182 break;
1183 case 'K':
1184 ptr = L"alt-k";
1185 break;
1186 case 'L':
1187 ptr = L"alt-l";
1188 break;
1189 case 'M':
1190 ptr = L"alt-m";
1191 break;
1192 case 'N':
1193 ptr = L"alt-n";
1194 break;
1195 case 'O':
1196 ptr = L"alt-o";
1197 break;
1198 case 'P':
1199 ptr = L"alt-p";
1200 break;
1201 case 'Q':
1202 ptr = L"alt-q";
1203 break;
1204 case 'R':
1205 ptr = L"alt-r";
1206 break;
1207 case 'S':
1208 ptr = L"alt-s";
1209 break;
1210 case 'T':
1211 ptr = L"alt-t";
1212 break;
1213 case 'U':
1214 ptr = L"alt-u";
1215 break;
1216 case 'V':
1217 ptr = L"alt-v";
1218 break;
1219 case 'W':
1220 ptr = L"alt-w";
1221 break;
1222 case 'X':
1223 ptr = L"alt-x";
1224 break;
1225 case 'Y':
1226 ptr = L"alt-y";
1227 break;
1228 case 'Z':
1229 ptr = L"alt-z";
1230 break;
1234 else {
1235 switch (event->keyval) {
1236 case GDK_Up:
1237 ptr = L"cursor-up";
1238 break;
1239 case GDK_Down:
1240 ptr = L"cursor-down";
1241 break;
1242 case GDK_Left:
1243 ptr = L"cursor-left";
1244 break;
1245 case GDK_Right:
1246 ptr = L"cursor-right";
1247 break;
1248 case GDK_Prior:
1249 ptr = L"page-up";
1250 break;
1251 case GDK_Next:
1252 ptr = L"page-down";
1253 break;
1254 case GDK_Home:
1255 ptr = L"home";
1256 break;
1257 case GDK_End:
1258 ptr = L"end";
1259 break;
1260 case GDK_space:
1261 ptr = L"space";
1262 break;
1263 case GDK_KP_Add:
1264 ptr = L"kp-plus";
1265 break;
1266 case GDK_KP_Subtract:
1267 ptr = L"kp-minus";
1268 break;
1269 case GDK_KP_Multiply:
1270 ptr = L"kp-multiply";
1271 break;
1272 case GDK_KP_Divide:
1273 ptr = L"kp-divide";
1274 break;
1275 case GDK_F1:
1276 ptr = L"f1";
1277 break;
1278 case GDK_F2:
1279 ptr = L"f2";
1280 break;
1281 case GDK_F3:
1282 ptr = L"f3";
1283 break;
1284 case GDK_F4:
1285 ptr = L"f4";
1286 break;
1287 case GDK_F5:
1288 ptr = L"f5";
1289 break;
1290 case GDK_F6:
1291 ptr = L"f6";
1292 break;
1293 case GDK_F7:
1294 ptr = L"f7";
1295 break;
1296 case GDK_F8:
1297 ptr = L"f8";
1298 break;
1299 case GDK_F9:
1300 ptr = L"f9";
1301 break;
1302 case GDK_F10:
1303 ptr = L"f10";
1304 break;
1305 case GDK_F11:
1306 ptr = L"f11";
1307 break;
1308 case GDK_F12:
1309 ptr = L"f12";
1310 break;
1311 case GDK_Insert:
1312 ptr = L"insert";
1313 break;
1314 case GDK_BackSpace:
1315 ptr = L"backspace";
1316 break;
1317 case GDK_Delete:
1318 ptr = L"delete";
1319 break;
1320 case GDK_KP_Enter:
1321 case GDK_Return:
1322 ptr = L"enter";
1323 break;
1324 case GDK_Tab:
1325 ptr = L"tab";
1326 break;
1327 case GDK_ISO_Left_Tab:
1328 ptr = L"shift-tab";
1329 break;
1330 case GDK_Escape:
1331 ptr = L"escape";
1332 break;
1336 /* if there is a pending char in im_char, use it */
1337 if (ptr == NULL && im_char[0] != L'\0')
1338 ptr = im_char;
1340 /* finally process */
1341 if (ptr != NULL)
1342 mp_process_event(MPDM_S(ptr));
1344 /* delete the pending char */
1345 im_char[0] = L'\0';
1347 if (mp_exit_requested)
1348 gtk_main_quit();
1350 if (mp_keypress_throttle(1))
1351 gtk_drv_paint(mp_active(), 1);
1353 return 0;
1357 static gint button_press_event(GtkWidget * widget, GdkEventButton * event,
1358 gpointer data)
1359 /* 'button_press_event' handler (mouse buttons) */
1361 int x, y;
1362 wchar_t *ptr = NULL;
1364 mouse_down = 1;
1366 /* mouse instant positioning */
1367 x = ((int) event->x) / font_width;
1368 y = ((int) event->y) / font_height;
1370 mpdm_hset_s(mp, L"mouse_x", MPDM_I(x));
1371 mpdm_hset_s(mp, L"mouse_y", MPDM_I(y));
1373 switch (event->button) {
1374 case 1:
1375 ptr = L"mouse-left-button";
1376 break;
1377 case 2:
1378 ptr = L"mouse-middle-button";
1379 break;
1380 case 3:
1381 ptr = L"mouse-right-button";
1382 break;
1383 case 4:
1384 ptr = L"mouse-wheel-up";
1385 break;
1386 case 5:
1387 ptr = L"mouse-wheel-down";
1388 break;
1391 if (ptr != NULL)
1392 mp_process_event(MPDM_S(ptr));
1394 redraw();
1396 return 0;
1400 static gint button_release_event(GtkWidget * widget,
1401 GdkEventButton * event, gpointer data)
1402 /* 'button_release_event' handle (mouse buttons) */
1404 mouse_down = 0;
1406 return TRUE;
1410 static gint motion_notify_event(GtkWidget * widget, GdkEventMotion * event,
1411 gpointer data)
1412 /* 'motion_notify_event' handler (mouse movement) */
1414 static int ox = 0;
1415 static int oy = 0;
1417 if (mouse_down) {
1418 int x, y;
1420 /* mouse dragging */
1421 x = ((int) event->x) / font_width;
1422 y = ((int) event->y) / font_height;
1424 if (ox != x && oy != y) {
1425 mpdm_hset_s(mp, L"mouse_to_x", MPDM_I(x));
1426 mpdm_hset_s(mp, L"mouse_to_y", MPDM_I(y));
1428 mp_process_event(MPDM_LS(L"mouse-drag"));
1429 gtk_drv_paint(mp_active(), 1);
1433 return TRUE;
1437 static void drag_data_received(GtkWidget * widget, GdkDragContext * dc,
1438 gint x, gint y, GtkSelectionData * data,
1439 guint info, guint time)
1440 /* 'drag_data_received' handler */
1442 printf("drag_data_received (unsupported)\n");
1446 /** clipboard functions **/
1448 static void commit(GtkIMContext * i, char *str, gpointer u)
1449 /* 'commit' handler */
1451 wchar_t *wstr;
1453 wstr = (wchar_t *) g_convert(str, -1,
1454 "WCHAR_T", "UTF-8", NULL, NULL, NULL);
1456 im_char[0] = *wstr;
1457 im_char[1] = L'\0';
1459 g_free(wstr);
1463 static void realize(GtkWidget * widget)
1464 /* 'realize' handler */
1466 im = gtk_im_multicontext_new();
1467 g_signal_connect(im, "commit", G_CALLBACK(commit), NULL);
1468 gtk_im_context_set_client_window(im, widget->window);
1472 static gint expose_event(GtkWidget * widget, GdkEventExpose * event)
1473 /* 'expose_event' handler */
1475 redraw();
1477 return FALSE;
1481 static gint configure_event(GtkWidget * widget, GdkEventConfigure * event)
1482 /* 'configure_event' handler */
1484 static GdkEventConfigure o;
1486 if (memcmp(&o, event, sizeof(o)) == 0)
1487 return TRUE;
1489 memcpy(&o, event, sizeof(o));
1491 update_window_size();
1492 redraw();
1494 return TRUE;
1498 static gint selection_clear_event(GtkWidget * widget,
1499 GdkEventSelection * event, gpointer data)
1500 /* 'selection_clear_event' handler */
1502 got_selection = 0;
1504 return TRUE;
1508 static void selection_get(GtkWidget * widget,
1509 GtkSelectionData * sel, guint info, guint tm)
1510 /* 'selection_get' handler */
1512 mpdm_t d;
1513 unsigned char *ptr;
1514 int s;
1516 if (!got_selection)
1517 return;
1519 /* gets the clipboard and joins */
1520 d = mpdm_hget_s(mp, L"clipboard");
1522 if (mpdm_size(d) == 0)
1523 return;
1525 d = mpdm_ref(mpdm_join_s(d, L"\n"));
1527 /* convert to current locale */
1528 ptr = (unsigned char *) mpdm_wcstombs(d->data, &s);
1530 /* pastes into primary selection */
1531 gtk_selection_data_set(sel, GDK_SELECTION_TYPE_STRING, 8, ptr,
1532 (gsize) s);
1534 free(ptr);
1536 mpdm_unref(d);
1540 static void selection_received(GtkWidget * widget,
1541 GtkSelectionData * sel, gpointer data)
1542 /* 'selection_received' handler */
1544 mpdm_t d;
1546 if (sel->data != NULL) {
1547 /* get selection */
1548 wchar_t *wptr = utf8_to_wcs((char *) sel->data);
1549 d = MPDM_S(wptr);
1550 g_free(wptr);
1552 /* split and set as the clipboard */
1553 mpdm_hset_s(mp, L"clipboard", mpdm_split_s(d, L"\n"));
1554 mpdm_hset_s(mp, L"clipboard_vertical", MPDM_I(0));
1556 /* wait no more for the selection */
1557 wait_for_selection = 0;
1559 else
1560 wait_for_selection = -1;
1564 static mpdm_t gtk_drv_clip_to_sys(mpdm_t a, mpdm_t ctxt)
1565 /* driver-dependent mp to system clipboard */
1567 got_selection = gtk_selection_owner_set(area,
1568 GDK_SELECTION_PRIMARY,
1569 GDK_CURRENT_TIME);
1571 return NULL;
1575 static mpdm_t gtk_drv_sys_to_clip(mpdm_t a, mpdm_t ctxt)
1576 /* driver-dependent system to mp clipboard */
1578 if (!got_selection) {
1579 int n;
1580 char *formats[] = { "UTF8_STRING", "STRING", NULL };
1582 for (n = 0; formats[n] != NULL; n++) {
1584 /* triggers a selection capture */
1585 if (gtk_selection_convert(area, GDK_SELECTION_PRIMARY,
1586 gdk_atom_intern(formats[n], FALSE),
1587 GDK_CURRENT_TIME)) {
1589 /* processes the pending events
1590 (i.e., the 'selection_received' handler) */
1591 wait_for_selection = 1;
1593 while (wait_for_selection == 1)
1594 gtk_main_iteration();
1596 if (!wait_for_selection)
1597 break;
1602 return NULL;
1606 /** interface functions **/
1608 static void clicked_ok(GtkWidget * widget, gpointer data)
1609 /* 'clicked_on' signal handler (for gtk_drv_form) */
1611 int n;
1613 for (n = 0; n < mpdm_size(form_args); n++) {
1614 GtkWidget *widget = form_widgets[n];
1615 mpdm_t w = mpdm_aget(form_args, n);
1616 wchar_t *wptr = mpdm_string(mpdm_hget_s(w, L"type"));
1617 mpdm_t v = NULL;
1619 /* if there is already a value there, if was
1620 previously set from a callback */
1621 if (mpdm_aget(form_values, n) != NULL)
1622 continue;
1624 if (wcscmp(wptr, L"text") == 0 || wcscmp(wptr, L"password") == 0) {
1625 char *ptr;
1626 GtkWidget *gw = widget;
1627 mpdm_t h;
1629 if (wcscmp(wptr, L"text") == 0)
1630 gw = GTK_COMBO(widget)->entry;
1632 if ((ptr =
1633 gtk_editable_get_chars(GTK_EDITABLE(gw), 0, -1)) != NULL
1634 && (wptr = utf8_to_wcs(ptr)) != NULL) {
1635 v = MPDM_S(wptr);
1636 g_free(wptr);
1637 g_free(ptr);
1640 mpdm_ref(v);
1642 /* if it has history, fill it */
1643 if ((h = mpdm_hget_s(w, L"history")) != NULL &&
1644 v != NULL && mpdm_cmp_s(v, L"") != 0) {
1645 h = mp_get_history(h);
1647 if (mpdm_cmp(v, mpdm_aget(h, -1)) != 0)
1648 mpdm_push(h, v);
1651 mpdm_unrefnd(v);
1653 else
1654 if (wcscmp(wptr, L"checkbox") == 0) {
1655 v = MPDM_I(gtk_toggle_button_get_active
1656 (GTK_TOGGLE_BUTTON(widget)));
1658 else
1659 if (wcscmp(wptr, L"list") == 0) {
1660 GtkWidget *list = gtk_bin_get_child(GTK_BIN(widget));
1661 GtkTreeSelection *selection =
1662 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1663 GList *selected =
1664 gtk_tree_selection_get_selected_rows(selection, NULL);
1665 GtkTreePath *path = selected->data;
1667 v = MPDM_I(gtk_tree_path_get_indices(path)[0]);
1668 gtk_tree_path_free(path);
1669 g_list_free(selected);
1673 mpdm_aset(form_values, v, n);
1678 static gint timer_callback(gpointer data)
1680 mpdm_void(mpdm_exec(timer_func, NULL, NULL));
1681 redraw();
1683 return TRUE;
1687 static void build_form_data(mpdm_t widget_list)
1688 /* builds the necessary information for a list of widgets */
1690 mpdm_unref(form_args);
1691 form_args = mpdm_ref(widget_list);
1693 mpdm_unref(form_values);
1694 form_values = widget_list == NULL ? NULL :
1695 mpdm_ref(MPDM_A(mpdm_size(form_args)));
1697 /* resize the widget array */
1698 form_widgets = (GtkWidget **) realloc(form_widgets,
1699 mpdm_size(form_args) *
1700 sizeof(GtkWidget *));
1704 /** dialog functions **/
1706 #define DIALOG_BUTTON(l,f) do { GtkWidget * btn; \
1707 ptr = localize(l); btn = gtk_button_new_with_label(ptr); \
1708 g_signal_connect_swapped(G_OBJECT(btn), "clicked", \
1709 G_CALLBACK(f), G_OBJECT(dlg)); \
1710 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), \
1711 btn, TRUE, TRUE, 0); \
1712 g_free(ptr); \
1713 } while (0);
1715 static mpdm_t gtk_drv_alert(mpdm_t a, mpdm_t ctxt)
1716 /* alert driver function */
1718 gchar *ptr;
1719 GtkWidget *dlg;
1721 build_form_data(NULL);
1723 /* 1# arg: prompt */
1724 if ((ptr = v_to_utf8(mpdm_aget(a, 0))) == NULL)
1725 return NULL;
1727 dlg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
1728 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, ptr);
1729 gtk_window_set_title(GTK_WINDOW(dlg), "mp " VERSION);
1730 g_free(ptr);
1732 gtk_dialog_run(GTK_DIALOG(dlg));
1733 gtk_widget_destroy(dlg);
1735 return NULL;
1739 static mpdm_t gtk_drv_confirm(mpdm_t a, mpdm_t ctxt)
1740 /* confirm driver function */
1742 char *ptr;
1743 GtkWidget *dlg;
1744 gint response;
1746 build_form_data(NULL);
1748 /* 1# arg: prompt */
1749 if ((ptr = v_to_utf8(mpdm_aget(a, 0))) == NULL)
1750 return NULL;
1752 dlg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
1753 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1754 ptr);
1755 gtk_window_set_title(GTK_WINDOW(dlg), "mp " VERSION);
1756 g_free(ptr);
1758 gtk_dialog_add_button(GTK_DIALOG(dlg), GTK_STOCK_YES, 1);
1759 gtk_dialog_add_button(GTK_DIALOG(dlg), GTK_STOCK_NO, 2);
1760 gtk_dialog_add_button(GTK_DIALOG(dlg), GTK_STOCK_CANCEL, 0);
1762 response = gtk_dialog_run(GTK_DIALOG(dlg));
1763 gtk_widget_destroy(dlg);
1765 if (response == GTK_RESPONSE_DELETE_EVENT)
1766 response = 0;
1768 return MPDM_I(response);
1772 static mpdm_t gtk_drv_form(mpdm_t a, mpdm_t ctxt)
1773 /* 'form' driver function */
1775 GtkWidget *dlg;
1776 GtkWidget *table;
1777 GtkWidget *content_area;
1778 int n;
1779 mpdm_t ret = NULL;
1781 /* first argument: list of widgets */
1782 build_form_data(mpdm_aget(a, 0));
1784 dlg = gtk_dialog_new_with_buttons("mp " VERSION, GTK_WINDOW(window),
1785 GTK_DIALOG_MODAL |
1786 GTK_DIALOG_NO_SEPARATOR,
1787 GTK_STOCK_CANCEL,
1788 GTK_RESPONSE_CANCEL, GTK_STOCK_OK,
1789 GTK_RESPONSE_OK, NULL);
1790 gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
1791 gtk_container_set_border_width(GTK_CONTAINER(dlg), 5);
1793 content_area = GTK_DIALOG(dlg)->vbox;
1794 gtk_box_set_spacing(GTK_BOX(content_area), 2);
1796 table = gtk_table_new(mpdm_size(a), 2, FALSE);
1797 gtk_container_set_border_width(GTK_CONTAINER(table), 5);
1798 gtk_table_set_col_spacings(GTK_TABLE(table), 12);
1799 gtk_table_set_row_spacings(GTK_TABLE(table), 6);
1801 for (n = 0; n < mpdm_size(form_args); n++) {
1802 mpdm_t w = mpdm_aget(form_args, n);
1803 GtkWidget *widget = NULL;
1804 wchar_t *type;
1805 char *ptr;
1806 mpdm_t t;
1807 int col = 0;
1809 type = mpdm_string(mpdm_hget_s(w, L"type"));
1811 if ((t = mpdm_hget_s(w, L"label")) != NULL) {
1812 GtkWidget *label;
1814 if ((ptr = v_to_utf8(mpdm_gettext(t))) != NULL) {
1815 label = gtk_label_new(ptr);
1816 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
1818 gtk_table_attach_defaults(GTK_TABLE(table),
1819 label, 0, wcscmp(type,
1820 L"label") ==
1821 0 ? 2 : 1, n, n + 1);
1823 g_free(ptr);
1825 col++;
1829 t = mpdm_hget_s(w, L"value");
1831 if (wcscmp(type, L"text") == 0) {
1832 GList *combo_items = NULL;
1833 mpdm_t h;
1835 widget = gtk_combo_new();
1836 gtk_widget_set_size_request(widget, 300, -1);
1837 gtk_combo_set_use_arrows_always(GTK_COMBO(widget), TRUE);
1838 gtk_combo_set_case_sensitive(GTK_COMBO(widget), TRUE);
1839 gtk_entry_set_activates_default(GTK_ENTRY
1840 (GTK_COMBO(widget)->entry),
1841 TRUE);
1843 if ((h = mpdm_hget_s(w, L"history")) != NULL) {
1844 int i;
1846 /* has history; fill it */
1847 h = mp_get_history(h);
1849 for (i = 0; i < mpdm_size(h); i++) {
1850 ptr = v_to_utf8(mpdm_aget(h, i));
1852 combo_items = g_list_prepend(combo_items, ptr);
1856 if (t != NULL) {
1857 ptr = v_to_utf8(t);
1859 combo_items = g_list_prepend(combo_items, ptr);
1862 gtk_combo_set_popdown_strings(GTK_COMBO(widget), combo_items);
1863 g_list_free(combo_items);
1865 else
1866 if (wcscmp(type, L"password") == 0) {
1867 widget = gtk_entry_new();
1868 gtk_widget_set_size_request(widget, 300, -1);
1869 gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
1871 else
1872 if (wcscmp(type, L"checkbox") == 0) {
1873 widget = gtk_check_button_new();
1875 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
1876 mpdm_ival(t) ? TRUE : FALSE);
1878 else
1879 if (wcscmp(type, L"list") == 0) {
1880 GtkWidget *list;
1881 GtkListStore *list_store;
1882 GtkCellRenderer *renderer;
1883 GtkTreeViewColumn *column;
1884 GtkTreePath *path;
1885 mpdm_t l;
1886 gint i;
1888 if ((i = 450 / mpdm_size(form_args)) < 100)
1889 i = 100;
1891 widget = gtk_scrolled_window_new(NULL, NULL);
1892 gtk_widget_set_size_request(widget, 500, i);
1893 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
1894 GTK_POLICY_NEVER,
1895 GTK_POLICY_AUTOMATIC);
1896 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
1897 (widget), GTK_SHADOW_IN);
1899 list_store = gtk_list_store_new(1, G_TYPE_STRING);
1900 list =
1901 gtk_tree_view_new_with_model(GTK_TREE_MODEL(list_store));
1902 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1903 renderer = gtk_cell_renderer_text_new();
1904 column = gtk_tree_view_column_new_with_attributes("", renderer,
1905 "text", 0,
1906 NULL);
1907 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1908 gtk_container_add(GTK_CONTAINER(widget), list);
1910 l = mpdm_hget_s(w, L"list");
1912 for (i = 0; i < mpdm_size(l); i++) {
1913 if ((ptr = v_to_utf8(mpdm_aget(l, i))) != NULL) {
1914 GtkTreeIter iter;
1915 gtk_list_store_append(list_store, &iter);
1916 gtk_list_store_set(list_store, &iter, 0, ptr, -1);
1917 g_free(ptr);
1921 /* initial position */
1922 i = mpdm_ival(t);
1924 path = gtk_tree_path_new_from_indices(i, -1);
1925 gtk_tree_view_set_cursor(GTK_TREE_VIEW(list), path, NULL,
1926 FALSE);
1927 gtk_tree_path_free(path);
1929 g_signal_connect_swapped(G_OBJECT(list), "row-activated",
1930 G_CALLBACK
1931 (gtk_window_activate_default), dlg);
1934 if (widget != NULL) {
1935 form_widgets[n] = widget;
1936 gtk_table_attach_defaults(GTK_TABLE(table),
1937 widget, col, 2, n, n + 1);
1941 gtk_widget_show_all(table);
1943 gtk_box_pack_start(GTK_BOX(content_area), table, TRUE, TRUE, 0);
1945 if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK) {
1946 clicked_ok(NULL, NULL);
1947 ret = form_values;
1949 gtk_widget_destroy(dlg);
1951 return ret;
1955 static mpdm_t run_filechooser(mpdm_t a, gboolean save)
1956 /* openfile driver function */
1958 GtkWidget *dlg;
1959 char *ptr;
1960 mpdm_t ret = NULL;
1961 gint response;
1963 /* 1# arg: prompt */
1964 if ((ptr = v_to_utf8(mpdm_aget(a, 0))) == NULL)
1965 return (NULL);
1967 if (!save) {
1968 dlg = gtk_file_chooser_dialog_new(ptr, GTK_WINDOW(window),
1969 GTK_FILE_CHOOSER_ACTION_OPEN,
1970 GTK_STOCK_CANCEL,
1971 GTK_RESPONSE_CANCEL,
1972 GTK_STOCK_OK, GTK_RESPONSE_OK,
1973 NULL);
1975 else {
1976 dlg = gtk_file_chooser_dialog_new(ptr, GTK_WINDOW(window),
1977 GTK_FILE_CHOOSER_ACTION_SAVE,
1978 GTK_STOCK_CANCEL,
1979 GTK_STOCK_CANCEL, GTK_STOCK_OK,
1980 GTK_RESPONSE_OK, NULL);
1981 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER
1982 (dlg), TRUE);
1984 g_free(ptr);
1986 build_form_data(NULL);
1988 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), TRUE);
1989 response = gtk_dialog_run(GTK_DIALOG(dlg));
1991 if (response == GTK_RESPONSE_OK) {
1992 gchar *filename;
1993 gchar *utf8name;
1994 wchar_t *wfilename;
1996 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
1997 utf8name = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
1998 g_free(filename);
1999 wfilename = utf8_to_wcs(utf8name);
2000 g_free(utf8name);
2001 ret = MPDM_S(wfilename);
2002 g_free(wfilename);
2004 gtk_widget_destroy(dlg);
2006 return ret;
2010 static mpdm_t gtk_drv_openfile(mpdm_t a, mpdm_t ctxt)
2011 /* openfile driver function */
2013 return run_filechooser(a, FALSE);
2017 static mpdm_t gtk_drv_savefile(mpdm_t a, mpdm_t ctxt)
2018 /* savefile driver function */
2020 return run_filechooser(a, TRUE);
2024 static mpdm_t gtk_drv_update_ui(mpdm_t a, mpdm_t ctxt)
2026 build_fonts();
2027 build_colors();
2028 build_menu();
2030 redraw();
2032 return NULL;
2036 static mpdm_t gtk_drv_timer(mpdm_t a, mpdm_t ctxt)
2038 static guint prev = 0;
2039 int msecs = mpdm_ival(mpdm_aget(a, 0));
2040 mpdm_t func = mpdm_aget(a, 1);
2042 /* previously defined one? remove */
2043 if (timer_func != NULL)
2044 gtk_timeout_remove(prev);
2046 /* if msecs and func are set, program timer */
2047 if (msecs > 0 && func != NULL)
2048 prev = gtk_timeout_add(msecs, timer_callback, NULL);
2050 mpdm_ref(func);
2051 mpdm_unref(timer_func);
2052 timer_func = func;
2054 return NULL;
2058 static mpdm_t gtk_drv_busy(mpdm_t a, mpdm_t ctxt)
2060 int onoff = mpdm_ival(mpdm_aget(a, 0));
2062 gdk_window_set_cursor(window->window,
2063 gdk_cursor_new(onoff ? GDK_WATCH :
2064 GDK_LEFT_PTR));
2066 while (gtk_events_pending())
2067 gtk_main_iteration();
2069 return NULL;
2073 static mpdm_t gtk_drv_main_loop(mpdm_t a, mpdm_t ctxt)
2074 /* main loop */
2076 if (!mp_exit_requested) {
2077 gtk_drv_paint(mp_active(), 0);
2079 gtk_main();
2082 return NULL;
2086 static mpdm_t gtk_drv_shutdown(mpdm_t a, mpdm_t ctxt)
2087 /* shutdown */
2089 mpdm_t v;
2091 if ((v = mpdm_hget_s(mp, L"exit_message")) != NULL) {
2092 mpdm_write_wcs(stdout, mpdm_string(v));
2093 printf("\n");
2096 return NULL;
2100 static void register_functions(void)
2102 mpdm_t drv;
2104 drv = mpdm_hget_s(mp, L"drv");
2105 mpdm_hset_s(drv, L"main_loop", MPDM_X(gtk_drv_main_loop));
2106 mpdm_hset_s(drv, L"shutdown", MPDM_X(gtk_drv_shutdown));
2107 mpdm_hset_s(drv, L"clip_to_sys", MPDM_X(gtk_drv_clip_to_sys));
2108 mpdm_hset_s(drv, L"sys_to_clip", MPDM_X(gtk_drv_sys_to_clip));
2109 mpdm_hset_s(drv, L"update_ui", MPDM_X(gtk_drv_update_ui));
2110 mpdm_hset_s(drv, L"timer", MPDM_X(gtk_drv_timer));
2111 mpdm_hset_s(drv, L"busy", MPDM_X(gtk_drv_busy));
2112 mpdm_hset_s(drv, L"alert", MPDM_X(gtk_drv_alert));
2113 mpdm_hset_s(drv, L"confirm", MPDM_X(gtk_drv_confirm));
2114 mpdm_hset_s(drv, L"openfile", MPDM_X(gtk_drv_openfile));
2115 mpdm_hset_s(drv, L"savefile", MPDM_X(gtk_drv_savefile));
2116 mpdm_hset_s(drv, L"form", MPDM_X(gtk_drv_form));
2120 static mpdm_t gtk_drv_startup(mpdm_t a, mpdm_t ctxt)
2121 /* driver initialization */
2123 GtkWidget *vbox;
2124 GtkWidget *hbox;
2125 GdkPixmap *pixmap;
2126 GdkPixmap *mask;
2127 GdkScreen *screen;
2128 mpdm_t v;
2129 int w, h;
2130 GtkTargetEntry targets[] = {
2131 {"text/plain", 0, 0},
2132 {"text/uri-list", 0, 1}
2135 register_functions();
2137 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2139 gtk_window_set_title(GTK_WINDOW(window), "mp " VERSION);
2141 /* get real screen and pick a usable size for the main area */
2142 screen = gtk_window_get_screen(GTK_WINDOW(window));
2143 if (gdk_screen_get_n_monitors(screen) > 1) {
2144 GdkRectangle monitor_one_size;
2145 gdk_screen_get_monitor_geometry(screen, 0, &monitor_one_size);
2147 w = (monitor_one_size.width * 3) / 4;
2148 h = (monitor_one_size.height * 2) / 3;
2149 } else {
2150 w = (gdk_screen_get_width(screen) * 3) / 4;
2151 h = (gdk_screen_get_height(screen) * 2) / 3;
2154 g_signal_connect(G_OBJECT(window), "delete_event",
2155 G_CALLBACK(delete_event), NULL);
2157 g_signal_connect(G_OBJECT(window), "destroy",
2158 G_CALLBACK(destroy), NULL);
2160 /* file tabs */
2161 file_tabs = gtk_notebook_new();
2162 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(file_tabs), GTK_POS_TOP);
2163 GTK_WIDGET_UNSET_FLAGS(file_tabs, GTK_CAN_FOCUS);
2164 gtk_notebook_set_scrollable(GTK_NOTEBOOK(file_tabs), 1);
2166 vbox = gtk_vbox_new(FALSE, 2);
2167 gtk_container_add(GTK_CONTAINER(window), vbox);
2169 build_menu();
2171 hbox = gtk_hbox_new(FALSE, 0);
2172 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2173 gtk_box_pack_start(GTK_BOX(hbox), menu_bar, FALSE, FALSE, 0);
2174 gtk_box_pack_start(GTK_BOX(hbox), file_tabs, TRUE, TRUE, 0);
2176 gtk_notebook_popup_enable(GTK_NOTEBOOK(file_tabs));
2178 /* horizontal box holding the text and the scrollbar */
2179 hbox = gtk_hbox_new(FALSE, 2);
2180 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
2182 /* the Minimum Profit area */
2183 area = gtk_drawing_area_new();
2184 gtk_box_pack_start(GTK_BOX(hbox), area, TRUE, TRUE, 0);
2185 gtk_widget_set_size_request(GTK_WIDGET(area), w, h);
2186 gtk_widget_set_events(GTK_WIDGET(area), GDK_BUTTON_PRESS_MASK |
2187 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
2188 | GDK_LEAVE_NOTIFY_MASK);
2190 gtk_widget_set_double_buffered(area, FALSE);
2192 g_signal_connect(G_OBJECT(area), "configure_event",
2193 G_CALLBACK(configure_event), NULL);
2195 g_signal_connect(G_OBJECT(area), "expose_event",
2196 G_CALLBACK(expose_event), NULL);
2198 g_signal_connect(G_OBJECT(area), "realize", G_CALLBACK(realize), NULL);
2200 g_signal_connect(G_OBJECT(window), "key_press_event",
2201 G_CALLBACK(key_press_event), NULL);
2203 g_signal_connect(G_OBJECT(window), "key_release_event",
2204 G_CALLBACK(key_release_event), NULL);
2206 g_signal_connect(G_OBJECT(area), "button_press_event",
2207 G_CALLBACK(button_press_event), NULL);
2209 g_signal_connect(G_OBJECT(area), "button_release_event",
2210 G_CALLBACK(button_release_event), NULL);
2212 g_signal_connect(G_OBJECT(area), "motion_notify_event",
2213 G_CALLBACK(motion_notify_event), NULL);
2215 g_signal_connect(G_OBJECT(area), "selection_clear_event",
2216 G_CALLBACK(selection_clear_event), NULL);
2218 g_signal_connect(G_OBJECT(area), "selection_get",
2219 G_CALLBACK(selection_get), NULL);
2221 g_signal_connect(G_OBJECT(area), "selection_received",
2222 G_CALLBACK(selection_received), NULL);
2224 g_signal_connect(G_OBJECT(area), "scroll_event",
2225 G_CALLBACK(scroll_event), NULL);
2227 gtk_drag_dest_set(area, GTK_DEST_DEFAULT_ALL, targets,
2228 sizeof(targets) / sizeof(GtkTargetEntry),
2229 GDK_ACTION_COPY);
2230 g_signal_connect(G_OBJECT(area), "drag_data_received",
2231 G_CALLBACK(drag_data_received), NULL);
2233 gtk_selection_add_target(area, GDK_SELECTION_PRIMARY,
2234 GDK_SELECTION_TYPE_STRING, 1);
2236 g_signal_connect(G_OBJECT(file_tabs), "switch_page",
2237 G_CALLBACK(switch_page), NULL);
2239 /* the scrollbar */
2240 scrollbar = gtk_vscrollbar_new(NULL);
2241 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, FALSE, 0);
2243 g_signal_connect(G_OBJECT
2244 (gtk_range_get_adjustment(GTK_RANGE(scrollbar))),
2245 "value_changed", G_CALLBACK(value_changed), NULL);
2247 /* the status bar */
2248 status = gtk_label_new("mp " VERSION);
2249 gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0);
2250 gtk_misc_set_alignment(GTK_MISC(status), 0, 0.5);
2251 gtk_label_set_justify(GTK_LABEL(status), GTK_JUSTIFY_LEFT);
2253 gtk_widget_show_all(window);
2255 /* set application icon */
2256 pixmap = gdk_pixmap_create_from_xpm_d(window->window,
2257 &mask, NULL, mp_xpm);
2258 gdk_window_set_icon(window->window, NULL, pixmap, mask);
2260 build_colors();
2262 if ((v = mpdm_hget_s(mp, L"config")) != NULL &&
2263 mpdm_ival(mpdm_hget_s(v, L"maximize")) > 0)
2264 maximize = 1;
2266 return NULL;
2270 int gtk_drv_detect(int *argc, char ***argv)
2272 mpdm_t drv;
2273 int n;
2275 for (n = 0; n < *argc; n++) {
2276 if (strcmp(argv[0][n], "-txt") == 0 ||
2277 strcmp(argv[0][n], "-h") == 0)
2278 return 0;
2281 if (!gtk_init_check(argc, argv))
2282 return 0;
2284 drv = mpdm_hget_s(mp, L"drv");
2285 mpdm_hset_s(drv, L"id", MPDM_LS(L"gtk"));
2286 mpdm_hset_s(drv, L"startup", MPDM_X(gtk_drv_startup));
2288 return 1;
2291 #endif /* CONFOPT_GTK */