]> gitweb.factorcode.org Git - factor.git/blob - basis/ui/backend/gtk/gtk.factor
5169d880cd0d6863088f53eef3ea8f478304165e
[factor.git] / basis / ui / backend / gtk / gtk.factor
1 ! Copyright (C) 2010, 2011 Anton Gorenko, Philipp Bruschweiler.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors alien.accessors alien.c-types alien.data
4 alien.strings arrays assocs classes.struct command-line
5 continuations destructors environment gdk.ffi gdk.gl.ffi
6 glib.ffi gobject-introspection.standard-types gobject.ffi
7 gtk.ffi gtk.gl.ffi io.encodings.utf8 kernel libc literals locals
8 math math.bitwise math.order math.vectors namespaces sequences
9 strings system threads ui ui.backend ui.backend.gtk.input-methods
10 ui.backend.gtk.io ui.clipboards ui.event-loop ui.gadgets
11 ui.gadgets.private ui.gadgets.worlds ui.gestures
12 ui.pixel-formats ui.pixel-formats.private ui.private
13 vocabs.loader ;
14 IN: ui.backend.gtk
15
16 SINGLETON: gtk-ui-backend
17
18 TUPLE: handle ;
19 TUPLE: window-handle < handle window fullscreen? im-context ;
20
21 : <window-handle> ( window im-context -- window-handle )
22     window-handle new
23         swap >>im-context
24         swap >>window ;
25
26 : connect-signal-with-data ( object signal-name callback data -- )
27     [ utf8 string>alien ] 2dip g_signal_connect drop ;
28
29 : connect-signal ( object signal-name callback -- )
30     f connect-signal-with-data ;
31
32 ! Clipboards
33
34 TUPLE: gtk-clipboard handle ;
35
36 C: <gtk-clipboard> gtk-clipboard
37
38 M: gtk-clipboard clipboard-contents
39     [
40         handle>> gtk_clipboard_wait_for_text
41         [ &g_free utf8 alien>string ] [ f ] if*
42     ] with-destructors ;
43
44 M: gtk-clipboard set-clipboard-contents
45     swap [ handle>> ] [ utf8 string>alien ] bi*
46     -1 gtk_clipboard_set_text ;
47
48 : init-clipboard ( -- )
49     selection "PRIMARY"
50     clipboard "CLIPBOARD"
51     [
52         utf8 string>alien gdk_atom_intern_static_string
53         gtk_clipboard_get <gtk-clipboard> swap set-global
54     ] 2bi@ ;
55
56 ! Timer
57
58 SYMBOL: next-fire-time
59
60 : set-timeout*-value ( alien value -- )
61     swap 0 set-alien-signed-4 ; inline
62
63 : timer-prepare ( source timeout* -- ? )
64     nip next-fire-time get-global nano-count [-]
65     [ 1,000,000 /i set-timeout*-value ] keep 0 = ;
66
67 : timer-check ( source -- ? )
68     drop next-fire-time get-global nano-count [-] 0 = ;
69
70 : timer-dispatch ( source callback user_data -- ? )
71     3drop sleep-time [ 1,000,000,000 ] unless* nano-count +
72     next-fire-time set-global
73     yield t ;
74
75 : <timer-funcs> ( -- timer-funcs )
76     GSourceFuncs malloc-struct
77         [ timer-prepare ] GSourceFuncsPrepareFunc >>prepare
78         [ timer-check ] GSourceFuncsCheckFunc >>check
79         [ timer-dispatch ] GSourceFuncsDispatchFunc >>dispatch ;
80
81 :: with-timer ( quot -- )
82     nano-count next-fire-time set-global
83     <timer-funcs> &free
84     GSource heap-size g_source_new &g_source_unref :> source
85     source f g_source_attach drop
86     [ quot call( -- ) ]
87     [ source g_source_destroy ] [ ] cleanup ;
88
89 ! User input
90
91 CONSTANT: events-mask
92     flags{
93         GDK_POINTER_MOTION_MASK
94         GDK_POINTER_MOTION_HINT_MASK
95         GDK_ENTER_NOTIFY_MASK
96         GDK_LEAVE_NOTIFY_MASK
97         GDK_BUTTON_PRESS_MASK
98         GDK_BUTTON_RELEASE_MASK
99         GDK_KEY_PRESS_MASK
100         GDK_KEY_RELEASE_MASK
101         GDK_FOCUS_CHANGE_MASK
102     }
103
104 CONSTANT: modifiers
105     {
106         { S+ $ GDK_SHIFT_MASK }
107         { C+ $ GDK_CONTROL_MASK }
108         { A+ $ GDK_MOD1_MASK }
109     }
110
111 CONSTANT: action-key-codes
112     H{
113         { $ GDK_KEY_BackSpace "BACKSPACE" }
114         { $ GDK_KEY_Tab "TAB" }
115         { $ GDK_KEY_Return "RET" }
116         { $ GDK_KEY_KP_Enter "ENTER" }
117         { $ GDK_KEY_Escape "ESC" }
118         { $ GDK_KEY_Delete "DELETE" }
119         { $ GDK_KEY_Home "HOME" }
120         { $ GDK_KEY_Left "LEFT" }
121         { $ GDK_KEY_Up "UP" }
122         { $ GDK_KEY_Right "RIGHT" }
123         { $ GDK_KEY_Down "DOWN" }
124         { $ GDK_KEY_Page_Up "PAGE_UP" }
125         { $ GDK_KEY_Page_Down "PAGE_DOWN" }
126         { $ GDK_KEY_End "END" }
127         { $ GDK_KEY_Begin "BEGIN" }
128         { $ GDK_KEY_F1 "F1" }
129         { $ GDK_KEY_F2 "F2" }
130         { $ GDK_KEY_F3 "F3" }
131         { $ GDK_KEY_F4 "F4" }
132         { $ GDK_KEY_F5 "F5" }
133         { $ GDK_KEY_F6 "F6" }
134         { $ GDK_KEY_F7 "F7" }
135         { $ GDK_KEY_F8 "F8" }
136         { $ GDK_KEY_F9 "F9" }
137         { $ GDK_KEY_F10 "F10" }
138         { $ GDK_KEY_F11 "F11" }
139         { $ GDK_KEY_F12 "F12" }
140     }
141
142 : event-modifiers ( event -- seq )
143     state>> modifiers modifier ;
144
145 : event-loc ( event -- loc )
146     [ x>> ] [ y>> ] bi [ >fixnum ] bi@ 2array ;
147
148 : event-dim ( event -- dim )
149     [ width>> ] [ height>> ] bi 2array ;
150
151 : scroll-direction ( event -- pair )
152     direction>> {
153         { $ GDK_SCROLL_UP { 0 -1 } }
154         { $ GDK_SCROLL_DOWN { 0 1 } }
155         { $ GDK_SCROLL_LEFT { -1 0 } }
156         { $ GDK_SCROLL_RIGHT { 1 0 } }
157     } at ;
158
159 : mouse-event>gesture ( event -- modifiers button loc )
160     [ event-modifiers ] [ button>> ] [ event-loc ] tri ;
161
162 : on-motion ( win event user-data -- ? )
163     drop swap
164     [ event-loc ] dip window
165     move-hand fire-motion t ;
166
167 : on-leave ( win event user-data -- ? )
168     3drop forget-rollover t ;
169
170 : on-button-press ( win event user-data -- ? )
171     drop swap [
172         mouse-event>gesture [ <button-down> ] dip
173     ] dip window send-button-down t ;
174
175 : on-button-release ( win event user-data -- ? )
176     drop swap [
177         mouse-event>gesture [ <button-up> ] dip
178     ] dip window send-button-up t ;
179
180 : on-scroll ( win event user-data -- ? )
181     drop swap [
182         [ scroll-direction ] [ event-loc ] bi
183     ] dip window send-scroll t ;
184
185 : key-sym ( event -- sym/f action? )
186     keyval>> dup action-key-codes at [ t ]
187     [ gdk_keyval_to_unicode [ f ] [ 1string ] if-zero f ] ?if ;
188
189 : key-event>gesture ( event -- mods sym/f action? )
190     [ event-modifiers ] [ key-sym ] bi ;
191   
192 : on-key-press ( win event user-data -- ? )
193     drop swap [ key-event>gesture <key-down> ] [ window ] bi*
194     propagate-key-gesture t ;
195
196 : on-key-release ( win event user-data -- ? )
197     drop swap [ key-event>gesture <key-up> ] [ window ] bi*
198     propagate-key-gesture t ;
199
200 : on-focus-in ( win event user-data -- ? )
201     2drop window focus-world t ;
202
203 : on-focus-out ( win event user-data -- ? )
204     2drop window unfocus-world t ;
205
206 :: connect-user-input-signals ( win -- )
207     win events-mask gtk_widget_add_events
208     win "motion-notify-event" [ on-motion yield ]
209     GtkWidget:motion-notify-event connect-signal
210     win "leave-notify-event" [ on-leave yield ]
211     GtkWidget:leave-notify-event connect-signal
212     win "button-press-event" [ on-button-press yield ]
213     GtkWidget:button-press-event connect-signal
214     win "button-release-event" [ on-button-release yield ]
215     GtkWidget:button-release-event connect-signal
216     win "scroll-event" [ on-scroll yield ]
217     GtkWidget:scroll-event connect-signal
218     win "key-press-event" [ on-key-press yield ]
219     GtkWidget:key-press-event connect-signal
220     win "key-release-event" [ on-key-release yield ]
221     GtkWidget:key-release-event connect-signal
222     win "focus-in-event" [ on-focus-in yield ]
223     GtkWidget:focus-in-event connect-signal
224     win "focus-out-event" [ on-focus-out yield ]
225     GtkWidget:focus-out-event connect-signal ;
226
227 ! Window state events
228
229 : on-expose ( win event user-data -- ? )
230     2drop window relayout t ;
231
232 : on-configure ( win event user-data -- ? )
233     drop [ window ] [ GdkEventConfigure memory>struct ] bi*
234     [ event-loc >>window-loc ] [ event-dim >>dim ] bi
235     relayout-1 f ;
236
237 : on-delete ( win event user-data -- ? )
238     2drop window ungraft t ;
239
240 :: connect-win-state-signals ( win -- )
241     win "expose-event" [ on-expose yield ]
242     GtkWidget:expose-event connect-signal
243     win "configure-event" [ on-configure yield ]
244     GtkWidget:configure-event connect-signal
245     win "delete-event" [ on-delete yield ]
246     GtkWidget:delete-event connect-signal ;
247
248 ! Input methods
249
250 : on-retrieve-surrounding ( im-context win -- ? )
251     window world-focus dup support-input-methods? [
252         cursor-surrounding [ utf8 string>alien -1 ] dip
253         gtk_im_context_set_surrounding t
254     ] [ 2drop f ] if ;
255
256 : on-delete-surrounding ( im-context offset n win -- ? )
257     window world-focus dup support-input-methods?
258     [ delete-cursor-surrounding t ] [ 3drop f ] if nip ;
259
260 : on-commit ( im-context str win -- )
261     [ drop ] [ utf8 alien>string ] [ window ] tri* user-input ;
262
263 : gadget-cursor-location ( gadget -- rectangle )
264     [ screen-loc ] [ cursor-loc&dim ] bi [ v+ ] dip
265     [ first2 [ >fixnum ] bi@ ] bi@
266     cairo_rectangle_int_t <struct-boa> ;
267
268 : update-cursor-location ( im-context gadget -- )
269     gadget-cursor-location gtk_im_context_set_cursor_location ;
270
271 ! has to be called before the window signal handler
272 :: im-on-key-event ( win event im-context -- ? )
273     win window world-focus :> gadget
274     gadget support-input-methods? [
275         im-context gadget update-cursor-location
276         im-context event gtk_im_context_filter_keypress
277     ] [ im-context gtk_im_context_reset f ] if ;
278
279 : im-on-focus-in ( win event im-context -- ? )
280     2nip
281     [ gtk_im_context_focus_in ] [ gtk_im_context_reset ] bi f ;
282
283 : im-on-focus-out ( win event im-context -- ? )
284     2nip
285     [ gtk_im_context_focus_out ] [ gtk_im_context_reset ] bi f ;
286
287 : im-on-destroy ( win im-context -- )
288     nip [ f gtk_im_context_set_client_window ]
289     ! weird GLib-GObject-WARNING message appears after calling this code
290     ! [ g_object_unref ] bi ;
291     [ drop ] bi ;
292
293 :: configure-im ( win im -- )
294     im win gtk_widget_get_window gtk_im_context_set_client_window
295     im f gtk_im_context_set_use_preedit
296     
297     im "commit" [ on-commit yield ]
298     GtkIMContext:commit win connect-signal-with-data
299     im "retrieve-surrounding" [ on-retrieve-surrounding yield ]
300     GtkIMContext:retrieve-surrounding win connect-signal-with-data
301     im "delete-surrounding" [ on-delete-surrounding yield ]
302     GtkIMContext:delete-surrounding win connect-signal-with-data
303
304     win "key-press-event" [ im-on-key-event yield ]
305     GtkWidget:key-press-event im connect-signal-with-data
306     win "key-release-event" [ im-on-key-event yield ]
307     GtkWidget:key-release-event im connect-signal-with-data
308     win "focus-in-event" [ im-on-focus-in yield ]
309     GtkWidget:focus-out-event im connect-signal-with-data
310     win "focus-out-event" [ im-on-focus-out yield ]
311     GtkWidget:focus-out-event im connect-signal-with-data
312     win "destroy" [ im-on-destroy yield ]
313     GtkObject:destroy im connect-signal-with-data ;
314
315 ! Window controls
316
317 CONSTANT: window-controls>decor-flags
318     H{
319         { close-button 0 }
320         { minimize-button $ GDK_DECOR_MINIMIZE }
321         { maximize-button $ GDK_DECOR_MAXIMIZE }
322         { resize-handles $ GDK_DECOR_RESIZEH }
323         { small-title-bar $ GDK_DECOR_TITLE }
324         { normal-title-bar $ GDK_DECOR_TITLE }
325         { textured-background 0 }
326     }
327     
328 CONSTANT: window-controls>func-flags
329     H{
330         { close-button $ GDK_FUNC_CLOSE }
331         { minimize-button $ GDK_FUNC_MINIMIZE }
332         { maximize-button $ GDK_FUNC_MAXIMIZE }
333         { resize-handles $ GDK_FUNC_RESIZE }
334         { small-title-bar 0 }
335         { normal-title-bar 0 }
336         { textured-background 0 }
337     }
338
339 : configure-window-controls ( win controls -- )
340     [
341         small-title-bar swap member-eq?
342         GDK_WINDOW_TYPE_HINT_UTILITY GDK_WINDOW_TYPE_HINT_NORMAL ?
343         gtk_window_set_type_hint
344     ] [
345         [ gtk_widget_get_window ] dip
346         window-controls>decor-flags symbols>flags
347         GDK_DECOR_BORDER bitor gdk_window_set_decorations
348     ] [
349         [ gtk_widget_get_window ] dip
350         window-controls>func-flags symbols>flags
351         GDK_FUNC_MOVE bitor gdk_window_set_functions
352     ] 2tri ;
353
354 ! OpenGL and Pixel formats
355
356 PIXEL-FORMAT-ATTRIBUTE-TABLE: gl-config-attribs
357     ${ GDK_GL_USE_GL GDK_GL_RGBA }
358     H{
359         { double-buffered ${ GDK_GL_DOUBLEBUFFER } }
360         { stereo ${ GDK_GL_STEREO } }
361         ! { offscreen ${ GDK_GL_DRAWABLE_TYPE 2 } }
362         ! { fullscreen ${ GDK_GL_DRAWABLE_TYPE 1 } }
363         ! { windowed ${ GDK_GL_DRAWABLE_TYPE 1 } }
364         { color-bits ${ GDK_GL_BUFFER_SIZE } }
365         { red-bits ${ GDK_GL_RED_SIZE } }
366         { green-bits ${ GDK_GL_GREEN_SIZE } }
367         { blue-bits ${ GDK_GL_BLUE_SIZE } }
368         { alpha-bits ${ GDK_GL_ALPHA_SIZE } }
369         { accum-red-bits ${ GDK_GL_ACCUM_RED_SIZE } }
370         { accum-green-bits ${ GDK_GL_ACCUM_GREEN_SIZE } }
371         { accum-blue-bits ${ GDK_GL_ACCUM_BLUE_SIZE } }
372         { accum-alpha-bits ${ GDK_GL_ACCUM_ALPHA_SIZE } }
373         { depth-bits ${ GDK_GL_DEPTH_SIZE } }
374         { stencil-bits ${ GDK_GL_STENCIL_SIZE } }
375         { aux-buffers ${ GDK_GL_AUX_BUFFERS } }
376         { sample-buffers ${ GDK_GL_SAMPLE_BUFFERS } }
377         { samples ${ GDK_GL_SAMPLES } }
378     }
379
380 M: gtk-ui-backend (make-pixel-format)
381     nip >gl-config-attribs-int-array gdk_gl_config_new ;
382
383 M: gtk-ui-backend (free-pixel-format)
384     handle>> g_object_unref ;
385
386 M: gtk-ui-backend (pixel-format-attribute)
387     [ handle>> ] [ >gl-config-attribs ] bi*
388     { gint } [ gdk_gl_config_get_attrib drop ]
389     with-out-parameters ;
390
391 M: window-handle select-gl-context ( handle -- )
392     window>>
393     [ gtk_widget_get_gl_window ] [ gtk_widget_get_gl_context ] bi
394     gdk_gl_drawable_make_current drop ;
395
396 M: window-handle flush-gl-context ( handle -- )
397     window>> gtk_widget_get_gl_window
398     gdk_gl_drawable_swap_buffers ;
399
400 ! Window
401
402 : configure-gl ( world -- )
403     [
404         [ handle>> window>> ] [ handle>> ] bi*
405         f t GDK_GL_RGBA_TYPE gtk_widget_set_gl_capability drop
406     ] with-world-pixel-format ;
407
408 : auto-position ( win loc -- )
409     dup { 0 0 } = [
410         drop dup window topmost-window =
411         GTK_WIN_POS_CENTER GTK_WIN_POS_NONE ?
412         gtk_window_set_position
413     ] [ first2 gtk_window_move ] if ;
414
415 M:: gtk-ui-backend (open-window) ( world -- )
416     GTK_WINDOW_TOPLEVEL gtk_window_new :> win
417     gtk_im_multicontext_new :> im
418
419     win im <window-handle> world handle<<
420
421     world win register-window
422     
423     win world [ window-loc>> auto-position ]
424     [ dim>> first2 gtk_window_set_default_size ] 2bi
425
426     win "factor" "Factor" [ utf8 string>alien ] bi@
427     gtk_window_set_wmclass
428     
429     world configure-gl
430
431     win gtk_widget_realize
432     win world window-controls>> configure-window-controls
433     
434     win im configure-im
435     win connect-user-input-signals
436     win connect-win-state-signals
437
438     win gtk_widget_show_all ;
439
440 M: gtk-ui-backend (close-window) ( handle -- )
441     window>> [ gtk_widget_destroy ] [ unregister-window ] bi
442     event-loop? [ gtk_main_quit ] unless ;
443
444 M: gtk-ui-backend set-title
445     swap [ handle>> window>> ] [ utf8 string>alien ] bi*
446     gtk_window_set_title ;
447
448 M: gtk-ui-backend (set-fullscreen)
449     [ handle>> ] dip [ >>fullscreen? ] keep
450     [ window>> ] dip
451     [ gtk_window_fullscreen ]
452     [ gtk_window_unfullscreen ] if ;
453
454 M: gtk-ui-backend (fullscreen?)
455     handle>> fullscreen?>> ;
456     
457 M: gtk-ui-backend raise-window*
458     handle>> window>> gtk_window_present ;
459
460 : set-cursor ( win cursor -- )
461     [
462         [ gtk_widget_get_window ] dip
463         gdk_cursor_new &gdk_cursor_unref
464         gdk_window_set_cursor
465     ] with-destructors ;
466
467 M: gtk-ui-backend (grab-input)
468     window>>
469     [ gtk_grab_add ] [ GDK_BLANK_CURSOR set-cursor ] bi ;
470
471 M: gtk-ui-backend (ungrab-input)
472     window>>
473     [ gtk_grab_remove ] [ GDK_LEFT_PTR set-cursor ] bi ;
474
475 ! Misc.
476
477 M: gtk-ui-backend beep
478     gdk_beep ;
479
480 M:: gtk-ui-backend system-alert ( caption text -- )
481     [
482         f GTK_DIALOG_MODAL GTK_MESSAGE_WARNING GTK_BUTTONS_OK
483         caption utf8 string>alien f
484         gtk_message_dialog_new &gtk_widget_destroy
485         [
486             text utf8 string>alien f
487             gtk_message_dialog_format_secondary_text
488         ] [ gtk_dialog_run drop ] bi
489     ] with-destructors ;
490
491 M: gtk-ui-backend (with-ui)
492     [
493         0 gint <ref> f void* <ref> gtk_init
494         0 gint <ref> f void* <ref> gtk_gl_init
495         init-clipboard
496         start-ui
497         [
498             [ [ gtk_main ] with-timer ] with-event-loop
499         ] with-destructors
500     ] ui-running ;
501
502
503 gtk-ui-backend ui-backend set-global
504
505 { "ui.backend.gtk" "io.backend.unix" }
506 "ui.backend.gtk.io.unix" require-when
507
508 { "ui.backend.gtk" "ui.gadgets.editors" }
509 "ui.backend.gtk.input-methods.editors" require-when
510
511 [ "DISPLAY" os-env "ui.tools" "listener" ? ] main-vocab-hook set-global