]> gitweb.factorcode.org Git - factor.git/blob - basis/ui/backend/gtk/gtk.factor
ui.backend.gtk: use png instead of ico to represent icons
[factor.git] / basis / ui / backend / gtk / gtk.factor
1 ! Copyright (C) 2010 Anton Gorenko, Philipp Brüschweiler.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors alien.accessors alien.c-types alien.data
4 alien.strings alien.syntax arrays assocs classes.struct
5 command-line destructors gdk.ffi gdk.gl.ffi glib.ffi
6 gobject.ffi gtk.ffi gtk.gl.ffi io.backend
7 io.backend.unix.multiplexers io.encodings.utf8 io.thread kernel
8 libc literals locals math math.bitwise math.order math.vectors
9 namespaces sequences strings system threads ui ui.backend
10 ui.clipboards ui.commands ui.event-loop ui.gadgets
11 ui.gadgets.menus ui.gadgets.private ui.gadgets.worlds
12 ui.gestures ui.pixel-formats ui.pixel-formats.private
13 ui.private ;
14 RENAME: windows ui.private => ui:windows
15 EXCLUDE: ui.gadgets.editors => change-caret ;
16 RENAME: change-caret ui.gadgets.editors => editors:change-caret
17 IN: ui.backend.gtk
18
19 SINGLETON: gtk-ui-backend
20
21 TUPLE: handle ;
22 TUPLE: window-handle < handle window fullscreen? im-context im-menu ;
23
24 : <window-handle> ( window im-context -- window-handle )
25     window-handle new
26         swap >>im-context
27         swap >>window ;
28
29 TUPLE: gtk-clipboard handle ;
30
31 C: <gtk-clipboard> gtk-clipboard
32
33 PIXEL-FORMAT-ATTRIBUTE-TABLE: gl-config-attribs ${ GDK_GL_USE_GL GDK_GL_RGBA } H{
34     { double-buffered ${ GDK_GL_DOUBLEBUFFER } }
35     { stereo ${ GDK_GL_STEREO } }
36     ! { offscreen ${ GDK_GL_DRAWABLE_TYPE 2 } }
37     ! { fullscreen ${ GDK_GL_DRAWABLE_TYPE 1 } }
38     ! { windowed ${ GDK_GL_DRAWABLE_TYPE 1 } }
39     { color-bits ${ GDK_GL_BUFFER_SIZE } }
40     { red-bits ${ GDK_GL_RED_SIZE } }
41     { green-bits ${ GDK_GL_GREEN_SIZE } }
42     { blue-bits ${ GDK_GL_BLUE_SIZE } }
43     { alpha-bits ${ GDK_GL_ALPHA_SIZE } }
44     { accum-red-bits ${ GDK_GL_ACCUM_RED_SIZE } }
45     { accum-green-bits ${ GDK_GL_ACCUM_GREEN_SIZE } }
46     { accum-blue-bits ${ GDK_GL_ACCUM_BLUE_SIZE } }
47     { accum-alpha-bits ${ GDK_GL_ACCUM_ALPHA_SIZE } }
48     { depth-bits ${ GDK_GL_DEPTH_SIZE } }
49     { stencil-bits ${ GDK_GL_STENCIL_SIZE } }
50     { aux-buffers ${ GDK_GL_AUX_BUFFERS } }
51     { sample-buffers ${ GDK_GL_SAMPLE_BUFFERS } }
52     { samples ${ GDK_GL_SAMPLES } }
53 }
54
55 M: gtk-ui-backend (make-pixel-format)
56     nip >gl-config-attribs-int-array gdk_gl_config_new ;
57
58 M: gtk-ui-backend (free-pixel-format)
59     handle>> g_object_unref ;
60
61 M: gtk-ui-backend (pixel-format-attribute)
62     [ handle>> ] [ >gl-config-attribs ] bi*
63     { int } [ gdk_gl_config_get_attrib drop ]
64     with-out-parameters ;
65
66 CONSTANT: events-mask
67     flags{
68         GDK_POINTER_MOTION_MASK
69         GDK_POINTER_MOTION_HINT_MASK
70         GDK_ENTER_NOTIFY_MASK
71         GDK_LEAVE_NOTIFY_MASK
72         GDK_BUTTON_PRESS_MASK
73         GDK_BUTTON_RELEASE_MASK
74         GDK_KEY_PRESS_MASK
75         GDK_KEY_RELEASE_MASK
76         GDK_FOCUS_CHANGE_MASK
77     }
78
79 CONSTANT: modifiers
80     {
81         { S+ $ GDK_SHIFT_MASK }
82         { C+ $ GDK_CONTROL_MASK }
83         { A+ $ GDK_MOD1_MASK }
84     }
85
86 CONSTANT: action-key-codes
87     H{
88         { $ GDK_BackSpace "BACKSPACE" }
89         { $ GDK_Tab "TAB" }
90         { $ GDK_Return "RET" }
91         { $ GDK_KP_Enter "ENTER" }
92         { $ GDK_Escape "ESC" }
93         { $ GDK_Delete "DELETE" }
94         { $ GDK_Home "HOME" }
95         { $ GDK_Left "LEFT" }
96         { $ GDK_Up "UP" }
97         { $ GDK_Right "RIGHT" }
98         { $ GDK_Down "DOWN" }
99         { $ GDK_Page_Up "PAGE_UP" }
100         { $ GDK_Page_Down "PAGE_DOWN" }
101         { $ GDK_End "END" }
102         { $ GDK_Begin "BEGIN" }
103         { $ GDK_F1 "F1" }
104         { $ GDK_F2 "F2" }
105         { $ GDK_F3 "F3" }
106         { $ GDK_F4 "F4" }
107         { $ GDK_F5 "F5" }
108         { $ GDK_F6 "F6" }
109         { $ GDK_F7 "F7" }
110         { $ GDK_F8 "F8" }
111         { $ GDK_F9 "F9" }
112         { $ GDK_F10 "F10" }
113         { $ GDK_F11 "F11" }
114         { $ GDK_F12 "F12" }
115     }
116
117 : event-modifiers ( event -- seq )
118     state>> modifiers modifier ;
119
120 : event-loc ( event -- loc )
121     [ x>> ] [ y>> ] bi [ >fixnum ] bi@ 2array ;
122
123 : event-dim ( event -- dim )
124     [ width>> ] [ height>> ] bi 2array ;
125
126 : scroll-direction ( event -- pair )
127     direction>> {
128         { $ GDK_SCROLL_UP { 0 -1 } }
129         { $ GDK_SCROLL_DOWN { 0 1 } }
130         { $ GDK_SCROLL_LEFT { -1 0 } }
131         { $ GDK_SCROLL_RIGHT { 1 0 } }
132     } at ;
133
134 : mouse-event>gesture ( event -- modifiers button loc )
135     [ event-modifiers ] [ button>> ] [ event-loc ] tri ;
136
137 : on-motion ( sender event user-data -- result )
138     drop swap
139     [ GdkEventMotion memory>struct event-loc ] dip window
140     move-hand fire-motion t ;
141
142 : on-enter ( sender event user-data -- result )
143     on-motion ;
144
145 : on-leave ( sender event user-data -- result )
146     3drop forget-rollover t ;
147
148 : on-button-press ( sender event user-data -- result )
149     drop swap [
150         GdkEventButton memory>struct
151         mouse-event>gesture [ <button-down> ] dip
152     ] dip window send-button-down t ;
153
154 : on-button-release ( sender event user-data -- result )
155     drop swap [
156         GdkEventButton memory>struct
157         mouse-event>gesture [ <button-up> ] dip
158     ] dip window send-button-up t ;
159
160 : on-scroll ( sender event user-data -- result )
161     drop swap [
162         GdkEventScroll memory>struct
163         [ scroll-direction ] [ event-loc ] bi
164     ] dip window send-scroll t ;
165
166 : key-sym ( event -- sym/f action? )
167     keyval>> dup action-key-codes at [ t ]
168     [ gdk_keyval_to_unicode [ f ] [ 1string ] if-zero f ] ?if ;
169
170 : key-event>gesture ( event -- mods sym/f action? )
171     GdkEventKey memory>struct
172     [ event-modifiers ] [ key-sym ] bi ;
173   
174 : on-key-press ( sender event user-data -- result )
175     drop swap [ key-event>gesture <key-down> ] [ window ] bi*
176     propagate-key-gesture t ;
177
178 : on-key-release ( sender event user-data -- result )
179     drop swap [ key-event>gesture <key-up> ] [ window ] bi*
180     propagate-key-gesture t ;
181
182 : on-focus-in ( sender event user-data -- result )
183     2drop window focus-world t ;
184
185 : on-focus-out ( sender event user-data -- result )
186     2drop window unfocus-world t ;
187
188 : on-expose ( sender event user-data -- result )
189     2drop window relayout t ;
190
191 : on-configure ( sender event user-data -- result )
192     drop [ window ] dip GdkEventConfigure memory>struct
193     [ event-loc >>window-loc ] [ event-dim >>dim  ] bi
194     relayout-1 f ;
195
196 : on-delete ( sender event user-data -- result )
197     2drop window ungraft t ;
198
199 : init-clipboard ( -- )
200     selection "PRIMARY"
201     clipboard "CLIPBOARD"
202     [
203         utf8 string>alien gdk_atom_intern_static_string
204         gtk_clipboard_get <gtk-clipboard> swap set-global
205     ] 2bi@ ;
206
207 : io-source-prepare ( source timeout -- result )
208     2drop f ;
209
210 : io-source-check ( source -- result )
211     poll_fds>> 0 g_slist_nth_data GPollFD memory>struct
212     revents>> 0 = not ;
213
214 : io-source-dispatch ( source callback user_data -- result )
215      3drop
216      0 mx get wait-for-events
217      yield t ;
218
219 CONSTANT: poll-fd-events
220     flags{
221         G_IO_IN
222         G_IO_OUT
223         G_IO_PRI
224         G_IO_ERR
225         G_IO_HUP
226         G_IO_NVAL
227     }
228
229 : create-poll-fd ( -- poll-fd )
230     GPollFD malloc-struct &free
231         mx get fd>> >>fd
232         poll-fd-events >>events ;
233
234 : init-io-event-source ( -- )
235     GSourceFuncs malloc-struct &free
236         [ io-source-prepare ] GSourceFuncsPrepareFunc >>prepare
237         [ io-source-check ] GSourceFuncsCheckFunc >>check
238         [ io-source-dispatch ] GSourceFuncsDispatchFunc >>dispatch
239     GSource heap-size g_source_new &g_source_unref
240     [ create-poll-fd g_source_add_poll ]
241     [ f g_source_attach drop ] bi ;
242
243 SYMBOL: next-timeout
244
245 : set-timeout*-value ( alien value -- )
246     swap 0 set-alien-signed-4 ; inline
247
248 : timeout-prepare ( source timeout* -- result )
249     nip next-timeout get-global nano-count [-]
250     [ 1,000,000 /i set-timeout*-value ] keep 0 = ;
251
252 : timeout-check ( source -- result )
253     drop next-timeout get-global nano-count [-] 0 = ;
254
255 : timeout-dispatch ( source callback user_data -- result )
256     3drop sleep-time [ 1,000,000,000 ] unless* nano-count +
257     next-timeout set-global
258     yield t ;
259
260 : init-timeout ( -- )
261     GSourceFuncs malloc-struct &free
262         [ timeout-prepare ] GSourceFuncsPrepareFunc >>prepare
263         [ timeout-check ] GSourceFuncsCheckFunc >>check
264         [ timeout-dispatch ] GSourceFuncsDispatchFunc >>dispatch
265     GSource heap-size g_source_new &g_source_unref
266     f g_source_attach drop
267     nano-count next-timeout set-global ;
268
269 : load-icon ( -- )
270     ! This file is not in a resource.txt because it can be
271     ! overwritten when deploying. See 'Vocabulary icons'
272     ! in the docs.
273     "vocab:ui/backend/gtk/icon.png"
274     normalize-path utf8 string>alien
275     { { pointer: GError initial: f } }
276     [ gtk_window_set_default_icon_from_file ] with-out-parameters
277     handle-GError drop ;
278
279 M: gtk-ui-backend (with-ui)
280     [
281         f f gtk_init
282         f f gtk_gl_init
283         load-icon
284         init-clipboard
285         start-ui
286         stop-io-thread
287         [
288             init-io-event-source
289             init-timeout
290             gtk_main
291         ] with-destructors
292     ] ui-running ;
293
294 : connect-signal-with-data ( object signal-name callback data -- )
295     [ utf8 string>alien ] 2dip g_signal_connect drop ;
296
297 : connect-signal ( object signal-name callback -- )
298     f connect-signal-with-data ;
299
300 :: connect-signals ( win -- )
301     win events-mask gtk_widget_add_events
302     
303     win "expose-event" [ on-expose yield ]
304     GtkWidget:expose-event connect-signal
305     win "configure-event" [ on-configure yield ]
306     GtkWidget:configure-event connect-signal
307     win "motion-notify-event" [ on-motion yield ]
308     GtkWidget:motion-notify-event connect-signal
309     win "leave-notify-event" [ on-leave yield ]
310     GtkWidget:leave-notify-event connect-signal
311     win "enter-notify-event" [ on-enter yield ]
312     GtkWidget:enter-notify-event connect-signal
313     win "button-press-event" [ on-button-press yield ]
314     GtkWidget:button-press-event connect-signal
315     win "button-release-event" [ on-button-release yield ]
316     GtkWidget:button-release-event connect-signal
317     win "scroll-event" [ on-scroll yield ]
318     GtkWidget:scroll-event connect-signal
319     win "key-press-event" [ on-key-press yield ]
320     GtkWidget:key-press-event connect-signal
321     win "key-release-event" [ on-key-release yield ]
322     GtkWidget:key-release-event connect-signal
323     win "focus-in-event" [ on-focus-in yield ]
324     GtkWidget:focus-in-event connect-signal
325     win "focus-out-event" [ on-focus-out yield ]
326     GtkWidget:focus-out-event connect-signal
327     win "delete-event" [ on-delete yield ]
328     GtkWidget:delete-event connect-signal ;
329
330 ! ----------------------
331
332 GENERIC: support-input-methods? ( gadget -- ? )
333 GENERIC: get-cursor-surrounding ( gadget -- text cursor-pos )
334 GENERIC: delete-cursor-surrounding ( offset count gadget -- )
335 GENERIC: set-preedit-string ( str cursor-pos gadget -- )
336 GENERIC: get-cursor-loc&dim ( gadget -- loc dim )
337
338 M: gadget support-input-methods? drop f ;
339
340 M: editor support-input-methods? drop t ;
341
342 M: editor get-cursor-surrounding
343     dup editor-caret first2 [ swap editor-line ] dip ;
344
345 M: editor delete-cursor-surrounding
346     3drop ;
347
348 M: editor set-preedit-string
349     nip dup [ editor-caret ] keep
350     [ user-input* drop ] 2dip
351     set-caret ;
352
353 M: editor get-cursor-loc&dim
354     [ caret-loc ] [ caret-dim ] bi ;
355
356 ! ----------------------
357
358 : on-retrieve-surrounding ( im-context win -- ? )
359     window world-focus dup support-input-methods? [
360         get-cursor-surrounding [ utf8 string>alien -1 ] dip
361         gtk_im_context_set_surrounding t
362     ] [ 2drop f ] if ;
363
364 : on-delete-surrounding ( im-context offset n win -- ? )
365     window world-focus dup support-input-methods?
366     [ delete-cursor-surrounding t ] [ 3drop f ] if nip ;
367
368 : get-preedit-string ( im-context -- str cursor-pos )
369     { void* int } [ f swap gtk_im_context_get_preedit_string ]
370     with-out-parameters 
371     [ [ utf8 alien>string ] [ g_free ] bi ] dip ;
372             
373 : on-preedit-changed ( im-context user-data -- )
374     window world-focus dup support-input-methods? [
375         [ get-preedit-string ] dip set-preedit-string
376     ] [ 2drop ] if ;
377
378 : on-commit ( sender str user_data -- )
379     [ drop ] [ utf8 alien>string ] [ window ] tri* user-input ;
380
381 : gadget-location ( gadget -- loc )
382     [ loc>> ] [ parent>> [ gadget-location v+ ] when* ] bi ;
383
384 : gadget-cursor-location ( gadget -- rectangle )
385     [ gadget-location ] [ get-cursor-loc&dim ] bi [ v+ ] dip
386     [ first2 ] bi@ GdkRectangle <struct-boa> ;
387
388 : update-cursor-location ( im-context gadget -- )
389     gadget-cursor-location gtk_im_context_set_cursor_location ;
390
391 ! has to be called before the window signal handler
392 :: im-on-key-event ( sender event im-context -- result )
393     sender window world-focus :> gadget
394     gadget support-input-methods? [
395         im-context gadget update-cursor-location
396         im-context event gtk_im_context_filter_keypress
397     ] [ im-context gtk_im_context_reset f ] if ;
398
399 : im-on-focus-in ( sender event user-data -- result )
400     2drop window handle>> im-context>>
401     [ gtk_im_context_focus_in ] [ gtk_im_context_reset ] bi f ;
402
403 : im-on-focus-out ( sender event user-data -- result )
404     2drop window handle>> im-context>>
405     [ gtk_im_context_focus_out ] [ gtk_im_context_reset ] bi f ;
406
407 : im-on-destroy ( sender user-data -- )
408     nip [ f gtk_im_context_set_client_window ]
409     [ g_object_unref ] bi ;
410
411 ! for testing only
412
413 : com-input-method ( world -- )
414     find-world handle>> im-menu>> f f f f 0
415     gtk_get_current_event_time gtk_menu_popup ;
416
417 : im-menu ( world -- )
418     { com-input-method } show-commands-menu ;
419
420 editor "input-method" f  {
421     { T{ button-down f { S+ C+ } 3 } im-menu }
422 } define-command-map
423
424 ! --------
425
426 :: configure-im ( win im -- )
427     im win gtk_widget_get_window gtk_im_context_set_client_window
428     im f gtk_im_context_set_use_preedit
429
430     gtk_menu_new :> menu
431     im menu gtk_im_multicontext_append_menuitems
432     menu win window handle>> im-menu<<
433     
434     im "commit" [ on-commit yield ]
435     GtkIMContext:commit win connect-signal-with-data
436     im "retrieve-surrounding" [ on-retrieve-surrounding yield ]
437     GtkIMContext:retrieve-surrounding win connect-signal-with-data
438     im "delete-surrounding" [ on-delete-surrounding yield ]
439     GtkIMContext:delete-surrounding win connect-signal-with-data
440     im "preedit-changed" [ on-preedit-changed yield ]
441     GtkIMContext:preedit-changed win connect-signal-with-data
442
443     win "key-press-event" [ im-on-key-event yield ]
444     GtkWidget:key-press-event im connect-signal-with-data
445     win "key-release-event" [ im-on-key-event yield ]
446     GtkWidget:key-release-event im connect-signal-with-data
447     win "focus-in-event" [ im-on-focus-in yield ]
448     GtkWidget:focus-out-event im connect-signal-with-data
449     win "focus-out-event" [ im-on-focus-out yield ]
450     GtkWidget:focus-out-event im connect-signal-with-data
451     win "destroy" [ im-on-destroy yield ]
452     GtkObject:destroy im connect-signal-with-data ;
453
454 CONSTANT: window-controls>decor-flags
455     H{
456         { close-button 0 }
457         { minimize-button $ GDK_DECOR_MINIMIZE }
458         { maximize-button $ GDK_DECOR_MAXIMIZE }
459         { resize-handles $ GDK_DECOR_RESIZEH }
460         { small-title-bar $ GDK_DECOR_TITLE }
461         { normal-title-bar $ GDK_DECOR_TITLE }
462         { textured-background 0 }
463     }
464     
465 CONSTANT: window-controls>func-flags
466     H{
467         { close-button $ GDK_FUNC_CLOSE }
468         { minimize-button $ GDK_FUNC_MINIMIZE }
469         { maximize-button $ GDK_FUNC_MAXIMIZE }
470         { resize-handles $ GDK_FUNC_RESIZE }
471         { small-title-bar 0 }
472         { normal-title-bar 0 }
473         { textured-background 0 }
474     }
475
476 : configure-window-controls ( win controls -- )
477     [
478         small-title-bar swap member-eq?
479         GDK_WINDOW_TYPE_HINT_UTILITY GDK_WINDOW_TYPE_HINT_NORMAL ?
480         gtk_window_set_type_hint
481     ] [
482         [ gtk_widget_get_window ] dip
483         window-controls>decor-flags symbols>flags
484         GDK_DECOR_BORDER bitor gdk_window_set_decorations
485     ] [
486         [ gtk_widget_get_window ] dip
487         window-controls>func-flags symbols>flags
488         GDK_FUNC_MOVE bitor gdk_window_set_functions
489     ] 2tri ;
490
491 : setup-gl ( world -- ? )
492     [
493         [ handle>> window>> ] [ handle>> ] bi*
494         f t GDK_GL_RGBA_TYPE gtk_widget_set_gl_capability
495     ] with-world-pixel-format ;
496
497 : auto-position ( win loc -- )
498     dup { 0 0 } = [
499         drop dup window topmost-window =
500         GTK_WIN_POS_CENTER GTK_WIN_POS_NONE ?
501         gtk_window_set_position
502     ] [ first2 gtk_window_move ] if ;
503
504 M:: gtk-ui-backend (open-window) ( world -- )
505     GTK_WINDOW_TOPLEVEL gtk_window_new :> win
506     gtk_im_multicontext_new :> im
507     
508     win im <window-handle> world handle<<
509
510     world win register-window
511     
512     win world [ window-loc>> auto-position ]
513     [ dim>> first2 gtk_window_set_default_size ] 2bi
514     
515     world setup-gl drop
516
517     win gtk_widget_realize
518     win world window-controls>> configure-window-controls
519     
520     win im configure-im
521     win connect-signals
522
523     win gtk_widget_show_all ;
524
525 M: gtk-ui-backend (close-window) ( handle -- )
526     window>> [ gtk_widget_destroy ] [ unregister-window ] bi
527     event-loop? [ gtk_main_quit ] unless ;
528
529 M: gtk-ui-backend set-title
530     swap [ handle>> window>> ] [ utf8 string>alien ] bi*
531     gtk_window_set_title ;
532
533 M: gtk-ui-backend (set-fullscreen)
534     [ handle>> ] dip [ >>fullscreen? ] keep
535     [ window>> ] dip
536     [ gtk_window_fullscreen ]
537     [ gtk_window_unfullscreen ] if ;
538
539 M: gtk-ui-backend (fullscreen?)
540     handle>> fullscreen?>> ;
541     
542 M: gtk-ui-backend raise-window*
543     handle>> window>> gtk_window_present ;
544
545 : set-cursor ( win cursor -- )
546     [
547         [ gtk_widget_get_window ] dip
548         gdk_cursor_new &gdk_cursor_unref
549         gdk_window_set_cursor
550     ] with-destructors ;
551
552 M: gtk-ui-backend (grab-input)
553     window>>
554     [ gtk_grab_add ] [ GDK_BLANK_CURSOR set-cursor ] bi ;
555
556 M: gtk-ui-backend (ungrab-input)
557     window>>
558     [ gtk_grab_remove ] [ GDK_LEFT_PTR set-cursor ] bi ;
559
560 M: window-handle select-gl-context ( handle -- )
561     window>>
562     [ gtk_widget_get_gl_window ] [ gtk_widget_get_gl_context ] bi
563     gdk_gl_drawable_make_current drop ;
564
565 M: window-handle flush-gl-context ( handle -- )
566     window>> gtk_widget_get_gl_window
567     gdk_gl_drawable_swap_buffers ;
568
569 M: gtk-ui-backend beep
570     gdk_beep ;
571
572 M:: gtk-ui-backend system-alert ( caption text -- )
573     f GTK_DIALOG_MODAL GTK_MESSAGE_WARNING GTK_BUTTONS_OK
574     caption utf8 string>alien f gtk_message_dialog_new
575     [ text utf8 string>alien f gtk_message_dialog_format_secondary_text ]
576     [ gtk_dialog_run drop ]
577     [ gtk_widget_destroy ] tri ;
578
579 M: gtk-clipboard clipboard-contents
580     [
581         handle>> gtk_clipboard_wait_for_text
582         [ &g_free utf8 alien>string ] [ f ] if*
583     ] with-destructors ;
584
585 M: gtk-clipboard set-clipboard-contents
586     swap [ handle>> ] [ utf8 string>alien ] bi*
587     -1 gtk_clipboard_set_text ;
588
589 gtk-ui-backend ui-backend set-global
590
591 [ "ui.tools" ] main-vocab-hook set-global