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
17 SINGLETON: gtk-ui-backend
20 TUPLE: window-handle < handle window fullscreen? im-context ;
22 : <window-handle> ( window im-context -- window-handle )
27 TUPLE: gtk-clipboard handle ;
29 C: <gtk-clipboard> gtk-clipboard
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 ] } }
53 M: gtk-ui-backend (make-pixel-format)
54 nip >gl-config-attribs-int-array gdk_gl_config_new ;
56 M: gtk-ui-backend (free-pixel-format)
57 handle>> g_object_unref ;
59 M: gtk-ui-backend (pixel-format-attribute)
60 [ handle>> ] [ >gl-config-attribs ] bi*
61 { int } [ gdk_gl_config_get_attrib drop ] [ ]
66 GDK_POINTER_MOTION_MASK
67 GDK_POINTER_MOTION_HINT_MASK
71 GDK_BUTTON_RELEASE_MASK
79 { S+ $[ GDK_SHIFT_MASK enum>number ] }
80 { C+ $[ GDK_CONTROL_MASK enum>number ] }
81 { A+ $[ GDK_MOD1_MASK enum>number ] }
84 CONSTANT: action-key-codes
86 ${ GDK_BackSpace "BACKSPACE" }
89 ${ GDK_KP_Enter "ENTER" }
91 ${ GDK_Delete "DELETE" }
95 ${ GDK_Right "RIGHT" }
97 ${ GDK_Page_Up "PAGE_UP" }
98 ${ GDK_Page_Down "PAGE_DOWN" }
100 ${ GDK_Begin "BEGIN" }
115 : event-modifiers ( event -- seq )
116 state>> modifiers modifier ;
118 : event-loc ( event -- loc )
119 [ x>> ] [ y>> ] bi [ >fixnum ] bi@ 2array ;
121 : event-dim ( event -- dim )
122 [ width>> ] [ height>> ] bi 2array ;
124 : scroll-direction ( event -- pair )
126 ${ GDK_SCROLL_UP { 0 -1 } }
127 ${ GDK_SCROLL_DOWN { 0 1 } }
128 ${ GDK_SCROLL_LEFT { -1 0 } }
129 ${ GDK_SCROLL_RIGHT { 1 0 } }
132 : mouse-event>gesture ( event -- modifiers button loc )
133 [ event-modifiers ] [ button>> ] [ event-loc ] tri ;
135 : gadget-location ( gadget -- loc )
136 [ loc>> ] [ parent>> [ gadget-location v+ ] when* ] bi ;
138 : focusable-editor ( world -- editor/f )
139 focusable-child dup editor? [ drop f ] unless ;
141 : get-cursor-location ( editor -- GdkRectangle )
142 [ [ gadget-location ] [ caret-loc ] bi v+ first2 ]
143 [ line-height ] bi 0 swap GdkRectangle <struct-boa> ;
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
151 : on-motion ( sender event user-data -- result )
153 [ GdkEventMotion memory>struct event-loc ] dip window
154 move-hand fire-motion t ;
156 : on-enter ( sender event user-data -- result )
159 : on-leave ( sender event user-data -- result )
160 3drop forget-rollover t ;
162 : on-button-press ( sender event user-data -- result )
164 GdkEventButton memory>struct
165 mouse-event>gesture [ <button-down> ] dip
166 ] dip window send-button-down t ;
168 : on-button-release ( sender event user-data -- result )
170 GdkEventButton memory>struct
171 mouse-event>gesture [ <button-up> ] dip
172 ] dip window send-button-up t ;
174 : on-scroll ( sender event user-data -- result )
176 GdkEventScroll memory>struct
177 [ scroll-direction ] [ event-loc ] bi
178 ] dip window send-scroll t ;
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 ;
184 : key-event>gesture ( event -- mods sym/f action? )
185 GdkEventKey memory>struct
186 [ event-modifiers ] [ key-sym ] bi ;
188 : handle-key-gesture ( key-gesture world -- )
189 [ propagate-key-gesture ]
190 [ update-im-cursor-location ] bi ;
192 : on-key-press ( sender event user-data -- result )
193 drop swap [ key-event>gesture <key-down> ] [ window ] bi*
194 handle-key-gesture t ;
196 : on-key-release ( sender event user-data -- result )
197 drop swap [ key-event>gesture <key-up> ] [ window ] bi*
198 handle-key-gesture t ;
200 : on-focus-in ( sender event user-data -- result )
201 2drop window focus-world t ;
203 : on-focus-out ( sender event user-data -- result )
204 2drop window unfocus-world t ;
206 : on-expose ( sender event user-data -- result )
207 2drop window relayout t ;
209 : on-configure ( sender event user-data -- result )
210 drop [ window ] dip GdkEventConfigure memory>struct
211 [ event-loc >>window-loc ] [ event-dim >>dim ] bi
214 : on-delete ( sender event user-data -- result )
215 2drop window ungraft t ;
217 : init-clipboard ( -- )
219 clipboard "CLIPBOARD"
221 utf8 string>alien gdk_atom_intern_static_string
222 gtk_clipboard_get <gtk-clipboard> swap set-global
225 : io-source-prepare ( source timeout -- result )
228 : io-source-check ( source -- result )
229 poll_fds>> 0 g_slist_nth_data GPollFD memory>struct
232 : io-source-dispatch ( source callback user_data -- result )
234 0 mx get wait-for-events
237 CONSTANT: poll-fd-events
247 : create-poll-fd ( -- poll-fd )
248 GPollFD malloc-struct &free
250 poll-fd-events [ enum>number ] [ bitor ] map-reduce >>events ;
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 ;
263 : set-timeout*-value ( alien value -- )
264 swap 0 set-alien-signed-4 ; inline
266 : timeout-prepare ( source timeout* -- result )
267 nip next-timeout get-global nano-count [-]
268 [ 1,000,000 /i set-timeout*-value ] keep 0 = ;
270 : timeout-check ( source -- result )
271 drop next-timeout get-global nano-count [-] 0 = ;
273 : timeout-dispatch ( source callback user_data -- result )
274 3drop sleep-time [ 1,000,000,000 ] unless* nano-count +
275 next-timeout set-global
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 ;
287 M: gtk-ui-backend (with-ui)
293 f io-thread-running? set-global
301 : connect-signal-with-data ( object signal-name callback data -- )
302 [ utf8 string>alien ] 2dip f 0 g_signal_connect_data drop ;
304 : connect-signal ( object signal-name callback -- )
305 f connect-signal-with-data ;
307 :: connect-signals ( win -- )
308 win events-mask [ enum>number ] [ bitor ] map-reduce
309 gtk_widget_add_events
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 ;
338 : on-retrieve-surrounding ( im-context user-data -- ? )
339 window focusable-editor [| im-context editor |
340 editor editor-caret first2 :> ( x y )
342 y editor editor-line utf8 string>alien
344 gtk_im_context_set_surrounding t
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
357 : on-commit ( sender str user_data -- )
358 [ drop ] [ utf8 alien>string ] [ window ] tri*
360 [ [ f swap key-down boa ] dip handle-key-gesture ] 2bi ;
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 ;
366 : im-on-focus-in ( sender event user-data -- result )
368 [ handle>> im-context>> gtk_im_context_focus_in ]
369 [ update-im-cursor-location ] bi f ;
371 : im-on-focus-out ( sender event user-data -- result )
373 [ handle>> im-context>> gtk_im_context_focus_out ]
374 [ update-im-cursor-location ] bi f ;
376 : im-on-motion ( sender event user-data -- result )
377 2drop window update-im-cursor-location f ;
379 : im-on-destroy ( sender user-data -- result )
380 nip [ f gtk_im_context_set_client_window ]
381 [ g_object_unref ] bi f ;
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
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
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 ;
411 CONSTANT: window-controls>decor-flags
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 }
422 CONSTANT: window-controls>func-flags
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 }
433 : configure-window-controls ( win controls -- )
435 small-title-bar swap member-eq?
436 GDK_WINDOW_TYPE_HINT_UTILITY GDK_WINDOW_TYPE_HINT_NORMAL ?
437 gtk_window_set_type_hint
439 [ gtk_widget_get_window ] dip
440 window-controls>decor-flags symbols>flags
441 GDK_DECOR_BORDER enum>number bitor gdk_window_set_decorations
443 [ gtk_widget_get_window ] dip
444 window-controls>func-flags symbols>flags
445 GDK_FUNC_MOVE enum>number bitor gdk_window_set_functions
448 : setup-gl ( world -- ? )
450 [ handle>> window>> ] [ handle>> ] bi*
451 f t GDK_GL_RGBA_TYPE enum>number gtk_widget_set_gl_capability
452 ] with-world-pixel-format ;
454 : auto-position ( win loc -- )
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 ;
461 M:: gtk-ui-backend (open-window) ( world -- )
462 GTK_WINDOW_TOPLEVEL gtk_window_new :> win
463 gtk_im_multicontext_new :> im
465 win im <window-handle> world handle<<
467 world win register-window
469 win world [ window-loc>> auto-position ]
470 [ dim>> first2 gtk_window_set_default_size ] 2bi
474 win gtk_widget_realize
475 win world window-controls>> configure-window-controls
480 win gtk_widget_show_all ;
482 M: gtk-ui-backend (close-window) ( handle -- )
483 window>> [ gtk_widget_destroy ] [ unregister-window ] bi
484 event-loop? [ gtk_main_quit ] unless ;
486 M: gtk-ui-backend set-title
487 swap [ handle>> window>> ] [ utf8 string>alien ] bi*
488 gtk_window_set_title ;
490 M: gtk-ui-backend (set-fullscreen)
492 [ handle>> ] dip [ >>fullscreen? ] keep
494 [ gtk_window_fullscreen ]
495 [ gtk_window_unfullscreen ] if
496 ] [ drop update-im-cursor-location ] 2bi ;
498 M: gtk-ui-backend (fullscreen?)
499 handle>> fullscreen?>> ;
501 M: gtk-ui-backend raise-window*
502 handle>> window>> gtk_window_present ;
504 : set-cursor ( win cursor -- )
506 [ gtk_widget_get_window ] dip
507 gdk_cursor_new &gdk_cursor_unref
508 gdk_window_set_cursor
511 M: gtk-ui-backend (grab-input)
513 [ gtk_grab_add ] [ GDK_BLANK_CURSOR set-cursor ] bi ;
515 M: gtk-ui-backend (ungrab-input)
517 [ gtk_grab_remove ] [ GDK_LEFT_PTR set-cursor ] bi ;
519 M: window-handle select-gl-context ( handle -- )
521 [ gtk_widget_get_gl_window ] [ gtk_widget_get_gl_context ] bi
522 gdk_gl_drawable_make_current drop ;
524 M: window-handle flush-gl-context ( handle -- )
525 window>> gtk_widget_get_gl_window
526 gdk_gl_drawable_swap_buffers ;
528 M: gtk-ui-backend beep
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 ;
538 M: gtk-clipboard clipboard-contents
540 handle>> gtk_clipboard_wait_for_text
541 [ &g_free utf8 alien>string ] [ f ] if*
544 M: gtk-clipboard set-clipboard-contents
545 swap [ handle>> ] [ utf8 string>alien ] bi*
546 -1 gtk_clipboard_set_text ;
548 gtk-ui-backend ui-backend set-global
550 [ "ui.tools" ] main-vocab-hook set-global