! Copyright (C) 2010, 2011 Anton Gorenko, Philipp Bruschweiler. ! See http://factorcode.org/license.txt for BSD license. USING: accessors alien.accessors alien.c-types alien.data alien.strings arrays assocs classes.struct command-line continuations destructors environment gdk.ffi gdk.gl.ffi gdk.pixbuf.ffi glib.ffi gobject-introspection.standard-types gobject.ffi gtk.ffi gtk.gl.ffi io io.encodings.binary io.encodings.utf8 io.files kernel libc literals locals math math.bitwise math.order math.vectors namespaces sequences strings system threads ui ui.backend ui.backend.gtk.input-methods ui.backend.gtk.io ui.clipboards ui.event-loop ui.gadgets ui.gadgets.private ui.gadgets.worlds ui.gestures ui.pixel-formats ui.pixel-formats.private ui.private vocabs.loader combinators ; IN: ui.backend.gtk SINGLETON: gtk-ui-backend TUPLE: handle ; TUPLE: window-handle < handle window fullscreen? im-context ; : ( window im-context -- window-handle ) window-handle new swap >>im-context swap >>window ; : connect-signal-with-data ( object signal-name callback data -- ) [ utf8 string>alien ] 2dip g_signal_connect drop ; : connect-signal ( object signal-name callback -- ) f connect-signal-with-data ; ! Clipboards TUPLE: gtk-clipboard handle ; C: gtk-clipboard M: gtk-clipboard clipboard-contents [ handle>> gtk_clipboard_wait_for_text [ &g_free utf8 alien>string ] [ f ] if* ] with-destructors ; : save-global-clipboard ( -- ) clipboard get-global handle>> gtk_clipboard_store ; M: gtk-clipboard set-clipboard-contents swap [ handle>> ] [ utf8 string>alien ] bi* -1 gtk_clipboard_set_text save-global-clipboard ; : init-clipboard ( -- ) selection "PRIMARY" clipboard "CLIPBOARD" [ utf8 string>alien gdk_atom_intern_static_string gtk_clipboard_get swap set-global ] 2bi@ ; ! Timer SYMBOL: next-fire-time : set-timeout*-value ( alien value -- ) swap 0 set-alien-signed-4 ; inline : timer-prepare ( source timeout* -- ? ) nip next-fire-time get-global nano-count [-] [ 1,000,000 /i set-timeout*-value ] keep 0 = ; : timer-check ( source -- ? ) drop next-fire-time get-global nano-count [-] 0 = ; : timer-dispatch ( source callback user_data -- ? ) 3drop sleep-time [ 1,000,000,000 ] unless* nano-count + next-fire-time set-global yield t ; : ( -- timer-funcs ) GSourceFuncs malloc-struct [ timer-prepare ] GSourceFuncsPrepareFunc >>prepare [ timer-check ] GSourceFuncsCheckFunc >>check [ timer-dispatch ] GSourceFuncsDispatchFunc >>dispatch ; :: with-timer ( quot -- ) nano-count next-fire-time set-global &free GSource heap-size g_source_new &g_source_unref :> source source f g_source_attach drop [ quot call( -- ) ] [ source g_source_destroy ] [ ] cleanup ; ! User input CONSTANT: events-mask flags{ GDK_POINTER_MOTION_MASK GDK_POINTER_MOTION_HINT_MASK GDK_ENTER_NOTIFY_MASK GDK_LEAVE_NOTIFY_MASK GDK_BUTTON_PRESS_MASK GDK_BUTTON_RELEASE_MASK GDK_KEY_PRESS_MASK GDK_KEY_RELEASE_MASK GDK_FOCUS_CHANGE_MASK } CONSTANT: modifiers { { S+ $ GDK_SHIFT_MASK } { C+ $ GDK_CONTROL_MASK } { A+ $ GDK_MOD1_MASK } } CONSTANT: action-key-codes H{ { $ GDK_KEY_BackSpace "BACKSPACE" } { $ GDK_KEY_Tab "TAB" } { $ GDK_KEY_ISO_Left_Tab "TAB" } { $ GDK_KEY_Return "RET" } { $ GDK_KEY_KP_Enter "ENTER" } { $ GDK_KEY_Escape "ESC" } { $ GDK_KEY_Delete "DELETE" } { $ GDK_KEY_Home "HOME" } { $ GDK_KEY_Left "LEFT" } { $ GDK_KEY_Up "UP" } { $ GDK_KEY_Right "RIGHT" } { $ GDK_KEY_Down "DOWN" } { $ GDK_KEY_Page_Up "PAGE_UP" } { $ GDK_KEY_Page_Down "PAGE_DOWN" } { $ GDK_KEY_End "END" } { $ GDK_KEY_Begin "BEGIN" } { $ GDK_KEY_F1 "F1" } { $ GDK_KEY_F2 "F2" } { $ GDK_KEY_F3 "F3" } { $ GDK_KEY_F4 "F4" } { $ GDK_KEY_F5 "F5" } { $ GDK_KEY_F6 "F6" } { $ GDK_KEY_F7 "F7" } { $ GDK_KEY_F8 "F8" } { $ GDK_KEY_F9 "F9" } { $ GDK_KEY_F10 "F10" } { $ GDK_KEY_F11 "F11" } { $ GDK_KEY_F12 "F12" } } : event-modifiers ( event -- seq ) state>> modifiers modifier ; : event-loc ( event -- loc ) [ x>> ] [ y>> ] bi [ >fixnum ] bi@ 2array ; : event-dim ( event -- dim ) [ width>> ] [ height>> ] bi 2array ; : scroll-direction ( event -- pair ) direction>> { { $ GDK_SCROLL_UP { 0 -1 } } { $ GDK_SCROLL_DOWN { 0 1 } } { $ GDK_SCROLL_LEFT { -1 0 } } { $ GDK_SCROLL_RIGHT { 1 0 } } } at ; : on-motion ( win event user-data -- ? ) drop swap [ event-loc ] dip window move-hand fire-motion t ; : on-leave ( win event user-data -- ? ) 3drop forget-rollover t ; :: on-button-press ( win event user-data -- ? ) win window :> world event type>> GDK_BUTTON_PRESS = [ event button>> { { 8 [ ] } { 9 [ ] } [ event event-modifiers swap event event-loc world send-button-down ] } case ] when t ; :: on-button-release ( win event user-data -- ? ) win window :> world event type>> GDK_BUTTON_RELEASE = [ event button>> { { 8 [ world left-action send-action ] } { 9 [ world right-action send-action ] } [ event event-modifiers swap event event-loc world send-button-up ] } case ] when t ; : on-scroll ( win event user-data -- ? ) drop swap [ [ scroll-direction ] [ event-loc ] bi ] dip window send-scroll t ; : key-sym ( event -- sym/f action? ) keyval>> dup action-key-codes at [ t ] [ gdk_keyval_to_unicode [ f ] [ 1string ] if-zero f ] ?if ; : key-event>gesture ( event -- mods sym/f action? ) [ event-modifiers ] [ key-sym ] bi ; : on-key-press ( win event user-data -- ? ) drop swap [ key-event>gesture ] [ window ] bi* propagate-key-gesture t ; : on-key-release ( win event user-data -- ? ) drop swap [ key-event>gesture ] [ window ] bi* propagate-key-gesture t ; : on-focus-in ( win event user-data -- ? ) 2drop window focus-world t ; : on-focus-out ( win event user-data -- ? ) 2drop window unfocus-world t ; ! This word gets replaced when deploying. See 'Vocabulary icons' ! in the docs and tools.deploy.shaker.gtk-icon : get-icon-data ( -- byte-array/f ) [ "resource:misc/icons/Factor_48x48.png" binary file-contents ] [ drop f ] recover ; : load-icon ( -- ) get-icon-data [ [ data>GInputStream &g_object_unref GInputStream>GdkPixbuf gtk_window_set_default_icon ] with-destructors ] when* ; :: connect-user-input-signals ( win -- ) win events-mask gtk_widget_add_events win "motion-notify-event" [ on-motion yield ] GtkWidget:motion-notify-event connect-signal win "leave-notify-event" [ on-leave yield ] GtkWidget:leave-notify-event connect-signal win "button-press-event" [ on-button-press yield ] GtkWidget:button-press-event connect-signal win "button-release-event" [ on-button-release yield ] GtkWidget:button-release-event connect-signal win "scroll-event" [ on-scroll yield ] GtkWidget:scroll-event connect-signal win "key-press-event" [ on-key-press yield ] GtkWidget:key-press-event connect-signal win "key-release-event" [ on-key-release yield ] GtkWidget:key-release-event connect-signal win "focus-in-event" [ on-focus-in yield ] GtkWidget:focus-in-event connect-signal win "focus-out-event" [ on-focus-out yield ] GtkWidget:focus-out-event connect-signal ; ! Window state events : on-expose ( win event user-data -- ? ) 2drop window relayout t ; : on-configure ( win event user-data -- ? ) drop [ window ] [ GdkEventConfigure memory>struct ] bi* [ event-loc >>window-loc ] [ event-dim >>dim ] bi relayout-1 f ; : on-delete ( win event user-data -- ? ) 2drop window ungraft t ; :: connect-win-state-signals ( win -- ) win "expose-event" [ on-expose yield ] GtkWidget:expose-event connect-signal win "configure-event" [ on-configure yield ] GtkWidget:configure-event connect-signal win "delete-event" [ on-delete yield ] GtkWidget:delete-event connect-signal ; ! Input methods : on-retrieve-surrounding ( im-context win -- ? ) window world-focus dup support-input-methods? [ cursor-surrounding [ utf8 string>alien -1 ] dip gtk_im_context_set_surrounding t ] [ 2drop f ] if ; : on-delete-surrounding ( im-context offset n win -- ? ) window world-focus dup support-input-methods? [ delete-cursor-surrounding t ] [ 3drop f ] if nip ; : on-commit ( im-context str win -- ) [ drop ] [ utf8 alien>string ] [ window ] tri* user-input ; : gadget-cursor-location ( gadget -- rectangle ) [ screen-loc ] [ cursor-loc&dim ] bi [ v+ ] dip [ first2 [ >fixnum ] bi@ ] bi@ cairo_rectangle_int_t ; : update-cursor-location ( im-context gadget -- ) gadget-cursor-location gtk_im_context_set_cursor_location ; ! has to be called before the window signal handler :: im-on-key-event ( win event im-context -- ? ) win window world-focus :> gadget gadget support-input-methods? [ im-context gadget update-cursor-location im-context event gtk_im_context_filter_keypress ] [ im-context gtk_im_context_reset f ] if ; : im-on-focus-in ( win event im-context -- ? ) 2nip [ gtk_im_context_focus_in ] [ gtk_im_context_reset ] bi f ; : im-on-focus-out ( win event im-context -- ? ) 2nip [ gtk_im_context_focus_out ] [ gtk_im_context_reset ] bi f ; : im-on-destroy ( win im-context -- ) nip [ f gtk_im_context_set_client_window ] ! weird GLib-GObject-WARNING message appears after calling this code ! [ g_object_unref ] bi ; [ drop ] bi ; :: configure-im ( win im -- ) im win gtk_widget_get_window gtk_im_context_set_client_window im f gtk_im_context_set_use_preedit im "commit" [ on-commit yield ] GtkIMContext:commit win connect-signal-with-data im "retrieve-surrounding" [ on-retrieve-surrounding yield ] GtkIMContext:retrieve-surrounding win connect-signal-with-data im "delete-surrounding" [ on-delete-surrounding yield ] GtkIMContext:delete-surrounding win connect-signal-with-data win "key-press-event" [ im-on-key-event yield ] GtkWidget:key-press-event im connect-signal-with-data win "key-release-event" [ im-on-key-event yield ] GtkWidget:key-release-event im connect-signal-with-data win "focus-in-event" [ im-on-focus-in yield ] GtkWidget:focus-out-event im connect-signal-with-data win "focus-out-event" [ im-on-focus-out yield ] GtkWidget:focus-out-event im connect-signal-with-data win "destroy" [ im-on-destroy yield ] GtkObject:destroy im connect-signal-with-data ; ! Window controls CONSTANT: window-controls>decor-flags H{ { close-button 0 } { minimize-button $ GDK_DECOR_MINIMIZE } { maximize-button $ GDK_DECOR_MAXIMIZE } { resize-handles $ GDK_DECOR_RESIZEH } { small-title-bar $ GDK_DECOR_TITLE } { normal-title-bar $ GDK_DECOR_TITLE } { textured-background 0 } { floating-window 0 } } CONSTANT: window-controls>func-flags H{ { close-button $ GDK_FUNC_CLOSE } { minimize-button $ GDK_FUNC_MINIMIZE } { maximize-button $ GDK_FUNC_MAXIMIZE } { resize-handles $ GDK_FUNC_RESIZE } { small-title-bar 0 } { normal-title-bar 0 } { textured-background 0 } { floating-window 0 } } : set-window-hint ( win controls -- ) { { [ floating-window over member-eq? ] [ drop GDK_WINDOW_TYPE_HINT_DIALOG ] } { [ small-title-bar over member-eq? ] [ drop GDK_WINDOW_TYPE_HINT_UTILITY ] } [ drop GDK_WINDOW_TYPE_HINT_NORMAL ] } cond gtk_window_set_type_hint ; : configure-window-controls ( win controls -- ) [ set-window-hint ] [ [ gtk_widget_get_window ] dip window-controls>decor-flags symbols>flags GDK_DECOR_BORDER bitor gdk_window_set_decorations ] [ [ gtk_widget_get_window ] dip window-controls>func-flags symbols>flags GDK_FUNC_MOVE bitor gdk_window_set_functions ] 2tri ; ! OpenGL and Pixel formats PIXEL-FORMAT-ATTRIBUTE-TABLE: gl-config-attribs ${ GDK_GL_USE_GL GDK_GL_RGBA } H{ { double-buffered ${ GDK_GL_DOUBLEBUFFER } } { stereo ${ GDK_GL_STEREO } } ! { offscreen ${ GDK_GL_DRAWABLE_TYPE 2 } } ! { fullscreen ${ GDK_GL_DRAWABLE_TYPE 1 } } ! { windowed ${ GDK_GL_DRAWABLE_TYPE 1 } } { color-bits ${ GDK_GL_BUFFER_SIZE } } { red-bits ${ GDK_GL_RED_SIZE } } { green-bits ${ GDK_GL_GREEN_SIZE } } { blue-bits ${ GDK_GL_BLUE_SIZE } } { alpha-bits ${ GDK_GL_ALPHA_SIZE } } { accum-red-bits ${ GDK_GL_ACCUM_RED_SIZE } } { accum-green-bits ${ GDK_GL_ACCUM_GREEN_SIZE } } { accum-blue-bits ${ GDK_GL_ACCUM_BLUE_SIZE } } { accum-alpha-bits ${ GDK_GL_ACCUM_ALPHA_SIZE } } { depth-bits ${ GDK_GL_DEPTH_SIZE } } { stencil-bits ${ GDK_GL_STENCIL_SIZE } } { aux-buffers ${ GDK_GL_AUX_BUFFERS } } { sample-buffers ${ GDK_GL_SAMPLE_BUFFERS } } { samples ${ GDK_GL_SAMPLES } } } M: gtk-ui-backend (make-pixel-format) nip >gl-config-attribs-int-array gdk_gl_config_new ; M: gtk-ui-backend (free-pixel-format) handle>> g_object_unref ; M: gtk-ui-backend (pixel-format-attribute) [ handle>> ] [ >gl-config-attribs ] bi* { gint } [ gdk_gl_config_get_attrib drop ] with-out-parameters ; M: window-handle select-gl-context ( handle -- ) window>> [ gtk_widget_get_gl_window ] [ gtk_widget_get_gl_context ] bi gdk_gl_drawable_make_current drop ; M: window-handle flush-gl-context ( handle -- ) window>> gtk_widget_get_gl_window gdk_gl_drawable_swap_buffers ; ! Window : configure-gl ( world -- ) [ [ handle>> window>> ] [ handle>> ] bi* f t GDK_GL_RGBA_TYPE gtk_widget_set_gl_capability drop ] with-world-pixel-format ; : auto-position ( win loc -- ) dup { 0 0 } = [ drop dup window topmost-window = GTK_WIN_POS_CENTER GTK_WIN_POS_NONE ? gtk_window_set_position ] [ first2 gtk_window_move ] if ; M:: gtk-ui-backend (open-window) ( world -- ) GTK_WINDOW_TOPLEVEL gtk_window_new :> win gtk_im_multicontext_new :> im win im world handle<< world win register-window win world [ window-loc>> auto-position ] [ dim>> first2 gtk_window_set_default_size ] 2bi win "factor" "Factor" [ utf8 string>alien ] bi@ gtk_window_set_wmclass world configure-gl win im configure-im win connect-user-input-signals win connect-win-state-signals win gtk_widget_realize win world window-controls>> configure-window-controls win gtk_widget_show_all ; M: gtk-ui-backend (close-window) ( handle -- ) window>> [ gtk_widget_destroy ] [ unregister-window ] bi event-loop? [ gtk_main_quit ] unless ; M: gtk-ui-backend resize-window [ handle>> window>> ] [ first2 ] bi* gtk_window_resize ; M: gtk-ui-backend set-title swap [ handle>> window>> ] [ utf8 string>alien ] bi* gtk_window_set_title ; M: gtk-ui-backend (set-fullscreen) [ handle>> ] dip [ >>fullscreen? ] keep [ window>> ] dip [ gtk_window_fullscreen ] [ gtk_window_unfullscreen ] if ; M: gtk-ui-backend (fullscreen?) handle>> fullscreen?>> ; M: gtk-ui-backend raise-window* handle>> window>> gtk_window_present ; : set-cursor ( win cursor -- ) [ [ gtk_widget_get_window ] dip gdk_cursor_new &gdk_cursor_unref gdk_window_set_cursor ] with-destructors ; M: gtk-ui-backend (grab-input) window>> [ gtk_grab_add ] [ GDK_BLANK_CURSOR set-cursor ] bi ; M: gtk-ui-backend (ungrab-input) window>> [ gtk_grab_remove ] [ GDK_LEFT_PTR set-cursor ] bi ; ! Misc. M: gtk-ui-backend beep gdk_beep ; M:: gtk-ui-backend system-alert ( caption text -- ) [ f GTK_DIALOG_MODAL GTK_MESSAGE_WARNING GTK_BUTTONS_OK caption utf8 string>alien f gtk_message_dialog_new >k_widget_destroy [ text utf8 string>alien f gtk_message_dialog_format_secondary_text ] [ gtk_dialog_run drop ] bi ] with-destructors ; M: gtk-ui-backend (with-ui) [ 0 gint f void* gtk_init 0 gint f void* gtk_gl_init load-icon init-clipboard start-ui [ [ [ gtk_main ] with-timer ] with-event-loop ] with-destructors ] ui-running ; os unix? os macosx? not and [ gtk-ui-backend ui-backend set-global ] when { "ui.backend.gtk" "io.backend.unix" } "ui.backend.gtk.io.unix" require-when { "ui.backend.gtk" "ui.gadgets.editors" } "ui.backend.gtk.input-methods.editors" require-when M: gtk-ui-backend ui-backend-available? "DISPLAY" os-env >boolean ;