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
14 RENAME: windows ui.private => ui:windows
15 EXCLUDE: ui.gadgets.editors => change-caret ;
16 RENAME: change-caret ui.gadgets.editors => editors:change-caret
19 SINGLETON: gtk-ui-backend
22 TUPLE: window-handle < handle window fullscreen? im-context im-menu ;
24 : <window-handle> ( window im-context -- window-handle )
29 TUPLE: gtk-clipboard handle ;
31 C: <gtk-clipboard> gtk-clipboard
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 } }
55 M: gtk-ui-backend (make-pixel-format)
56 nip >gl-config-attribs-int-array gdk_gl_config_new ;
58 M: gtk-ui-backend (free-pixel-format)
59 handle>> g_object_unref ;
61 M: gtk-ui-backend (pixel-format-attribute)
62 [ handle>> ] [ >gl-config-attribs ] bi*
63 { int } [ gdk_gl_config_get_attrib drop ]
68 GDK_POINTER_MOTION_MASK
69 GDK_POINTER_MOTION_HINT_MASK
73 GDK_BUTTON_RELEASE_MASK
81 { S+ $ GDK_SHIFT_MASK }
82 { C+ $ GDK_CONTROL_MASK }
83 { A+ $ GDK_MOD1_MASK }
86 CONSTANT: action-key-codes
88 { $ GDK_BackSpace "BACKSPACE" }
90 { $ GDK_Return "RET" }
91 { $ GDK_KP_Enter "ENTER" }
92 { $ GDK_Escape "ESC" }
93 { $ GDK_Delete "DELETE" }
97 { $ GDK_Right "RIGHT" }
99 { $ GDK_Page_Up "PAGE_UP" }
100 { $ GDK_Page_Down "PAGE_DOWN" }
102 { $ GDK_Begin "BEGIN" }
117 : event-modifiers ( event -- seq )
118 state>> modifiers modifier ;
120 : event-loc ( event -- loc )
121 [ x>> ] [ y>> ] bi [ >fixnum ] bi@ 2array ;
123 : event-dim ( event -- dim )
124 [ width>> ] [ height>> ] bi 2array ;
126 : scroll-direction ( event -- pair )
128 { $ GDK_SCROLL_UP { 0 -1 } }
129 { $ GDK_SCROLL_DOWN { 0 1 } }
130 { $ GDK_SCROLL_LEFT { -1 0 } }
131 { $ GDK_SCROLL_RIGHT { 1 0 } }
134 : mouse-event>gesture ( event -- modifiers button loc )
135 [ event-modifiers ] [ button>> ] [ event-loc ] tri ;
137 : on-motion ( sender event user-data -- result )
139 [ GdkEventMotion memory>struct event-loc ] dip window
140 move-hand fire-motion t ;
142 : on-enter ( sender event user-data -- result )
145 : on-leave ( sender event user-data -- result )
146 3drop forget-rollover t ;
148 : on-button-press ( sender event user-data -- result )
150 GdkEventButton memory>struct
151 mouse-event>gesture [ <button-down> ] dip
152 ] dip window send-button-down t ;
154 : on-button-release ( sender event user-data -- result )
156 GdkEventButton memory>struct
157 mouse-event>gesture [ <button-up> ] dip
158 ] dip window send-button-up t ;
160 : on-scroll ( sender event user-data -- result )
162 GdkEventScroll memory>struct
163 [ scroll-direction ] [ event-loc ] bi
164 ] dip window send-scroll t ;
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 ;
170 : key-event>gesture ( event -- mods sym/f action? )
171 GdkEventKey memory>struct
172 [ event-modifiers ] [ key-sym ] bi ;
174 : on-key-press ( sender event user-data -- result )
175 drop swap [ key-event>gesture <key-down> ] [ window ] bi*
176 propagate-key-gesture t ;
178 : on-key-release ( sender event user-data -- result )
179 drop swap [ key-event>gesture <key-up> ] [ window ] bi*
180 propagate-key-gesture t ;
182 : on-focus-in ( sender event user-data -- result )
183 2drop window focus-world t ;
185 : on-focus-out ( sender event user-data -- result )
186 2drop window unfocus-world t ;
188 : on-expose ( sender event user-data -- result )
189 2drop window relayout t ;
191 : on-configure ( sender event user-data -- result )
192 drop [ window ] dip GdkEventConfigure memory>struct
193 [ event-loc >>window-loc ] [ event-dim >>dim ] bi
196 : on-delete ( sender event user-data -- result )
197 2drop window ungraft t ;
199 : init-clipboard ( -- )
201 clipboard "CLIPBOARD"
203 utf8 string>alien gdk_atom_intern_static_string
204 gtk_clipboard_get <gtk-clipboard> swap set-global
207 : io-source-prepare ( source timeout -- result )
210 : io-source-check ( source -- result )
211 poll_fds>> 0 g_slist_nth_data GPollFD memory>struct
214 : io-source-dispatch ( source callback user_data -- result )
216 0 mx get wait-for-events
219 CONSTANT: poll-fd-events
229 : create-poll-fd ( -- poll-fd )
230 GPollFD malloc-struct &free
232 poll-fd-events >>events ;
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 ;
245 : set-timeout*-value ( alien value -- )
246 swap 0 set-alien-signed-4 ; inline
248 : timeout-prepare ( source timeout* -- result )
249 nip next-timeout get-global nano-count [-]
250 [ 1,000,000 /i set-timeout*-value ] keep 0 = ;
252 : timeout-check ( source -- result )
253 drop next-timeout get-global nano-count [-] 0 = ;
255 : timeout-dispatch ( source callback user_data -- result )
256 3drop sleep-time [ 1,000,000,000 ] unless* nano-count +
257 next-timeout set-global
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 ;
270 ! This file is not in a resource.txt because it can be
271 ! overwritten when deploying. See 'Vocabulary icons'
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
279 M: gtk-ui-backend (with-ui)
294 : connect-signal-with-data ( object signal-name callback data -- )
295 [ utf8 string>alien ] 2dip g_signal_connect drop ;
297 : connect-signal ( object signal-name callback -- )
298 f connect-signal-with-data ;
300 :: connect-signals ( win -- )
301 win events-mask gtk_widget_add_events
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 ;
330 ! ----------------------
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 )
338 M: gadget support-input-methods? drop f ;
340 M: editor support-input-methods? drop t ;
342 M: editor get-cursor-surrounding
343 dup editor-caret first2 [ swap editor-line ] dip ;
345 M: editor delete-cursor-surrounding
348 M: editor set-preedit-string
349 nip dup [ editor-caret ] keep
350 [ user-input* drop ] 2dip
353 M: editor get-cursor-loc&dim
354 [ caret-loc ] [ caret-dim ] bi ;
356 ! ----------------------
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
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 ;
368 : get-preedit-string ( im-context -- str cursor-pos )
369 { void* int } [ f swap gtk_im_context_get_preedit_string ]
371 [ [ utf8 alien>string ] [ g_free ] bi ] dip ;
373 : on-preedit-changed ( im-context user-data -- )
374 window world-focus dup support-input-methods? [
375 [ get-preedit-string ] dip set-preedit-string
378 : on-commit ( sender str user_data -- )
379 [ drop ] [ utf8 alien>string ] [ window ] tri* user-input ;
381 : gadget-location ( gadget -- loc )
382 [ loc>> ] [ parent>> [ gadget-location v+ ] when* ] bi ;
384 : gadget-cursor-location ( gadget -- rectangle )
385 [ gadget-location ] [ get-cursor-loc&dim ] bi [ v+ ] dip
386 [ first2 ] bi@ GdkRectangle <struct-boa> ;
388 : update-cursor-location ( im-context gadget -- )
389 gadget-cursor-location gtk_im_context_set_cursor_location ;
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 ;
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 ;
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 ;
407 : im-on-destroy ( sender user-data -- )
408 nip [ f gtk_im_context_set_client_window ]
409 [ g_object_unref ] bi ;
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 ;
417 : im-menu ( world -- )
418 { com-input-method } show-commands-menu ;
420 editor "input-method" f {
421 { T{ button-down f { S+ C+ } 3 } im-menu }
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
431 im menu gtk_im_multicontext_append_menuitems
432 menu win window handle>> im-menu<<
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
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 ;
454 CONSTANT: window-controls>decor-flags
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 }
465 CONSTANT: window-controls>func-flags
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 }
476 : configure-window-controls ( win controls -- )
478 small-title-bar swap member-eq?
479 GDK_WINDOW_TYPE_HINT_UTILITY GDK_WINDOW_TYPE_HINT_NORMAL ?
480 gtk_window_set_type_hint
482 [ gtk_widget_get_window ] dip
483 window-controls>decor-flags symbols>flags
484 GDK_DECOR_BORDER bitor gdk_window_set_decorations
486 [ gtk_widget_get_window ] dip
487 window-controls>func-flags symbols>flags
488 GDK_FUNC_MOVE bitor gdk_window_set_functions
491 : setup-gl ( world -- ? )
493 [ handle>> window>> ] [ handle>> ] bi*
494 f t GDK_GL_RGBA_TYPE gtk_widget_set_gl_capability
495 ] with-world-pixel-format ;
497 : auto-position ( win loc -- )
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 ;
504 M:: gtk-ui-backend (open-window) ( world -- )
505 GTK_WINDOW_TOPLEVEL gtk_window_new :> win
506 gtk_im_multicontext_new :> im
508 win im <window-handle> world handle<<
510 world win register-window
512 win world [ window-loc>> auto-position ]
513 [ dim>> first2 gtk_window_set_default_size ] 2bi
517 win gtk_widget_realize
518 win world window-controls>> configure-window-controls
523 win gtk_widget_show_all ;
525 M: gtk-ui-backend (close-window) ( handle -- )
526 window>> [ gtk_widget_destroy ] [ unregister-window ] bi
527 event-loop? [ gtk_main_quit ] unless ;
529 M: gtk-ui-backend set-title
530 swap [ handle>> window>> ] [ utf8 string>alien ] bi*
531 gtk_window_set_title ;
533 M: gtk-ui-backend (set-fullscreen)
534 [ handle>> ] dip [ >>fullscreen? ] keep
536 [ gtk_window_fullscreen ]
537 [ gtk_window_unfullscreen ] if ;
539 M: gtk-ui-backend (fullscreen?)
540 handle>> fullscreen?>> ;
542 M: gtk-ui-backend raise-window*
543 handle>> window>> gtk_window_present ;
545 : set-cursor ( win cursor -- )
547 [ gtk_widget_get_window ] dip
548 gdk_cursor_new &gdk_cursor_unref
549 gdk_window_set_cursor
552 M: gtk-ui-backend (grab-input)
554 [ gtk_grab_add ] [ GDK_BLANK_CURSOR set-cursor ] bi ;
556 M: gtk-ui-backend (ungrab-input)
558 [ gtk_grab_remove ] [ GDK_LEFT_PTR set-cursor ] bi ;
560 M: window-handle select-gl-context ( handle -- )
562 [ gtk_widget_get_gl_window ] [ gtk_widget_get_gl_context ] bi
563 gdk_gl_drawable_make_current drop ;
565 M: window-handle flush-gl-context ( handle -- )
566 window>> gtk_widget_get_gl_window
567 gdk_gl_drawable_swap_buffers ;
569 M: gtk-ui-backend beep
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 ;
579 M: gtk-clipboard clipboard-contents
581 handle>> gtk_clipboard_wait_for_text
582 [ &g_free utf8 alien>string ] [ f ] if*
585 M: gtk-clipboard set-clipboard-contents
586 swap [ handle>> ] [ utf8 string>alien ] bi*
587 -1 gtk_clipboard_set_text ;
589 gtk-ui-backend ui-backend set-global
591 [ "ui.tools" ] main-vocab-hook set-global