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