More GTK 2.x compatible fixes towards GTK 3.x support.
[mp-5.x.git] / mpv_gtk.c
blob32f1b54db64fc23708c529bd3af25412c03ea412
1 /*
3 Minimum Profit - Programmer Text Editor
5 GTK driver.
7 Copyright (C) 1991-2011 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 GtkIMContext *im = NULL;
60 /* character read from the keyboard */
61 static wchar_t im_char[2];
63 /* font information */
64 static int font_width = 0;
65 static int font_height = 0;
66 static PangoFontDescription *font = NULL;
68 /* the attributes */
69 static GdkColor *inks = NULL;
70 static GdkColor *papers = NULL;
71 static int *underlines = NULL;
73 /* true if the selection is ours */
74 static int got_selection = 0;
76 /* hack for active waiting for the selection */
77 static int wait_for_selection = 0;
79 /* global modal status */
80 /* code for the 'normal' attribute */
81 static int normal_attr = 0;
83 /* mp.drv.form() controls */
85 static GtkWidget **form_widgets = NULL;
86 static mpdm_t form_args = NULL;
87 static mpdm_t form_values = NULL;
89 /* mouse down flag */
90 static int mouse_down = 0;
92 /* timer function */
93 static mpdm_t timer_func = NULL;
95 /* maximize wanted? */
96 static int maximize = 0;
98 /** code **/
100 /** support functions **/
102 #define LL(m) (m)
104 static char *wcs_to_utf8(const wchar_t * wptr)
105 /* converts a wcs to utf-8 */
107 char *ptr;
108 gsize i, o;
110 i = wcslen(wptr);
112 /* do the conversion */
113 ptr = g_convert((const gchar *) wptr, (i + 1) * sizeof(wchar_t),
114 "UTF-8", "WCHAR_T", NULL, &o, NULL);
116 return ptr;
120 static char *v_to_utf8(mpdm_t v)
122 char *ptr = NULL;
124 if (v != NULL) {
125 mpdm_ref(v);
126 ptr = wcs_to_utf8(mpdm_string(v));
127 mpdm_unref(v);
130 return ptr;
133 static wchar_t *utf8_to_wcs(const char *ptr)
134 /* converts utf-8 to wcs */
136 wchar_t *wptr;
137 gsize i, o;
139 i = strlen(ptr);
141 /* do the conversion */
142 wptr = (wchar_t *) g_convert((gchar *) ptr, i + 1,
143 "WCHAR_T", "UTF-8", NULL, &o, NULL);
145 return wptr;
149 static void update_window_size(void)
150 /* updates the viewport size in characters */
152 PangoLayout *pa;
153 int tx, ty;
154 mpdm_t v;
156 /* get font metrics */
157 pa = gtk_widget_create_pango_layout(area, "m");
158 pango_layout_set_font_description(pa, font);
159 pango_layout_get_pixel_size(pa, &font_width, &font_height);
160 g_object_unref(pa);
162 /* calculate the size in chars */
163 tx = (area->allocation.width / font_width);
164 ty = (area->allocation.height / font_height) + 1;
166 /* store the 'window' size */
167 v = mpdm_hget_s(mp, L"window");
168 mpdm_hset_s(v, L"tx", MPDM_I(tx));
169 mpdm_hset_s(v, L"ty", MPDM_I(ty));
171 /* rebuild the pixmap for the double buffer */
175 static void build_fonts(void)
176 /* builds the fonts */
178 char tmp[128];
179 int font_size = 12;
180 const char *font_face = "Mono";
181 mpdm_t c;
182 mpdm_t w = NULL;
184 if (font != NULL)
185 pango_font_description_free(font);
187 /* get current configuration */
188 if ((c = mpdm_hget_s(mp, L"config")) != NULL) {
189 mpdm_t v;
191 if ((v = mpdm_hget_s(c, L"font_size")) != NULL)
192 font_size = mpdm_ival(v);
193 else
194 mpdm_hset_s(c, L"font_size", MPDM_I(font_size));
196 if ((v = mpdm_hget_s(c, L"font_face")) != NULL) {
197 w = mpdm_ref(MPDM_2MBS(v->data));
198 font_face = w->data;
200 else
201 mpdm_hset_s(c, L"font_face", MPDM_MBS(font_face));
204 snprintf(tmp, sizeof(tmp) - 1, "%s %d", font_face, font_size);
205 tmp[sizeof(tmp) - 1] = '\0';
207 font = pango_font_description_from_string(tmp);
208 update_window_size();
210 mpdm_unref(w);
214 static void build_color(GdkColor * gdkcolor, int rgb)
215 /* builds a color */
217 gdkcolor->pixel = 0;
218 gdkcolor->blue = (rgb & 0x000000ff) << 8;
219 gdkcolor->green = (rgb & 0x0000ff00);
220 gdkcolor->red = (rgb & 0x00ff0000) >> 8;
221 gdk_colormap_alloc_color(gdk_colormap_get_system(), gdkcolor, FALSE,
222 TRUE);
226 static void build_colors(void)
227 /* builds the colors */
229 mpdm_t colors;
230 mpdm_t l;
231 mpdm_t c;
232 int n, s;
234 /* gets the color definitions and attribute names */
235 colors = mpdm_hget_s(mp, L"colors");
236 l = mpdm_ref(mpdm_keys(colors));
237 s = mpdm_size(l);
239 /* redim the structures */
240 inks = realloc(inks, sizeof(GdkColor) * s);
241 papers = realloc(papers, sizeof(GdkColor) * s);
242 underlines = realloc(underlines, sizeof(int) * s);
244 /* loop the colors */
245 for (n = 0; n < s && (c = mpdm_aget(l, n)) != NULL; n++) {
246 mpdm_t d = mpdm_hget(colors, c);
247 mpdm_t v = mpdm_hget_s(d, L"gui");
249 /* store the 'normal' attribute */
250 if (wcscmp(mpdm_string(c), L"normal") == 0)
251 normal_attr = n;
253 /* store the attr */
254 mpdm_hset_s(d, L"attr", MPDM_I(n));
256 build_color(&inks[n], mpdm_ival(mpdm_aget(v, 0)));
257 build_color(&papers[n], mpdm_ival(mpdm_aget(v, 1)));
259 /* flags */
260 v = mpdm_hget_s(d, L"flags");
261 underlines[n] = mpdm_seek_s(v, L"underline", 1) != -1 ? 1 : 0;
263 if (mpdm_seek_s(v, L"reverse", 1) != -1) {
264 GdkColor t;
266 t = inks[n];
267 inks[n] = papers[n];
268 papers[n] = t;
272 mpdm_unref(l);
276 /** menu functions **/
278 static void redraw(void);
280 static void menu_item_callback(mpdm_t action)
281 /* menu click callback */
283 mp_process_action(action);
284 redraw();
286 if (mp_exit_requested)
287 gtk_main_quit();
291 static void build_submenu(GtkWidget * menu, mpdm_t labels)
292 /* build a submenu */
294 int n;
295 GtkWidget *menu_item;
297 mpdm_ref(labels);
299 for (n = 0; n < mpdm_size(labels); n++) {
300 /* get the action */
301 mpdm_t v = mpdm_aget(labels, n);
303 /* if the action is a separator... */
304 if (*((wchar_t *) v->data) == L'-')
305 menu_item = gtk_menu_item_new();
306 else {
307 char *ptr;
309 ptr = v_to_utf8(mp_menu_label(v));
310 menu_item = gtk_menu_item_new_with_label(ptr);
311 g_free(ptr);
314 gtk_menu_append(GTK_MENU(menu), menu_item);
315 g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
316 G_CALLBACK(menu_item_callback), v);
317 gtk_widget_show(menu_item);
320 mpdm_unref(labels);
324 static void build_menu(void)
325 /* builds the menu */
327 static mpdm_t prev_menu = NULL;
328 int n;
329 mpdm_t m;
331 /* gets the current menu */
332 if ((m = mpdm_hget_s(mp, L"menu")) == NULL)
333 return;
335 /* if it's the same, do nothing */
336 if (mpdm_cmp(m, prev_menu) == 0)
337 return;
339 /* create a new menu */
340 menu_bar = gtk_menu_bar_new();
342 for (n = 0; n < mpdm_size(m); n++) {
343 char *ptr;
344 mpdm_t mi;
345 mpdm_t v;
346 GtkWidget *menu;
347 GtkWidget *menu_item;
348 int i;
350 /* get the label and the items */
351 mi = mpdm_aget(m, n);
352 v = mpdm_aget(mi, 0);
354 if ((ptr = v_to_utf8(mpdm_gettext(v))) == NULL)
355 continue;
357 /* change the & by _ for the mnemonic */
358 for (i = 0; ptr[i]; i++)
359 if (ptr[i] == '&')
360 ptr[i] = '_';
362 /* add the menu and the label */
363 menu = gtk_menu_new();
364 menu_item = gtk_menu_item_new_with_mnemonic(ptr);
365 g_free(ptr);
367 gtk_widget_show(menu_item);
368 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu);
369 gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), menu_item);
371 /* now loop the items */
372 build_submenu(menu, mpdm_aget(mi, 1));
377 /** main area drawing functions **/
379 static void switch_page(GtkNotebook * notebook, gpointer * page,
380 gint pg_num, gpointer data)
381 /* 'switch_page' handler (filetabs) */
383 /* sets the active one */
384 mpdm_hset_s(mp, L"active_i", MPDM_I(pg_num));
386 gtk_widget_grab_focus(area);
387 redraw();
391 static void draw_filetabs(void)
392 /* draws the filetabs */
394 static mpdm_t last = NULL;
395 mpdm_t names;
396 int n;
398 names = mpdm_ref(mp_get_doc_names());
400 /* disconnect redraw signal to avoid infinite loops */
401 g_signal_handlers_disconnect_by_func(G_OBJECT(file_tabs),
402 G_CALLBACK(switch_page), NULL);
404 /* is the list different from the previous one? */
405 if (mpdm_cmp(names, last) != 0) {
407 /* delete the current tabs */
408 for (n = 0; n < mpdm_size(last); n++)
409 gtk_notebook_remove_page(GTK_NOTEBOOK(file_tabs), 0);
411 /* create the new ones */
412 for (n = 0; n < mpdm_size(names); n++) {
413 GtkWidget *p;
414 GtkWidget *f;
415 char *ptr;
416 mpdm_t v = mpdm_aget(names, n);
418 if ((ptr = v_to_utf8(v)) != NULL) {
419 p = gtk_label_new(ptr);
420 gtk_widget_show(p);
422 f = gtk_frame_new(NULL);
423 gtk_widget_show(f);
425 gtk_notebook_append_page(GTK_NOTEBOOK(file_tabs), f, p);
427 g_free(ptr);
431 /* store for the next time */
432 mpdm_unref(last);
433 last = mpdm_ref(names);
436 mpdm_unref(names);
438 /* set the active one */
439 gtk_notebook_set_current_page(GTK_NOTEBOOK(file_tabs),
440 mpdm_ival(mpdm_hget_s(mp, L"active_i")));
442 /* reconnect signal */
443 g_signal_connect(G_OBJECT(file_tabs), "switch_page",
444 G_CALLBACK(switch_page), NULL);
446 gtk_widget_grab_focus(area);
450 static void draw_status(void)
451 /* draws the status line */
453 char *ptr;
455 if ((ptr = v_to_utf8(mp_build_status_line())) != NULL) {
456 gtk_label_set_text(GTK_LABEL(status), ptr);
457 g_free(ptr);
462 static gint scroll_event(GtkWidget * widget, GdkEventScroll * event)
463 /* 'scroll_event' handler (mouse wheel) */
465 wchar_t *ptr = NULL;
467 switch (event->direction) {
468 case GDK_SCROLL_UP:
469 ptr = L"mouse-wheel-up";
470 break;
471 case GDK_SCROLL_DOWN:
472 ptr = L"mouse-wheel-down";
473 break;
474 case GDK_SCROLL_LEFT:
475 ptr = L"mouse-wheel-left";
476 break;
477 case GDK_SCROLL_RIGHT:
478 ptr = L"mouse-wheel-right";
479 break;
482 if (ptr != NULL)
483 mp_process_event(MPDM_S(ptr));
485 redraw();
487 return 0;
491 static void value_changed(GtkAdjustment * adj, gpointer * data)
492 /* 'value_changed' handler (scrollbar) */
494 int i = (int) gtk_adjustment_get_value(adj);
495 mpdm_t doc;
496 mpdm_t txt;
497 int y;
499 /* get current y position */
500 doc = mp_active();
501 txt = mpdm_hget_s(doc, L"txt");
502 y = mpdm_ival(mpdm_hget_s(txt, L"y"));
504 /* if it's different, set and redraw */
505 if (y != i) {
506 mp_set_y(doc, i);
507 mpdm_hset_s(txt, L"vy", MPDM_I(i));
508 redraw();
513 static void draw_scrollbar(void)
514 /* updates the scrollbar */
516 GtkAdjustment *adjustment;
517 mpdm_t d;
518 mpdm_t v;
519 int pos, size, max;
521 /* gets the active document */
522 if ((d = mp_active()) == NULL)
523 return;
525 /* get the coordinates */
526 v = mpdm_hget_s(d, L"txt");
527 pos = mpdm_ival(mpdm_hget_s(v, L"vy"));
528 max = mpdm_size(mpdm_hget_s(v, L"lines"));
530 v = mpdm_hget_s(mp, L"window");
531 size = mpdm_ival(mpdm_hget_s(v, L"ty"));
533 adjustment = gtk_range_get_adjustment(GTK_RANGE(scrollbar));
535 /* disconnect to avoid infinite loops */
536 g_signal_handlers_disconnect_by_func(G_OBJECT(adjustment),
537 G_CALLBACK(value_changed), NULL);
539 gtk_adjustment_set_step_increment(adjustment, (gdouble) 1);
540 gtk_adjustment_set_upper(adjustment, (gdouble) max);
541 gtk_adjustment_set_page_size(adjustment, (gdouble) size);
542 gtk_adjustment_set_page_increment(adjustment, (gdouble) size);
543 gtk_adjustment_set_value(adjustment, (gdouble) pos);
545 gtk_range_set_adjustment(GTK_RANGE(scrollbar), adjustment);
547 gtk_adjustment_changed(adjustment);
548 gtk_adjustment_value_changed(adjustment);
550 /* reattach again */
551 g_signal_connect(G_OBJECT
552 (gtk_range_get_adjustment(GTK_RANGE(scrollbar))),
553 "value_changed", G_CALLBACK(value_changed), NULL);
557 static void gtk_drv_paint(mpdm_t doc, int optimize)
558 /* GTK document draw function */
560 GdkRectangle gr;
561 cairo_t *cr;
562 mpdm_t d = NULL;
563 int n, m;
565 if (maximize)
566 gtk_window_maximize(GTK_WINDOW(window));
568 /* no gc? create it */
569 if (font == NULL)
570 build_fonts();
572 if ((d = mp_draw(doc, optimize)) == NULL)
573 return;
575 mpdm_ref(d);
577 gr.x = 0;
578 gr.y = 0;
579 gr.width = area->allocation.width;
580 gr.height = font_height;
582 cr = gdk_cairo_create(gtk_widget_get_window(area));
583 for (n = 0; n < mpdm_size(d); n++) {
584 PangoLayout *pl;
585 PangoAttrList *pal;
586 mpdm_t l = mpdm_aget(d, n);
587 char *str = NULL;
588 int u, p;
590 if (l == NULL)
591 continue;
593 /* create the pango stuff */
594 pl = gtk_widget_create_pango_layout(area, NULL);
595 pango_layout_set_font_description(pl, font);
596 pal = pango_attr_list_new();
598 for (m = u = p = 0; m < mpdm_size(l); m++, u = p) {
599 PangoAttribute *pa;
600 int attr;
601 mpdm_t s;
602 char *ptr;
604 /* get the attribute and the string */
605 attr = mpdm_ival(mpdm_aget(l, m++));
606 s = mpdm_aget(l, m);
608 /* convert the string to utf8 */
609 ptr = v_to_utf8(s);
611 /* add to the full line */
612 str = mpdm_poke(str, &p, ptr, strlen(ptr), 1);
614 g_free(ptr);
616 /* create the background if it's
617 different from the default */
618 if (papers[attr].red != papers[normal_attr].red ||
619 papers[attr].green != papers[normal_attr].green ||
620 papers[attr].blue != papers[normal_attr].blue) {
621 pa = pango_attr_background_new(papers[attr].red,
622 papers[attr].green,
623 papers[attr].blue);
625 pa->start_index = u;
626 pa->end_index = p;
628 pango_attr_list_insert(pal, pa);
631 /* underline? */
632 if (underlines[attr]) {
633 pa = pango_attr_underline_new(TRUE);
635 pa->start_index = u;
636 pa->end_index = p;
638 pango_attr_list_insert(pal, pa);
641 /* foreground color */
642 pa = pango_attr_foreground_new(inks[attr].red,
643 inks[attr].green,
644 inks[attr].blue);
646 pa->start_index = u;
647 pa->end_index = p;
649 pango_attr_list_insert(pal, pa);
652 /* store the attributes */
653 pango_layout_set_attributes(pl, pal);
654 pango_attr_list_unref(pal);
656 /* store and free the text */
657 pango_layout_set_text(pl, str, p);
658 free(str);
660 /* draw the background */
661 gdk_cairo_set_source_color(cr, &papers[normal_attr]);
662 cairo_rectangle(cr, 0, n * font_height, gr.width, gr.height);
663 cairo_fill(cr);
665 /* draw the text */
666 cairo_move_to(cr, 2, n * font_height);
667 pango_cairo_show_layout(cr, pl);
669 /* dump the pixmap */
671 g_object_unref(pl);
674 cairo_destroy(cr);
675 mpdm_unref(d);
677 draw_filetabs();
678 draw_scrollbar();
679 draw_status();
683 static void redraw(void)
685 if (mpdm_size(mpdm_hget_s(mp, L"docs")))
686 gtk_drv_paint(mp_active(), 0);
690 static gint delete_event(GtkWidget * w, GdkEvent * e, gpointer data)
691 /* 'delete_event' handler */
693 mp_process_event(MPDM_LS(L"close-window"));
695 return mp_exit_requested ? FALSE : TRUE;
699 static void destroy(GtkWidget * w, gpointer data)
700 /* 'destroy' handler */
702 gtk_main_quit();
706 static gint key_release_event(GtkWidget * widget, GdkEventKey * event,
707 gpointer data)
708 /* 'key_release_event' handler */
710 if (mp_keypress_throttle(0))
711 gtk_drv_paint(mp_active(), 0);
713 return 0;
717 static gint key_press_event(GtkWidget * widget, GdkEventKey * event,
718 gpointer data)
719 /* 'key_press_event' handler */
721 wchar_t *ptr = NULL;
723 gtk_im_context_filter_keypress(im, event);
725 /* set mp.shift_pressed */
726 if (event->state & (GDK_SHIFT_MASK))
727 mpdm_hset_s(mp, L"shift_pressed", MPDM_I(1));
729 /* reserve alt for menu mnemonics */
730 /* if (GDK_MOD1_MASK & event->state)
731 return(0);*/
733 if (event->state & (GDK_CONTROL_MASK)) {
734 switch (event->keyval) {
735 case GDK_Up:
736 ptr = L"ctrl-cursor-up";
737 break;
738 case GDK_Down:
739 ptr = L"ctrl-cursor-down";
740 break;
741 case GDK_Left:
742 ptr = L"ctrl-cursor-left";
743 break;
744 case GDK_Right:
745 ptr = L"ctrl-cursor-right";
746 break;
747 case GDK_Prior:
748 ptr = L"ctrl-page-up";
749 break;
750 case GDK_Next:
751 ptr = L"ctrl-page-down";
752 break;
753 case GDK_Home:
754 ptr = L"ctrl-home";
755 break;
756 case GDK_End:
757 ptr = L"ctrl-end";
758 break;
759 case GDK_space:
760 ptr = L"ctrl-space";
761 break;
762 case GDK_KP_Add:
763 ptr = L"ctrl-kp-plus";
764 break;
765 case GDK_KP_Subtract:
766 ptr = L"ctrl-kp-minus";
767 break;
768 case GDK_KP_Multiply:
769 ptr = L"ctrl-kp-multiply";
770 break;
771 case GDK_KP_Divide:
772 ptr = L"ctrl-kp-divide";
773 break;
774 case GDK_F1:
775 ptr = L"ctrl-f1";
776 break;
777 case GDK_F2:
778 ptr = L"ctrl-f2";
779 break;
780 case GDK_F3:
781 ptr = L"ctrl-f3";
782 break;
783 case GDK_F4:
784 ptr = L"ctrl-f4";
785 break;
786 case GDK_F5:
787 ptr = L"ctrl-f5";
788 break;
789 case GDK_F6:
790 ptr = L"ctrl-f6";
791 break;
792 case GDK_F7:
793 ptr = L"ctrl-f7";
794 break;
795 case GDK_F8:
796 ptr = L"ctrl-f8";
797 break;
798 case GDK_F9:
799 ptr = L"ctrl-f9";
800 break;
801 case GDK_F10:
802 ptr = L"ctrl-f10";
803 break;
804 case GDK_F11:
805 ptr = L"ctrl-f11";
806 break;
807 case GDK_F12:
808 ptr = L"ctrl-f12";
809 break;
810 case GDK_KP_Enter:
811 case GDK_Return:
812 ptr = L"ctrl-enter";
813 break;
814 case GDK_Cyrillic_ve:
815 ptr = L"ctrl-d";
816 break;
817 case GDK_Cyrillic_a:
818 ptr = L"ctrl-f";
819 break;
820 case GDK_Cyrillic_tse:
821 ptr = L"ctrl-w";
822 break;
823 case GDK_Cyrillic_de:
824 ptr = L"ctrl-l";
825 break;
826 case GDK_Cyrillic_ie:
827 ptr = L"ctrl-t";
828 break;
829 case GDK_Cyrillic_ef:
830 ptr = L"ctrl-a";
831 break;
832 case GDK_Cyrillic_ghe:
833 ptr = L"ctrl-u";
834 break;
835 case GDK_Cyrillic_i:
836 ptr = L"ctrl-b";
837 break;
838 case GDK_Cyrillic_shorti:
839 ptr = L"ctrl-q";
840 break;
841 case GDK_Cyrillic_ka:
842 ptr = L"ctrl-r";
843 break;
844 case GDK_Cyrillic_el:
845 ptr = L"ctrl-k";
846 break;
847 case GDK_Cyrillic_em:
848 ptr = L"ctrl-v";
849 break;
850 case GDK_Cyrillic_en:
851 ptr = L"ctrl-y";
852 break;
853 case GDK_Cyrillic_o:
854 ptr = L"ctrl-j";
855 break;
856 case GDK_Cyrillic_pe:
857 ptr = L"ctrl-g";
858 break;
859 case GDK_Cyrillic_ya:
860 ptr = L"ctrl-z";
861 break;
862 case GDK_Cyrillic_er:
863 ptr = L"ctrl-h";
864 break;
865 case GDK_Cyrillic_es:
866 ptr = L"ctrl-c";
867 break;
868 case GDK_Cyrillic_te:
869 ptr = L"ctrl-n";
870 break;
871 case GDK_Cyrillic_softsign:
872 ptr = L"ctrl-m";
873 break;
874 case GDK_Cyrillic_yeru:
875 ptr = L"ctrl-s";
876 break;
877 case GDK_Cyrillic_ze:
878 ptr = L"ctrl-p";
879 break;
880 case GDK_Cyrillic_sha:
881 ptr = L"ctrl-i";
882 break;
883 case GDK_Cyrillic_e:
884 ptr = L"ctrl-t";
885 break;
886 case GDK_Cyrillic_shcha:
887 ptr = L"ctrl-o";
888 break;
889 case GDK_Cyrillic_che:
890 ptr = L"ctrl-x";
891 break;
894 if (ptr == NULL) {
895 char c = event->keyval & 0xdf;
897 switch (c) {
898 case 'A':
899 ptr = L"ctrl-a";
900 break;
901 case 'B':
902 ptr = L"ctrl-b";
903 break;
904 case 'C':
905 ptr = L"ctrl-c";
906 break;
907 case 'D':
908 ptr = L"ctrl-d";
909 break;
910 case 'E':
911 ptr = L"ctrl-e";
912 break;
913 case 'F':
914 ptr = L"ctrl-f";
915 break;
916 case 'G':
917 ptr = L"ctrl-g";
918 break;
919 case 'H':
920 ptr = L"ctrl-h";
921 break;
922 case 'I':
923 ptr = L"ctrl-i";
924 break;
925 case 'J':
926 ptr = L"ctrl-j";
927 break;
928 case 'K':
929 ptr = L"ctrl-k";
930 break;
931 case 'L':
932 ptr = L"ctrl-l";
933 break;
934 case 'M':
935 ptr = L"ctrl-m";
936 break;
937 case 'N':
938 ptr = L"ctrl-n";
939 break;
940 case 'O':
941 ptr = L"ctrl-o";
942 break;
943 case 'P':
944 ptr = L"ctrl-p";
945 break;
946 case 'Q':
947 ptr = L"ctrl-q";
948 break;
949 case 'R':
950 ptr = L"ctrl-r";
951 break;
952 case 'S':
953 ptr = L"ctrl-s";
954 break;
955 case 'T':
956 ptr = L"ctrl-t";
957 break;
958 case 'U':
959 ptr = L"ctrl-u";
960 break;
961 case 'V':
962 ptr = L"ctrl-v";
963 break;
964 case 'W':
965 ptr = L"ctrl-w";
966 break;
967 case 'X':
968 ptr = L"ctrl-x";
969 break;
970 case 'Y':
971 ptr = L"ctrl-y";
972 break;
973 case 'Z':
974 ptr = L"ctrl-z";
975 break;
979 else
980 if (event->state & (GDK_MOD1_MASK)) {
981 switch (event->keyval) {
982 case GDK_Up:
983 ptr = L"alt-cursor-up";
984 break;
985 case GDK_Down:
986 ptr = L"alt-cursor-down";
987 break;
988 case GDK_Left:
989 ptr = L"alt-cursor-left";
990 break;
991 case GDK_Right:
992 ptr = L"alt-cursor-right";
993 break;
994 case GDK_Prior:
995 ptr = L"alt-page-up";
996 break;
997 case GDK_Next:
998 ptr = L"alt-page-down";
999 break;
1000 case GDK_Home:
1001 ptr = L"alt-home";
1002 break;
1003 case GDK_End:
1004 ptr = L"alt-end";
1005 break;
1006 case GDK_space:
1007 ptr = L"alt-space";
1008 break;
1009 case GDK_KP_Add:
1010 ptr = L"alt-kp-plus";
1011 break;
1012 case GDK_KP_Subtract:
1013 ptr = L"alt-kp-minus";
1014 break;
1015 case GDK_KP_Multiply:
1016 ptr = L"alt-kp-multiply";
1017 break;
1018 case GDK_KP_Divide:
1019 ptr = L"alt-kp-divide";
1020 break;
1021 case GDK_F1:
1022 ptr = L"alt-f1";
1023 break;
1024 case GDK_F2:
1025 ptr = L"alt-f2";
1026 break;
1027 case GDK_F3:
1028 ptr = L"alt-f3";
1029 break;
1030 case GDK_F4:
1031 ptr = L"alt-f4";
1032 break;
1033 case GDK_F5:
1034 ptr = L"alt-f5";
1035 break;
1036 case GDK_F6:
1037 ptr = L"alt-f6";
1038 break;
1039 case GDK_F7:
1040 ptr = L"alt-f7";
1041 break;
1042 case GDK_F8:
1043 ptr = L"alt-f8";
1044 break;
1045 case GDK_F9:
1046 ptr = L"alt-f9";
1047 break;
1048 case GDK_F10:
1049 ptr = L"alt-f10";
1050 break;
1051 case GDK_F11:
1052 ptr = L"alt-f11";
1053 break;
1054 case GDK_F12:
1055 ptr = L"alt-f12";
1056 break;
1057 case GDK_KP_Enter:
1058 case GDK_Return:
1059 ptr = L"alt-enter";
1060 break;
1061 case GDK_Cyrillic_ve:
1062 ptr = L"alt-d";
1063 break;
1064 case GDK_Cyrillic_a:
1065 ptr = L"alt-f";
1066 break;
1067 case GDK_Cyrillic_tse:
1068 ptr = L"alt-w";
1069 break;
1070 case GDK_Cyrillic_de:
1071 ptr = L"alt-l";
1072 break;
1073 case GDK_Cyrillic_ie:
1074 ptr = L"alt-t";
1075 break;
1076 case GDK_Cyrillic_ef:
1077 ptr = L"alt-a";
1078 break;
1079 case GDK_Cyrillic_ghe:
1080 ptr = L"alt-u";
1081 break;
1082 case GDK_Cyrillic_i:
1083 ptr = L"alt-b";
1084 break;
1085 case GDK_Cyrillic_shorti:
1086 ptr = L"alt-q";
1087 break;
1088 case GDK_Cyrillic_ka:
1089 ptr = L"alt-r";
1090 break;
1091 case GDK_Cyrillic_el:
1092 ptr = L"alt-k";
1093 break;
1094 case GDK_Cyrillic_em:
1095 ptr = L"alt-v";
1096 break;
1097 case GDK_Cyrillic_en:
1098 ptr = L"alt-y";
1099 break;
1100 case GDK_Cyrillic_o:
1101 ptr = L"alt-j";
1102 break;
1103 case GDK_Cyrillic_pe:
1104 ptr = L"alt-g";
1105 break;
1106 case GDK_Cyrillic_ya:
1107 ptr = L"alt-z";
1108 break;
1109 case GDK_Cyrillic_er:
1110 ptr = L"alt-h";
1111 break;
1112 case GDK_Cyrillic_es:
1113 ptr = L"alt-c";
1114 break;
1115 case GDK_Cyrillic_te:
1116 ptr = L"alt-n";
1117 break;
1118 case GDK_Cyrillic_softsign:
1119 ptr = L"alt-m";
1120 break;
1121 case GDK_Cyrillic_yeru:
1122 ptr = L"alt-s";
1123 break;
1124 case GDK_Cyrillic_ze:
1125 ptr = L"alt-p";
1126 break;
1127 case GDK_Cyrillic_sha:
1128 ptr = L"alt-i";
1129 break;
1130 case GDK_Cyrillic_e:
1131 ptr = L"alt-t";
1132 break;
1133 case GDK_Cyrillic_shcha:
1134 ptr = L"alt-o";
1135 break;
1136 case GDK_Cyrillic_che:
1137 ptr = L"alt-x";
1138 break;
1141 if (ptr == NULL) {
1142 char c = event->keyval & 0xdf;
1144 switch (c) {
1145 case 'A':
1146 ptr = L"alt-a";
1147 break;
1148 case 'B':
1149 ptr = L"alt-b";
1150 break;
1151 case 'C':
1152 ptr = L"alt-c";
1153 break;
1154 case 'D':
1155 ptr = L"alt-d";
1156 break;
1157 case 'E':
1158 ptr = L"alt-e";
1159 break;
1160 case 'F':
1161 ptr = L"alt-f";
1162 break;
1163 case 'G':
1164 ptr = L"alt-g";
1165 break;
1166 case 'H':
1167 ptr = L"alt-h";
1168 break;
1169 case 'I':
1170 ptr = L"alt-i";
1171 break;
1172 case 'J':
1173 ptr = L"alt-j";
1174 break;
1175 case 'K':
1176 ptr = L"alt-k";
1177 break;
1178 case 'L':
1179 ptr = L"alt-l";
1180 break;
1181 case 'M':
1182 ptr = L"alt-m";
1183 break;
1184 case 'N':
1185 ptr = L"alt-n";
1186 break;
1187 case 'O':
1188 ptr = L"alt-o";
1189 break;
1190 case 'P':
1191 ptr = L"alt-p";
1192 break;
1193 case 'Q':
1194 ptr = L"alt-q";
1195 break;
1196 case 'R':
1197 ptr = L"alt-r";
1198 break;
1199 case 'S':
1200 ptr = L"alt-s";
1201 break;
1202 case 'T':
1203 ptr = L"alt-t";
1204 break;
1205 case 'U':
1206 ptr = L"alt-u";
1207 break;
1208 case 'V':
1209 ptr = L"alt-v";
1210 break;
1211 case 'W':
1212 ptr = L"alt-w";
1213 break;
1214 case 'X':
1215 ptr = L"alt-x";
1216 break;
1217 case 'Y':
1218 ptr = L"alt-y";
1219 break;
1220 case 'Z':
1221 ptr = L"alt-z";
1222 break;
1226 else {
1227 switch (event->keyval) {
1228 case GDK_Up:
1229 ptr = L"cursor-up";
1230 break;
1231 case GDK_Down:
1232 ptr = L"cursor-down";
1233 break;
1234 case GDK_Left:
1235 ptr = L"cursor-left";
1236 break;
1237 case GDK_Right:
1238 ptr = L"cursor-right";
1239 break;
1240 case GDK_Prior:
1241 ptr = L"page-up";
1242 break;
1243 case GDK_Next:
1244 ptr = L"page-down";
1245 break;
1246 case GDK_Home:
1247 ptr = L"home";
1248 break;
1249 case GDK_End:
1250 ptr = L"end";
1251 break;
1252 case GDK_space:
1253 ptr = L"space";
1254 break;
1255 case GDK_KP_Add:
1256 ptr = L"kp-plus";
1257 break;
1258 case GDK_KP_Subtract:
1259 ptr = L"kp-minus";
1260 break;
1261 case GDK_KP_Multiply:
1262 ptr = L"kp-multiply";
1263 break;
1264 case GDK_KP_Divide:
1265 ptr = L"kp-divide";
1266 break;
1267 case GDK_F1:
1268 ptr = L"f1";
1269 break;
1270 case GDK_F2:
1271 ptr = L"f2";
1272 break;
1273 case GDK_F3:
1274 ptr = L"f3";
1275 break;
1276 case GDK_F4:
1277 ptr = L"f4";
1278 break;
1279 case GDK_F5:
1280 ptr = L"f5";
1281 break;
1282 case GDK_F6:
1283 ptr = L"f6";
1284 break;
1285 case GDK_F7:
1286 ptr = L"f7";
1287 break;
1288 case GDK_F8:
1289 ptr = L"f8";
1290 break;
1291 case GDK_F9:
1292 ptr = L"f9";
1293 break;
1294 case GDK_F10:
1295 ptr = L"f10";
1296 break;
1297 case GDK_F11:
1298 ptr = L"f11";
1299 break;
1300 case GDK_F12:
1301 ptr = L"f12";
1302 break;
1303 case GDK_Insert:
1304 ptr = L"insert";
1305 break;
1306 case GDK_BackSpace:
1307 ptr = L"backspace";
1308 break;
1309 case GDK_Delete:
1310 ptr = L"delete";
1311 break;
1312 case GDK_KP_Enter:
1313 case GDK_Return:
1314 ptr = L"enter";
1315 break;
1316 case GDK_Tab:
1317 ptr = L"tab";
1318 break;
1319 case GDK_ISO_Left_Tab:
1320 ptr = L"shift-tab";
1321 break;
1322 case GDK_Escape:
1323 ptr = L"escape";
1324 break;
1328 /* if there is a pending char in im_char, use it */
1329 if (ptr == NULL && im_char[0] != L'\0')
1330 ptr = im_char;
1332 /* finally process */
1333 if (ptr != NULL)
1334 mp_process_event(MPDM_S(ptr));
1336 /* delete the pending char */
1337 im_char[0] = L'\0';
1339 if (mp_exit_requested)
1340 gtk_main_quit();
1342 if (mp_keypress_throttle(1))
1343 gtk_drv_paint(mp_active(), 1);
1345 return 0;
1349 static gint button_press_event(GtkWidget * widget, GdkEventButton * event,
1350 gpointer data)
1351 /* 'button_press_event' handler (mouse buttons) */
1353 int x, y;
1354 wchar_t *ptr = NULL;
1356 mouse_down = 1;
1358 /* mouse instant positioning */
1359 x = ((int) event->x) / font_width;
1360 y = ((int) event->y) / font_height;
1362 mpdm_hset_s(mp, L"mouse_x", MPDM_I(x));
1363 mpdm_hset_s(mp, L"mouse_y", MPDM_I(y));
1365 switch (event->button) {
1366 case 1:
1367 ptr = L"mouse-left-button";
1368 break;
1369 case 2:
1370 ptr = L"mouse-middle-button";
1371 break;
1372 case 3:
1373 ptr = L"mouse-right-button";
1374 break;
1375 case 4:
1376 ptr = L"mouse-wheel-up";
1377 break;
1378 case 5:
1379 ptr = L"mouse-wheel-down";
1380 break;
1383 if (ptr != NULL)
1384 mp_process_event(MPDM_S(ptr));
1386 redraw();
1388 return 0;
1392 static gint button_release_event(GtkWidget * widget,
1393 GdkEventButton * event, gpointer data)
1394 /* 'button_release_event' handle (mouse buttons) */
1396 mouse_down = 0;
1398 return TRUE;
1402 static gint motion_notify_event(GtkWidget * widget, GdkEventMotion * event,
1403 gpointer data)
1404 /* 'motion_notify_event' handler (mouse movement) */
1406 static int ox = 0;
1407 static int oy = 0;
1409 if (mouse_down) {
1410 int x, y;
1412 /* mouse dragging */
1413 x = ((int) event->x) / font_width;
1414 y = ((int) event->y) / font_height;
1416 if (ox != x && oy != y) {
1417 mpdm_hset_s(mp, L"mouse_to_x", MPDM_I(x));
1418 mpdm_hset_s(mp, L"mouse_to_y", MPDM_I(y));
1420 mp_process_event(MPDM_LS(L"mouse-drag"));
1421 gtk_drv_paint(mp_active(), 1);
1425 return TRUE;
1429 static void drag_data_received(GtkWidget * widget, GdkDragContext * dc,
1430 gint x, gint y, GtkSelectionData * data,
1431 guint info, guint time)
1432 /* 'drag_data_received' handler */
1434 printf("drag_data_received (unsupported)\n");
1438 /** clipboard functions **/
1440 static void commit(GtkIMContext * i, char *str, gpointer u)
1441 /* 'commit' handler */
1443 wchar_t *wstr;
1445 wstr = (wchar_t *) g_convert(str, -1,
1446 "WCHAR_T", "UTF-8", NULL, NULL, NULL);
1448 im_char[0] = *wstr;
1449 im_char[1] = L'\0';
1451 g_free(wstr);
1455 static void realize(GtkWidget * widget)
1456 /* 'realize' handler */
1458 im = gtk_im_multicontext_new();
1459 g_signal_connect(im, "commit", G_CALLBACK(commit), NULL);
1460 gtk_im_context_set_client_window(im, gtk_widget_get_window(widget));
1464 static gint expose_event(GtkWidget * widget, GdkEventExpose * event)
1465 /* 'expose_event' handler */
1467 redraw();
1469 return FALSE;
1473 static gint configure_event(GtkWidget * widget, GdkEventConfigure * event)
1474 /* 'configure_event' handler */
1476 static GdkEventConfigure o;
1478 if (memcmp(&o, event, sizeof(o)) == 0)
1479 return TRUE;
1481 memcpy(&o, event, sizeof(o));
1483 update_window_size();
1484 redraw();
1486 return TRUE;
1490 static gint selection_clear_event(GtkWidget * widget,
1491 GdkEventSelection * event, gpointer data)
1492 /* 'selection_clear_event' handler */
1494 got_selection = 0;
1496 return TRUE;
1500 static void selection_get(GtkWidget * widget,
1501 GtkSelectionData * sel, guint info, guint tm)
1502 /* 'selection_get' handler */
1504 mpdm_t d;
1505 unsigned char *ptr;
1506 int s;
1508 if (!got_selection)
1509 return;
1511 /* gets the clipboard and joins */
1512 d = mpdm_hget_s(mp, L"clipboard");
1514 if (mpdm_size(d) == 0)
1515 return;
1517 d = mpdm_ref(mpdm_join_s(d, L"\n"));
1519 /* convert to current locale */
1520 ptr = (unsigned char *) mpdm_wcstombs(d->data, &s);
1522 /* pastes into primary selection */
1523 gtk_selection_data_set(sel, GDK_SELECTION_TYPE_STRING, 8, ptr,
1524 (gsize) s);
1526 free(ptr);
1528 mpdm_unref(d);
1532 static void selection_received(GtkWidget * widget,
1533 GtkSelectionData * sel, gpointer data)
1534 /* 'selection_received' handler */
1536 mpdm_t d;
1538 if (gtk_selection_data_get_data(sel) != NULL) {
1539 /* get selection */
1540 wchar_t *wptr = utf8_to_wcs((char *) gtk_selection_data_get_data(sel));
1541 d = MPDM_S(wptr);
1542 g_free(wptr);
1544 /* split and set as the clipboard */
1545 mpdm_hset_s(mp, L"clipboard", mpdm_split_s(d, L"\n"));
1546 mpdm_hset_s(mp, L"clipboard_vertical", MPDM_I(0));
1548 /* wait no more for the selection */
1549 wait_for_selection = 0;
1551 else
1552 wait_for_selection = -1;
1556 static mpdm_t gtk_drv_clip_to_sys(mpdm_t a, mpdm_t ctxt)
1557 /* driver-dependent mp to system clipboard */
1559 got_selection = gtk_selection_owner_set(area,
1560 GDK_SELECTION_PRIMARY,
1561 GDK_CURRENT_TIME);
1563 return NULL;
1567 static mpdm_t gtk_drv_sys_to_clip(mpdm_t a, mpdm_t ctxt)
1568 /* driver-dependent system to mp clipboard */
1570 if (!got_selection) {
1571 int n;
1572 char *formats[] = { "UTF8_STRING", "STRING", NULL };
1574 for (n = 0; formats[n] != NULL; n++) {
1576 /* triggers a selection capture */
1577 if (gtk_selection_convert(area, GDK_SELECTION_PRIMARY,
1578 gdk_atom_intern(formats[n], FALSE),
1579 GDK_CURRENT_TIME)) {
1581 /* processes the pending events
1582 (i.e., the 'selection_received' handler) */
1583 wait_for_selection = 1;
1585 while (wait_for_selection == 1)
1586 gtk_main_iteration();
1588 if (!wait_for_selection)
1589 break;
1594 return NULL;
1598 /** interface functions **/
1600 static void clicked_ok(GtkWidget * widget, gpointer data)
1601 /* 'clicked_on' signal handler (for gtk_drv_form) */
1603 int n;
1605 for (n = 0; n < mpdm_size(form_args); n++) {
1606 GtkWidget *widget = form_widgets[n];
1607 mpdm_t w = mpdm_aget(form_args, n);
1608 wchar_t *wptr = mpdm_string(mpdm_hget_s(w, L"type"));
1609 mpdm_t v = NULL;
1611 /* if there is already a value there, if was
1612 previously set from a callback */
1613 if (mpdm_aget(form_values, n) != NULL)
1614 continue;
1616 if (wcscmp(wptr, L"text") == 0 || wcscmp(wptr, L"password") == 0) {
1617 char *ptr;
1618 GtkWidget *gw = widget;
1619 mpdm_t h;
1621 if (wcscmp(wptr, L"text") == 0)
1622 gw = GTK_COMBO(widget)->entry;
1624 if ((ptr =
1625 gtk_editable_get_chars(GTK_EDITABLE(gw), 0, -1)) != NULL
1626 && (wptr = utf8_to_wcs(ptr)) != NULL) {
1627 v = MPDM_S(wptr);
1628 g_free(wptr);
1629 g_free(ptr);
1632 mpdm_ref(v);
1634 /* if it has history, fill it */
1635 if ((h = mpdm_hget_s(w, L"history")) != NULL &&
1636 v != NULL && mpdm_cmp_s(v, L"") != 0) {
1637 h = mp_get_history(h);
1639 if (mpdm_cmp(v, mpdm_aget(h, -1)) != 0)
1640 mpdm_push(h, v);
1643 mpdm_unrefnd(v);
1645 else
1646 if (wcscmp(wptr, L"checkbox") == 0) {
1647 v = MPDM_I(gtk_toggle_button_get_active
1648 (GTK_TOGGLE_BUTTON(widget)));
1650 else
1651 if (wcscmp(wptr, L"list") == 0) {
1652 GtkWidget *list = gtk_bin_get_child(GTK_BIN(widget));
1653 GtkTreeSelection *selection =
1654 gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
1655 GList *selected =
1656 gtk_tree_selection_get_selected_rows(selection, NULL);
1657 GtkTreePath *path = selected->data;
1659 v = MPDM_I(gtk_tree_path_get_indices(path)[0]);
1660 gtk_tree_path_free(path);
1661 g_list_free(selected);
1665 mpdm_aset(form_values, v, n);
1670 static gint timer_callback(gpointer data)
1672 mpdm_void(mpdm_exec(timer_func, NULL, NULL));
1673 redraw();
1675 return TRUE;
1679 static void build_form_data(mpdm_t widget_list)
1680 /* builds the necessary information for a list of widgets */
1682 mpdm_unref(form_args);
1683 form_args = mpdm_ref(widget_list);
1685 mpdm_unref(form_values);
1686 form_values = widget_list == NULL ? NULL :
1687 mpdm_ref(MPDM_A(mpdm_size(form_args)));
1689 /* resize the widget array */
1690 form_widgets = (GtkWidget **) realloc(form_widgets,
1691 mpdm_size(form_args) *
1692 sizeof(GtkWidget *));
1696 /** dialog functions **/
1698 #define DIALOG_BUTTON(l,f) do { GtkWidget * btn; \
1699 ptr = localize(l); btn = gtk_button_new_with_label(ptr); \
1700 g_signal_connect_swapped(G_OBJECT(btn), "clicked", \
1701 G_CALLBACK(f), G_OBJECT(dlg)); \
1702 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), \
1703 btn, TRUE, TRUE, 0); \
1704 g_free(ptr); \
1705 } while (0);
1707 static mpdm_t gtk_drv_alert(mpdm_t a, mpdm_t ctxt)
1708 /* alert driver function */
1710 gchar *ptr;
1711 GtkWidget *dlg;
1713 build_form_data(NULL);
1715 /* 1# arg: prompt */
1716 if ((ptr = v_to_utf8(mpdm_aget(a, 0))) == NULL)
1717 return NULL;
1719 dlg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
1720 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", ptr);
1721 gtk_window_set_title(GTK_WINDOW(dlg), "mp " VERSION);
1722 g_free(ptr);
1724 gtk_dialog_run(GTK_DIALOG(dlg));
1725 gtk_widget_destroy(dlg);
1727 return NULL;
1731 static mpdm_t gtk_drv_confirm(mpdm_t a, mpdm_t ctxt)
1732 /* confirm driver function */
1734 char *ptr;
1735 GtkWidget *dlg;
1736 gint response;
1738 build_form_data(NULL);
1740 /* 1# arg: prompt */
1741 if ((ptr = v_to_utf8(mpdm_aget(a, 0))) == NULL)
1742 return NULL;
1744 dlg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
1745 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1746 "%s", ptr);
1747 gtk_window_set_title(GTK_WINDOW(dlg), "mp " VERSION);
1748 g_free(ptr);
1750 gtk_dialog_add_button(GTK_DIALOG(dlg), GTK_STOCK_YES, 1);
1751 gtk_dialog_add_button(GTK_DIALOG(dlg), GTK_STOCK_NO, 2);
1752 gtk_dialog_add_button(GTK_DIALOG(dlg), GTK_STOCK_CANCEL, 0);
1754 response = gtk_dialog_run(GTK_DIALOG(dlg));
1755 gtk_widget_destroy(dlg);
1757 if (response == GTK_RESPONSE_DELETE_EVENT)
1758 response = 0;
1760 return MPDM_I(response);
1764 static mpdm_t gtk_drv_form(mpdm_t a, mpdm_t ctxt)
1765 /* 'form' driver function */
1767 GtkWidget *dlg;
1768 GtkWidget *table;
1769 GtkWidget *content_area;
1770 int n;
1771 mpdm_t ret = NULL;
1773 /* first argument: list of widgets */
1774 build_form_data(mpdm_aget(a, 0));
1776 dlg = gtk_dialog_new_with_buttons("mp " VERSION, GTK_WINDOW(window),
1777 GTK_DIALOG_MODAL |
1778 GTK_DIALOG_NO_SEPARATOR,
1779 GTK_STOCK_CANCEL,
1780 GTK_RESPONSE_CANCEL, GTK_STOCK_OK,
1781 GTK_RESPONSE_OK, NULL);
1782 gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
1783 gtk_container_set_border_width(GTK_CONTAINER(dlg), 5);
1785 content_area = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
1786 gtk_box_set_spacing(GTK_BOX(content_area), 2);
1788 table = gtk_table_new(mpdm_size(a), 2, FALSE);
1789 gtk_container_set_border_width(GTK_CONTAINER(table), 5);
1790 gtk_table_set_col_spacings(GTK_TABLE(table), 12);
1791 gtk_table_set_row_spacings(GTK_TABLE(table), 6);
1793 for (n = 0; n < mpdm_size(form_args); n++) {
1794 mpdm_t w = mpdm_aget(form_args, n);
1795 GtkWidget *widget = NULL;
1796 wchar_t *type;
1797 char *ptr;
1798 mpdm_t t;
1799 int col = 0;
1801 type = mpdm_string(mpdm_hget_s(w, L"type"));
1803 if ((t = mpdm_hget_s(w, L"label")) != NULL) {
1804 GtkWidget *label;
1806 if ((ptr = v_to_utf8(mpdm_gettext(t))) != NULL) {
1807 label = gtk_label_new(ptr);
1808 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
1810 gtk_table_attach_defaults(GTK_TABLE(table),
1811 label, 0, wcscmp(type,
1812 L"label") ==
1813 0 ? 2 : 1, n, n + 1);
1815 g_free(ptr);
1817 col++;
1821 t = mpdm_hget_s(w, L"value");
1823 if (wcscmp(type, L"text") == 0) {
1824 GList *combo_items = NULL;
1825 mpdm_t h;
1827 widget = gtk_combo_new();
1828 gtk_widget_set_size_request(widget, 300, -1);
1829 gtk_combo_set_use_arrows_always(GTK_COMBO(widget), TRUE);
1830 gtk_combo_set_case_sensitive(GTK_COMBO(widget), TRUE);
1831 gtk_entry_set_activates_default(GTK_ENTRY
1832 (GTK_COMBO(widget)->entry),
1833 TRUE);
1835 if ((h = mpdm_hget_s(w, L"history")) != NULL) {
1836 int i;
1838 /* has history; fill it */
1839 h = mp_get_history(h);
1841 for (i = 0; i < mpdm_size(h); i++) {
1842 ptr = v_to_utf8(mpdm_aget(h, i));
1844 combo_items = g_list_prepend(combo_items, ptr);
1848 if (t != NULL) {
1849 ptr = v_to_utf8(t);
1851 combo_items = g_list_prepend(combo_items, ptr);
1854 gtk_combo_set_popdown_strings(GTK_COMBO(widget), combo_items);
1855 g_list_free(combo_items);
1857 else
1858 if (wcscmp(type, L"password") == 0) {
1859 widget = gtk_entry_new();
1860 gtk_widget_set_size_request(widget, 300, -1);
1861 gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
1863 else
1864 if (wcscmp(type, L"checkbox") == 0) {
1865 widget = gtk_check_button_new();
1867 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
1868 mpdm_ival(t) ? TRUE : FALSE);
1870 else
1871 if (wcscmp(type, L"list") == 0) {
1872 GtkWidget *list;
1873 GtkListStore *list_store;
1874 GtkCellRenderer *renderer;
1875 GtkTreeViewColumn *column;
1876 GtkTreePath *path;
1877 mpdm_t l;
1878 gint i;
1880 if ((i = 450 / mpdm_size(form_args)) < 100)
1881 i = 100;
1883 widget = gtk_scrolled_window_new(NULL, NULL);
1884 gtk_widget_set_size_request(widget, 500, i);
1885 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
1886 GTK_POLICY_NEVER,
1887 GTK_POLICY_AUTOMATIC);
1888 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
1889 (widget), GTK_SHADOW_IN);
1891 list_store = gtk_list_store_new(1, G_TYPE_STRING);
1892 list =
1893 gtk_tree_view_new_with_model(GTK_TREE_MODEL(list_store));
1894 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
1895 renderer = gtk_cell_renderer_text_new();
1896 column = gtk_tree_view_column_new_with_attributes("", renderer,
1897 "text", 0,
1898 NULL);
1899 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1900 gtk_container_add(GTK_CONTAINER(widget), list);
1902 l = mpdm_hget_s(w, L"list");
1904 for (i = 0; i < mpdm_size(l); i++) {
1905 if ((ptr = v_to_utf8(mpdm_aget(l, i))) != NULL) {
1906 GtkTreeIter iter;
1907 gtk_list_store_append(list_store, &iter);
1908 gtk_list_store_set(list_store, &iter, 0, ptr, -1);
1909 g_free(ptr);
1913 /* initial position */
1914 i = mpdm_ival(t);
1916 path = gtk_tree_path_new_from_indices(i, -1);
1917 gtk_tree_view_set_cursor(GTK_TREE_VIEW(list), path, NULL,
1918 FALSE);
1919 gtk_tree_path_free(path);
1921 g_signal_connect_swapped(G_OBJECT(list), "row-activated",
1922 G_CALLBACK
1923 (gtk_window_activate_default), dlg);
1926 if (widget != NULL) {
1927 form_widgets[n] = widget;
1928 gtk_table_attach_defaults(GTK_TABLE(table),
1929 widget, col, 2, n, n + 1);
1933 gtk_widget_show_all(table);
1935 gtk_box_pack_start(GTK_BOX(content_area), table, TRUE, TRUE, 0);
1937 if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK) {
1938 clicked_ok(NULL, NULL);
1939 ret = form_values;
1941 gtk_widget_destroy(dlg);
1943 return ret;
1947 static mpdm_t run_filechooser(mpdm_t a, gboolean save)
1948 /* openfile driver function */
1950 GtkWidget *dlg;
1951 char *ptr;
1952 mpdm_t ret = NULL;
1953 gint response;
1955 /* 1# arg: prompt */
1956 if ((ptr = v_to_utf8(mpdm_aget(a, 0))) == NULL)
1957 return (NULL);
1959 if (!save) {
1960 dlg = gtk_file_chooser_dialog_new(ptr, GTK_WINDOW(window),
1961 GTK_FILE_CHOOSER_ACTION_OPEN,
1962 GTK_STOCK_CANCEL,
1963 GTK_RESPONSE_CANCEL,
1964 GTK_STOCK_OK, GTK_RESPONSE_OK,
1965 NULL);
1967 else {
1968 dlg = gtk_file_chooser_dialog_new(ptr, GTK_WINDOW(window),
1969 GTK_FILE_CHOOSER_ACTION_SAVE,
1970 GTK_STOCK_CANCEL,
1971 GTK_STOCK_CANCEL, GTK_STOCK_OK,
1972 GTK_RESPONSE_OK, NULL);
1973 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER
1974 (dlg), TRUE);
1976 g_free(ptr);
1978 build_form_data(NULL);
1980 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), TRUE);
1981 response = gtk_dialog_run(GTK_DIALOG(dlg));
1983 if (response == GTK_RESPONSE_OK) {
1984 gchar *filename;
1985 gchar *utf8name;
1986 wchar_t *wfilename;
1988 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
1989 utf8name = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
1990 g_free(filename);
1991 wfilename = utf8_to_wcs(utf8name);
1992 g_free(utf8name);
1993 ret = MPDM_S(wfilename);
1994 g_free(wfilename);
1996 gtk_widget_destroy(dlg);
1998 return ret;
2002 static mpdm_t gtk_drv_openfile(mpdm_t a, mpdm_t ctxt)
2003 /* openfile driver function */
2005 return run_filechooser(a, FALSE);
2009 static mpdm_t gtk_drv_savefile(mpdm_t a, mpdm_t ctxt)
2010 /* savefile driver function */
2012 return run_filechooser(a, TRUE);
2016 static mpdm_t gtk_drv_update_ui(mpdm_t a, mpdm_t ctxt)
2018 build_fonts();
2019 build_colors();
2020 build_menu();
2022 redraw();
2024 return NULL;
2028 static mpdm_t gtk_drv_timer(mpdm_t a, mpdm_t ctxt)
2030 static guint prev = 0;
2031 int msecs = mpdm_ival(mpdm_aget(a, 0));
2032 mpdm_t func = mpdm_aget(a, 1);
2034 /* previously defined one? remove */
2035 if (timer_func != NULL)
2036 g_source_remove(prev);
2038 /* if msecs and func are set, program timer */
2039 if (msecs > 0 && func != NULL)
2040 prev = g_timeout_add(msecs, timer_callback, NULL);
2042 mpdm_ref(func);
2043 mpdm_unref(timer_func);
2044 timer_func = func;
2046 return NULL;
2050 static mpdm_t gtk_drv_busy(mpdm_t a, mpdm_t ctxt)
2052 int onoff = mpdm_ival(mpdm_aget(a, 0));
2054 gdk_window_set_cursor(gtk_widget_get_window(window),
2055 gdk_cursor_new(onoff ? GDK_WATCH :
2056 GDK_LEFT_PTR));
2058 while (gtk_events_pending())
2059 gtk_main_iteration();
2061 return NULL;
2065 static mpdm_t gtk_drv_main_loop(mpdm_t a, mpdm_t ctxt)
2066 /* main loop */
2068 if (!mp_exit_requested) {
2069 gtk_drv_paint(mp_active(), 0);
2071 gtk_main();
2074 return NULL;
2078 static mpdm_t gtk_drv_shutdown(mpdm_t a, mpdm_t ctxt)
2079 /* shutdown */
2081 mpdm_t v;
2083 if ((v = mpdm_hget_s(mp, L"exit_message")) != NULL) {
2084 mpdm_write_wcs(stdout, mpdm_string(v));
2085 printf("\n");
2088 return NULL;
2092 static void register_functions(void)
2094 mpdm_t drv;
2096 drv = mpdm_hget_s(mp, L"drv");
2097 mpdm_hset_s(drv, L"main_loop", MPDM_X(gtk_drv_main_loop));
2098 mpdm_hset_s(drv, L"shutdown", MPDM_X(gtk_drv_shutdown));
2099 mpdm_hset_s(drv, L"clip_to_sys", MPDM_X(gtk_drv_clip_to_sys));
2100 mpdm_hset_s(drv, L"sys_to_clip", MPDM_X(gtk_drv_sys_to_clip));
2101 mpdm_hset_s(drv, L"update_ui", MPDM_X(gtk_drv_update_ui));
2102 mpdm_hset_s(drv, L"timer", MPDM_X(gtk_drv_timer));
2103 mpdm_hset_s(drv, L"busy", MPDM_X(gtk_drv_busy));
2104 mpdm_hset_s(drv, L"alert", MPDM_X(gtk_drv_alert));
2105 mpdm_hset_s(drv, L"confirm", MPDM_X(gtk_drv_confirm));
2106 mpdm_hset_s(drv, L"openfile", MPDM_X(gtk_drv_openfile));
2107 mpdm_hset_s(drv, L"savefile", MPDM_X(gtk_drv_savefile));
2108 mpdm_hset_s(drv, L"form", MPDM_X(gtk_drv_form));
2112 static mpdm_t gtk_drv_startup(mpdm_t a, mpdm_t ctxt)
2113 /* driver initialization */
2115 GtkWidget *vbox;
2116 GtkWidget *hbox;
2117 GdkPixmap *pixmap;
2118 GdkPixmap *mask;
2119 GdkScreen *screen;
2120 mpdm_t v;
2121 int w, h;
2122 GtkTargetEntry targets[] = {
2123 {"text/plain", 0, 0},
2124 {"text/uri-list", 0, 1}
2127 register_functions();
2129 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2131 gtk_window_set_title(GTK_WINDOW(window), "mp " VERSION);
2133 /* get real screen and pick a usable size for the main area */
2134 screen = gtk_window_get_screen(GTK_WINDOW(window));
2135 if (gdk_screen_get_n_monitors(screen) > 1) {
2136 GdkRectangle monitor_one_size;
2137 gdk_screen_get_monitor_geometry(screen, 0, &monitor_one_size);
2139 w = (monitor_one_size.width * 3) / 4;
2140 h = (monitor_one_size.height * 2) / 3;
2141 } else {
2142 w = (gdk_screen_get_width(screen) * 3) / 4;
2143 h = (gdk_screen_get_height(screen) * 2) / 3;
2146 g_signal_connect(G_OBJECT(window), "delete_event",
2147 G_CALLBACK(delete_event), NULL);
2149 g_signal_connect(G_OBJECT(window), "destroy",
2150 G_CALLBACK(destroy), NULL);
2152 /* file tabs */
2153 file_tabs = gtk_notebook_new();
2154 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(file_tabs), GTK_POS_TOP);
2155 GTK_WIDGET_UNSET_FLAGS(file_tabs, GTK_CAN_FOCUS);
2156 gtk_notebook_set_scrollable(GTK_NOTEBOOK(file_tabs), 1);
2158 vbox = gtk_vbox_new(FALSE, 2);
2159 gtk_container_add(GTK_CONTAINER(window), vbox);
2161 build_menu();
2163 hbox = gtk_hbox_new(FALSE, 0);
2164 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2165 gtk_box_pack_start(GTK_BOX(hbox), menu_bar, FALSE, FALSE, 0);
2166 gtk_box_pack_start(GTK_BOX(hbox), file_tabs, TRUE, TRUE, 0);
2168 gtk_notebook_popup_enable(GTK_NOTEBOOK(file_tabs));
2170 /* horizontal box holding the text and the scrollbar */
2171 hbox = gtk_hbox_new(FALSE, 2);
2172 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
2174 /* the Minimum Profit area */
2175 area = gtk_drawing_area_new();
2176 gtk_box_pack_start(GTK_BOX(hbox), area, TRUE, TRUE, 0);
2177 gtk_widget_set_size_request(GTK_WIDGET(area), w, h);
2178 gtk_widget_set_events(GTK_WIDGET(area), GDK_BUTTON_PRESS_MASK |
2179 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
2180 | GDK_LEAVE_NOTIFY_MASK);
2182 gtk_widget_set_double_buffered(area, FALSE);
2184 g_signal_connect(G_OBJECT(area), "configure_event",
2185 G_CALLBACK(configure_event), NULL);
2187 g_signal_connect(G_OBJECT(area), "expose_event",
2188 G_CALLBACK(expose_event), NULL);
2190 g_signal_connect(G_OBJECT(area), "realize", G_CALLBACK(realize), NULL);
2192 g_signal_connect(G_OBJECT(window), "key_press_event",
2193 G_CALLBACK(key_press_event), NULL);
2195 g_signal_connect(G_OBJECT(window), "key_release_event",
2196 G_CALLBACK(key_release_event), NULL);
2198 g_signal_connect(G_OBJECT(area), "button_press_event",
2199 G_CALLBACK(button_press_event), NULL);
2201 g_signal_connect(G_OBJECT(area), "button_release_event",
2202 G_CALLBACK(button_release_event), NULL);
2204 g_signal_connect(G_OBJECT(area), "motion_notify_event",
2205 G_CALLBACK(motion_notify_event), NULL);
2207 g_signal_connect(G_OBJECT(area), "selection_clear_event",
2208 G_CALLBACK(selection_clear_event), NULL);
2210 g_signal_connect(G_OBJECT(area), "selection_get",
2211 G_CALLBACK(selection_get), NULL);
2213 g_signal_connect(G_OBJECT(area), "selection_received",
2214 G_CALLBACK(selection_received), NULL);
2216 g_signal_connect(G_OBJECT(area), "scroll_event",
2217 G_CALLBACK(scroll_event), NULL);
2219 gtk_drag_dest_set(area, GTK_DEST_DEFAULT_ALL, targets,
2220 sizeof(targets) / sizeof(GtkTargetEntry),
2221 GDK_ACTION_COPY);
2222 g_signal_connect(G_OBJECT(area), "drag_data_received",
2223 G_CALLBACK(drag_data_received), NULL);
2225 gtk_selection_add_target(area, GDK_SELECTION_PRIMARY,
2226 GDK_SELECTION_TYPE_STRING, 1);
2228 g_signal_connect(G_OBJECT(file_tabs), "switch_page",
2229 G_CALLBACK(switch_page), NULL);
2231 /* the scrollbar */
2232 scrollbar = gtk_vscrollbar_new(NULL);
2233 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, FALSE, 0);
2235 g_signal_connect(G_OBJECT
2236 (gtk_range_get_adjustment(GTK_RANGE(scrollbar))),
2237 "value_changed", G_CALLBACK(value_changed), NULL);
2239 /* the status bar */
2240 status = gtk_label_new("mp " VERSION);
2241 gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0);
2242 gtk_misc_set_alignment(GTK_MISC(status), 0, 0.5);
2243 gtk_label_set_justify(GTK_LABEL(status), GTK_JUSTIFY_LEFT);
2245 gtk_widget_show_all(window);
2247 /* set application icon */
2248 pixmap = gdk_pixmap_create_from_xpm_d(window->window,
2249 &mask, NULL, mp_xpm);
2250 gdk_window_set_icon(window->window, NULL, pixmap, mask);
2252 build_colors();
2254 if ((v = mpdm_hget_s(mp, L"config")) != NULL &&
2255 mpdm_ival(mpdm_hget_s(v, L"maximize")) > 0)
2256 maximize = 1;
2258 return NULL;
2262 int gtk_drv_detect(int *argc, char ***argv)
2264 mpdm_t drv;
2265 int n;
2267 for (n = 0; n < *argc; n++) {
2268 if (strcmp(argv[0][n], "-txt") == 0 ||
2269 strcmp(argv[0][n], "-h") == 0)
2270 return 0;
2273 if (!gtk_init_check(argc, argv))
2274 return 0;
2276 drv = mpdm_hget_s(mp, L"drv");
2277 mpdm_hset_s(drv, L"id", MPDM_LS(L"gtk"));
2278 mpdm_hset_s(drv, L"startup", MPDM_X(gtk_drv_startup));
2280 return 1;
2283 #endif /* CONFOPT_GTK */