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 gdk.pixbuf.ffi glib.ffi
7 gobject-introspection.standard-types
8 gobject.ffi gtk.ffi gtk.gl.ffi io.backend
9 io.backend.unix.multiplexers io.encodings.binary
10 io.encodings.utf8 io.files io.thread kernel libc literals
11 locals math math.bitwise math.order math.vectors namespaces
12 sequences strings system threads ui ui.backend ui.backend.gtk.input-methods
13 ui.backend.gtk.io ui.clipboards
14 ui.commands ui.event-loop ui.gadgets ui.gadgets.editors
15 ui.gadgets.menus ui.gadgets.private ui.gadgets.worlds
16 ui.gestures ui.pixel-formats ui.pixel-formats.private
17 ui.private vocabs.loader combinators io ;
20 SINGLETON: gtk-ui-backend
23 TUPLE: window-handle < handle window fullscreen? im-context ;
25 : <window-handle> ( window im-context -- window-handle )
30 : connect-signal-with-data ( object signal-name callback data -- )
31 [ utf8 string>alien ] 2dip g_signal_connect drop ;
33 : connect-signal ( object signal-name callback -- )
34 f connect-signal-with-data ;
38 TUPLE: gtk-clipboard handle ;
40 C: <gtk-clipboard> gtk-clipboard
42 M: gtk-clipboard clipboard-contents
44 handle>> gtk_clipboard_wait_for_text
45 [ &g_free utf8 alien>string ] [ f ] if*
48 M: gtk-clipboard set-clipboard-contents
49 swap [ handle>> ] [ utf8 string>alien ] bi*
50 -1 gtk_clipboard_set_text ;
52 : init-clipboard ( -- )
56 utf8 string>alien gdk_atom_intern_static_string
57 gtk_clipboard_get <gtk-clipboard> swap set-global
62 SYMBOL: next-fire-time
64 : set-timeout*-value ( alien value -- )
65 swap 0 set-alien-signed-4 ; inline
67 : timer-prepare ( source timeout* -- ? )
68 nip next-fire-time get-global nano-count [-]
69 [ 1,000,000 /i set-timeout*-value ] keep 0 = ;
71 : timer-check ( source -- ? )
72 drop next-fire-time get-global nano-count [-] 0 = ;
74 : timer-dispatch ( source callback user_data -- ? )
75 3drop sleep-time [ 1,000,000,000 ] unless* nano-count +
76 next-fire-time set-global
79 : <timer-funcs> ( -- timer-funcs )
80 GSourceFuncs malloc-struct
81 [ timer-prepare ] GSourceFuncsPrepareFunc >>prepare
82 [ timer-check ] GSourceFuncsCheckFunc >>check
83 [ timer-dispatch ] GSourceFuncsDispatchFunc >>dispatch ;
85 :: with-timer ( quot -- )
86 nano-count next-fire-time set-global
88 GSource heap-size g_source_new &g_source_unref :> source
89 source f g_source_attach drop
91 [ source g_source_destroy ] [ ] cleanup ;
97 GDK_POINTER_MOTION_MASK
98 GDK_POINTER_MOTION_HINT_MASK
100 GDK_LEAVE_NOTIFY_MASK
101 GDK_BUTTON_PRESS_MASK
102 GDK_BUTTON_RELEASE_MASK
105 GDK_FOCUS_CHANGE_MASK
110 { S+ $ GDK_SHIFT_MASK }
111 { C+ $ GDK_CONTROL_MASK }
112 { A+ $ GDK_MOD1_MASK }
115 CONSTANT: action-key-codes
117 { $ GDK_KEY_BackSpace "BACKSPACE" }
118 { $ GDK_KEY_Tab "TAB" }
119 { $ GDK_KEY_Return "RET" }
120 { $ GDK_KEY_KP_Enter "ENTER" }
121 { $ GDK_KEY_Escape "ESC" }
122 { $ GDK_KEY_Delete "DELETE" }
123 { $ GDK_KEY_Home "HOME" }
124 { $ GDK_KEY_Left "LEFT" }
125 { $ GDK_KEY_Up "UP" }
126 { $ GDK_KEY_Right "RIGHT" }
127 { $ GDK_KEY_Down "DOWN" }
128 { $ GDK_KEY_Page_Up "PAGE_UP" }
129 { $ GDK_KEY_Page_Down "PAGE_DOWN" }
130 { $ GDK_KEY_End "END" }
131 { $ GDK_KEY_Begin "BEGIN" }
132 { $ GDK_KEY_F1 "F1" }
133 { $ GDK_KEY_F2 "F2" }
134 { $ GDK_KEY_F3 "F3" }
135 { $ GDK_KEY_F4 "F4" }
136 { $ GDK_KEY_F5 "F5" }
137 { $ GDK_KEY_F6 "F6" }
138 { $ GDK_KEY_F7 "F7" }
139 { $ GDK_KEY_F8 "F8" }
140 { $ GDK_KEY_F9 "F9" }
141 { $ GDK_KEY_F10 "F10" }
142 { $ GDK_KEY_F11 "F11" }
143 { $ GDK_KEY_F12 "F12" }
146 : event-modifiers ( event -- seq )
147 state>> modifiers modifier ;
149 : event-loc ( event -- loc )
150 [ x>> ] [ y>> ] bi [ >fixnum ] bi@ 2array ;
152 : event-dim ( event -- dim )
153 [ width>> ] [ height>> ] bi 2array ;
155 : scroll-direction ( event -- pair )
157 { $ GDK_SCROLL_UP { 0 -1 } }
158 { $ GDK_SCROLL_DOWN { 0 1 } }
159 { $ GDK_SCROLL_LEFT { -1 0 } }
160 { $ GDK_SCROLL_RIGHT { 1 0 } }
163 : mouse-event>gesture ( event -- modifiers button loc )
164 [ event-modifiers ] [ button>> ] [ event-loc ] tri ;
166 : on-motion ( win event user-data -- ? )
168 [ event-loc ] dip window
169 move-hand fire-motion t ;
171 : on-leave ( win event user-data -- ? )
172 3drop forget-rollover t ;
174 :: on-button-press ( win event user-data -- ? )
176 event mouse-event>gesture :> ( modifiers button loc )
180 [ modifiers swap <button-down> loc world
184 :: on-button-release ( win event user-data -- ? )
186 event mouse-event>gesture :> ( modifiers button loc )
188 { 8 [ world left-action send-action ] }
189 { 9 [ world right-action send-action ] }
190 [ modifiers swap <button-up> loc world
194 : on-scroll ( win event user-data -- ? )
196 [ scroll-direction ] [ event-loc ] bi
197 ] dip window send-scroll t ;
199 : key-sym ( event -- sym/f action? )
200 keyval>> dup action-key-codes at [ t ]
201 [ gdk_keyval_to_unicode [ f ] [ 1string ] if-zero f ] ?if ;
203 : key-event>gesture ( event -- mods sym/f action? )
204 [ event-modifiers ] [ key-sym ] bi ;
206 : on-key-press ( win event user-data -- ? )
207 drop swap [ key-event>gesture <key-down> ] [ window ] bi*
208 propagate-key-gesture t ;
210 : on-key-release ( win event user-data -- ? )
211 drop swap [ key-event>gesture <key-up> ] [ window ] bi*
212 propagate-key-gesture t ;
214 : on-focus-in ( win event user-data -- ? )
215 2drop window focus-world t ;
217 : on-focus-out ( win event user-data -- ? )
218 2drop window unfocus-world t ;
220 ! This word gets replaced when deploying. See 'Vocabulary icons'
221 ! in the docs and tools.deploy.shaker.gtk-icon
222 : get-icon-data ( -- byte-array )
223 "resource:misc/icons/Factor_48x48.png" binary file-contents ;
227 data>GInputStream &g_object_unref
228 GInputStream>GdkPixbuf gtk_window_set_default_icon
231 :: connect-user-input-signals ( win -- )
232 win events-mask gtk_widget_add_events
233 win "motion-notify-event" [ on-motion yield ]
234 GtkWidget:motion-notify-event connect-signal
235 win "leave-notify-event" [ on-leave yield ]
236 GtkWidget:leave-notify-event connect-signal
237 win "button-press-event" [ on-button-press yield ]
238 GtkWidget:button-press-event connect-signal
239 win "button-release-event" [ on-button-release yield ]
240 GtkWidget:button-release-event connect-signal
241 win "scroll-event" [ on-scroll yield ]
242 GtkWidget:scroll-event connect-signal
243 win "key-press-event" [ on-key-press yield ]
244 GtkWidget:key-press-event connect-signal
245 win "key-release-event" [ on-key-release yield ]
246 GtkWidget:key-release-event connect-signal
247 win "focus-in-event" [ on-focus-in yield ]
248 GtkWidget:focus-in-event connect-signal
249 win "focus-out-event" [ on-focus-out yield ]
250 GtkWidget:focus-out-event connect-signal ;
252 ! Window state events
254 : on-expose ( win event user-data -- ? )
255 2drop window relayout t ;
257 : on-configure ( win event user-data -- ? )
258 drop [ window ] [ GdkEventConfigure memory>struct ] bi*
259 [ event-loc >>window-loc ] [ event-dim >>dim ] bi
262 : on-delete ( win event user-data -- ? )
263 2drop window ungraft t ;
265 :: connect-win-state-signals ( win -- )
266 win "expose-event" [ on-expose yield ]
267 GtkWidget:expose-event connect-signal
268 win "configure-event" [ on-configure yield ]
269 GtkWidget:configure-event connect-signal
270 win "delete-event" [ on-delete yield ]
271 GtkWidget:delete-event connect-signal ;
275 : on-retrieve-surrounding ( im-context win -- ? )
276 window world-focus dup support-input-methods? [
277 cursor-surrounding [ utf8 string>alien -1 ] dip
278 gtk_im_context_set_surrounding t
281 : on-delete-surrounding ( im-context offset n win -- ? )
282 window world-focus dup support-input-methods?
283 [ delete-cursor-surrounding t ] [ 3drop f ] if nip ;
285 : on-commit ( im-context str win -- )
286 [ drop ] [ utf8 alien>string ] [ window ] tri* user-input ;
288 : gadget-cursor-location ( gadget -- rectangle )
289 [ screen-loc ] [ cursor-loc&dim ] bi [ v+ ] dip
290 [ first2 [ >fixnum ] bi@ ] bi@
291 cairo_rectangle_int_t <struct-boa> ;
293 : update-cursor-location ( im-context gadget -- )
294 gadget-cursor-location gtk_im_context_set_cursor_location ;
296 ! has to be called before the window signal handler
297 :: im-on-key-event ( win event im-context -- ? )
298 win window world-focus :> gadget
299 gadget support-input-methods? [
300 im-context gadget update-cursor-location
301 im-context event gtk_im_context_filter_keypress
302 ] [ im-context gtk_im_context_reset f ] if ;
304 : im-on-focus-in ( win event im-context -- ? )
306 [ gtk_im_context_focus_in ] [ gtk_im_context_reset ] bi f ;
308 : im-on-focus-out ( win event im-context -- ? )
310 [ gtk_im_context_focus_out ] [ gtk_im_context_reset ] bi f ;
312 : im-on-destroy ( win im-context -- )
313 nip [ f gtk_im_context_set_client_window ]
314 ! weird GLib-GObject-WARNING message appears after calling this code
315 ! [ g_object_unref ] bi ;
318 :: configure-im ( win im -- )
319 im win gtk_widget_get_window gtk_im_context_set_client_window
320 im f gtk_im_context_set_use_preedit
322 im "commit" [ on-commit yield ]
323 GtkIMContext:commit win connect-signal-with-data
324 im "retrieve-surrounding" [ on-retrieve-surrounding yield ]
325 GtkIMContext:retrieve-surrounding win connect-signal-with-data
326 im "delete-surrounding" [ on-delete-surrounding yield ]
327 GtkIMContext:delete-surrounding win connect-signal-with-data
329 win "key-press-event" [ im-on-key-event yield ]
330 GtkWidget:key-press-event im connect-signal-with-data
331 win "key-release-event" [ im-on-key-event yield ]
332 GtkWidget:key-release-event im connect-signal-with-data
333 win "focus-in-event" [ im-on-focus-in yield ]
334 GtkWidget:focus-out-event im connect-signal-with-data
335 win "focus-out-event" [ im-on-focus-out yield ]
336 GtkWidget:focus-out-event im connect-signal-with-data
337 win "destroy" [ im-on-destroy yield ]
338 GtkObject:destroy im connect-signal-with-data ;
342 CONSTANT: window-controls>decor-flags
345 { minimize-button $ GDK_DECOR_MINIMIZE }
346 { maximize-button $ GDK_DECOR_MAXIMIZE }
347 { resize-handles $ GDK_DECOR_RESIZEH }
348 { small-title-bar $ GDK_DECOR_TITLE }
349 { normal-title-bar $ GDK_DECOR_TITLE }
350 { textured-background 0 }
353 CONSTANT: window-controls>func-flags
355 { close-button $ GDK_FUNC_CLOSE }
356 { minimize-button $ GDK_FUNC_MINIMIZE }
357 { maximize-button $ GDK_FUNC_MAXIMIZE }
358 { resize-handles $ GDK_FUNC_RESIZE }
359 { small-title-bar 0 }
360 { normal-title-bar 0 }
361 { textured-background 0 }
364 : configure-window-controls ( win controls -- )
366 small-title-bar swap member-eq?
367 GDK_WINDOW_TYPE_HINT_UTILITY GDK_WINDOW_TYPE_HINT_NORMAL ?
368 gtk_window_set_type_hint
370 [ gtk_widget_get_window ] dip
371 window-controls>decor-flags symbols>flags
372 GDK_DECOR_BORDER bitor gdk_window_set_decorations
374 [ gtk_widget_get_window ] dip
375 window-controls>func-flags symbols>flags
376 GDK_FUNC_MOVE bitor gdk_window_set_functions
379 ! OpenGL and Pixel formats
381 PIXEL-FORMAT-ATTRIBUTE-TABLE: gl-config-attribs
382 ${ GDK_GL_USE_GL GDK_GL_RGBA }
384 { double-buffered ${ GDK_GL_DOUBLEBUFFER } }
385 { stereo ${ GDK_GL_STEREO } }
386 ! { offscreen ${ GDK_GL_DRAWABLE_TYPE 2 } }
387 ! { fullscreen ${ GDK_GL_DRAWABLE_TYPE 1 } }
388 ! { windowed ${ GDK_GL_DRAWABLE_TYPE 1 } }
389 { color-bits ${ GDK_GL_BUFFER_SIZE } }
390 { red-bits ${ GDK_GL_RED_SIZE } }
391 { green-bits ${ GDK_GL_GREEN_SIZE } }
392 { blue-bits ${ GDK_GL_BLUE_SIZE } }
393 { alpha-bits ${ GDK_GL_ALPHA_SIZE } }
394 { accum-red-bits ${ GDK_GL_ACCUM_RED_SIZE } }
395 { accum-green-bits ${ GDK_GL_ACCUM_GREEN_SIZE } }
396 { accum-blue-bits ${ GDK_GL_ACCUM_BLUE_SIZE } }
397 { accum-alpha-bits ${ GDK_GL_ACCUM_ALPHA_SIZE } }
398 { depth-bits ${ GDK_GL_DEPTH_SIZE } }
399 { stencil-bits ${ GDK_GL_STENCIL_SIZE } }
400 { aux-buffers ${ GDK_GL_AUX_BUFFERS } }
401 { sample-buffers ${ GDK_GL_SAMPLE_BUFFERS } }
402 { samples ${ GDK_GL_SAMPLES } }
405 M: gtk-ui-backend (make-pixel-format)
406 nip >gl-config-attribs-int-array gdk_gl_config_new ;
408 M: gtk-ui-backend (free-pixel-format)
409 handle>> g_object_unref ;
411 M: gtk-ui-backend (pixel-format-attribute)
412 [ handle>> ] [ >gl-config-attribs ] bi*
413 { gint } [ gdk_gl_config_get_attrib drop ]
414 with-out-parameters ;
416 M: window-handle select-gl-context ( handle -- )
418 [ gtk_widget_get_gl_window ] [ gtk_widget_get_gl_context ] bi
419 gdk_gl_drawable_make_current drop ;
421 M: window-handle flush-gl-context ( handle -- )
422 window>> gtk_widget_get_gl_window
423 gdk_gl_drawable_swap_buffers ;
427 : configure-gl ( world -- )
429 [ handle>> window>> ] [ handle>> ] bi*
430 f t GDK_GL_RGBA_TYPE gtk_widget_set_gl_capability drop
431 ] with-world-pixel-format ;
433 : auto-position ( win loc -- )
435 drop dup window topmost-window =
436 GTK_WIN_POS_CENTER GTK_WIN_POS_NONE ?
437 gtk_window_set_position
438 ] [ first2 gtk_window_move ] if ;
440 M:: gtk-ui-backend (open-window) ( world -- )
441 GTK_WINDOW_TOPLEVEL gtk_window_new :> win
442 gtk_im_multicontext_new :> im
444 win im <window-handle> world handle<<
446 world win register-window
448 win world [ window-loc>> auto-position ]
449 [ dim>> first2 gtk_window_set_default_size ] 2bi
451 win "factor" "Factor" [ utf8 string>alien ] bi@
452 gtk_window_set_wmclass
456 win gtk_widget_realize
457 win world window-controls>> configure-window-controls
460 win connect-user-input-signals
461 win connect-win-state-signals
463 win gtk_widget_show_all ;
465 M: gtk-ui-backend (close-window) ( handle -- )
466 window>> [ gtk_widget_destroy ] [ unregister-window ] bi
467 event-loop? [ gtk_main_quit ] unless ;
469 M: gtk-ui-backend set-title
470 swap [ handle>> window>> ] [ utf8 string>alien ] bi*
471 gtk_window_set_title ;
473 M: gtk-ui-backend (set-fullscreen)
474 [ handle>> ] dip [ >>fullscreen? ] keep
476 [ gtk_window_fullscreen ]
477 [ gtk_window_unfullscreen ] if ;
479 M: gtk-ui-backend (fullscreen?)
480 handle>> fullscreen?>> ;
482 M: gtk-ui-backend raise-window*
483 handle>> window>> gtk_window_present ;
485 : set-cursor ( win cursor -- )
487 [ gtk_widget_get_window ] dip
488 gdk_cursor_new &gdk_cursor_unref
489 gdk_window_set_cursor
492 M: gtk-ui-backend (grab-input)
494 [ gtk_grab_add ] [ GDK_BLANK_CURSOR set-cursor ] bi ;
496 M: gtk-ui-backend (ungrab-input)
498 [ gtk_grab_remove ] [ GDK_LEFT_PTR set-cursor ] bi ;
502 M: gtk-ui-backend beep
505 M:: gtk-ui-backend system-alert ( caption text -- )
507 f GTK_DIALOG_MODAL GTK_MESSAGE_WARNING GTK_BUTTONS_OK
508 caption utf8 string>alien f
509 gtk_message_dialog_new >k_widget_destroy
511 text utf8 string>alien f
512 gtk_message_dialog_format_secondary_text
513 ] [ gtk_dialog_run drop ] bi
516 M: gtk-ui-backend (with-ui)
518 0 gint <ref> f void* <ref> gtk_init
519 0 gint <ref> f void* <ref> gtk_gl_init
524 [ [ gtk_main ] with-timer ] with-event-loop
529 gtk-ui-backend ui-backend set-global
531 { "ui.backend.gtk" "io.backend.unix" }
532 "ui.backend.gtk.io.unix" require-when
534 { "ui.backend.gtk" "ui.gadgets.editors" }
535 "ui.backend.gtk.input-methods.editors" require-when
537 [ "DISPLAY" os-env "ui.tools" "listener" ? ] main-vocab-hook set-global