@c -----------------------------------------------------------------------------
@c File     : gtk-application.texi
@c License  : GNU General Public License (GPL)
@c Language : English
@c Author   : Dieter Kaiser
@c Date     : 31.07.2012
@c Revision : 22.04.2014
@c
@c Copyright (C) 2012, 2013, 2014 by Dieter Kaiser
@c -----------------------------------------------------------------------------

@menu
* Application Class::
* Application Window::
* Simple Application::
@end menu

@c -----------------------------------------------------------------------------
@node Application Class, Application Window, Top, Top
@section Application Class

@findex GtkApplication
@findex gtk-application
@findex application, gtk-application

@findex GtkApplicationWindow
@findex gtk-application-window
@findex application-window, gtk-application-window

@findex GApplication
@findex g-application
@findex applicaton, g-application

@findex g-action-group-activate-action
@findex action-group-activate-action, g-action-group-activate-action

@xurl{gtk-application} is a class that handles many important aspects of a GTK+
application in a convenient fashion, without enforcing a one-size-fits-all
application model.

Currently, @xurl{gtk-application} handles GTK+ initialization, application
uniqueness, session management, provides some basic scriptability and desktop
shell integration by exporting actions and menus and manages a list of toplevel
windows whose life-cycle is automatically tied to the life-cycle of your
application.

While @xurl{gtk-application} works fine with plain @xurl{gtk-window} widgets, it
is recommended to use it together with @xurl{gtk-application-window}.

When GDK threads are enabled, @xurl{gtk-application} will acquire the GDK lock
when invoking actions that arrive from other processes.  The GDK lock is not
touched for local action invocations.  In order to have actions invoked in a
predictable context it is therefore recommended that the GDK lock be held while
invoking actions locally with the function
@xurl{g-action-group-activate-action}.  The same applies to actions associated
with @xurl{gtk-application-window} and to the 'activate' and 'open'
@xurl{g-application} methods.

@findex GMenuModel
@findex g-menu-model
@findex menu-model, g-menu-model

@findex GtkBuilder
@findex gtk-builder
@findex builder, gtk-builder

@findex GActionMap
@findex g-action-map
@findex action-map, g-action-map

@findex gtk-application-app-menu
@findex application-app-menu, gtk-application-app-menu

@findex gtk-application-menubar
@findex application-menubar, gtk-application-menubar

To set an application menu for a @xurl{gtk-application}, use the generic
function @xurl{gtk-application-app-menu}.  The @xurl{g-menu-model} that this
function expects is usually constructed using @xurl{gtk-builder}, as seen in
@ref{example-application}.  To specify a menubar that will be shown by
@xurl{gtk-application-window} widgets, use the generic function
@xurl{gtk-application-menubar}.  Use the base @xurl{g-action-map} interface to
add actions, to respond to the user selecting these menu items.

GTK+ displays these menus as expected, depending on the platform the application
is running on.

@xurl{gtk-application} optionally registers with a session manager of the users
session, if you set the @xurl{register-session} property, and offers various
functionality related to the session life-cycle.

@findex gtk-application-inhibit
@findex application-inhibit, gtk-application-inhibit

An application can block various ways to end the session with the function
@xurl{gtk-application-inhibit}.  Typical use cases for this kind of inhibiting
are long-running, uninterruptible operations, such as burning a CD or performing
a disk backup.  The session manager may not honor the inhibitor, but it can be
expected to inform the user about the negative consequences of ending the
session while inhibitors are present.

@c -----------------------------------------------------------------------------
@node Application Window, Simple Application, Application Class, Top
@section Application Window

@findex GtkApplicationWindow
@findex gtk-application-window
@findex application-window, gtk-application-window

@findex GActionGroup
@findex g-action-group
@findex action-group, g-action-group

@findex GActionMap
@findex g-action-map
@findex action-map, g-action-map

@findex GMenuModel
@findex g-menu-model
@findex menu-model, g-menu-model

@xurl{gtk-application-window} is a @xurl{gtk-window} subclass that offers some
extra functionality for better integration with @xurl{gtk-application} features.
Notably, it can handle both the application menu as well as the menubar. See the
functions @xurl{gtk-application-app-menu} and @xurl{gtk-application-menubar}.

This class implements the @xurl{g-action-group} and @xurl{g-action-map}
interfaces, to let you add window-specific actions that will be exported by the
associated @xurl{gtk-application}, together with its application-wide actions.
Window-specific actions are prefixed with the "win." prefix and application-wide
actions are prefixed with the "app." prefix.  Actions must be addressed with the
prefixed name when referring to them from a @xurl{g-menu-model}.

Note that widgets that are placed inside a @xurl{gtk-application-window} can
also activate these actions, if they implement the @xurl{gtk-actionable}
interface.

As with @xurl{gtk-application}, the GDK lock will be acquired when processing
actions arriving from other processes and should therefore be held when
activating actions locally if GDK threads are enabled.

The settings @xurl{gtk-shell-shows-app-menu} and @xurl{gtk-shell-shows-menubar}
tell GTK+ whether the desktop environment is showing the application menu and
menubar models outside the application as part of the desktop shell.  For
instance, on OS X, both menus will be displayed remotely; on Windows neither
will be.  gnome-shell (starting with version 3.4) will display the application
menu, but not the menubar.

@findex GtkMenuBar
@findex gtk-menu-bar
@findex menu-bar, gtk-menu-bar

If the desktop environment does not display the menubar, then
@xurl{gtk-application-window} will automatically show a @xurl{gtk-menu-bar} for
it. See the @xurl{gtk-application} docs for some screenshots of how this looks
on different platforms.  This behaviour can be overridden with the
@xurl{show-menubar} property.  If the desktop environment does not display the
application menu, then it will automatically be included in the menubar.

The XML format understood by @xurl{gtk-builder} for @xurl{g-menu-model} consists
of a toplevel @code{<menu>} element, which contains one or more @code{<item>}
elements. Each @code{<item>} element contains @code{<attribute>} and
@code{<link>} elements with a mandatory name attribute.  @code{<link>} elements
have the same content model as @code{<menu>}.

Attribute values can be translated using gettext, like other @xurl{gtk-builder}
content.  @code{<attribute>} elements can be marked for translation with a
translatable = "yes" attribute.  It is also possible to specify message context
and translator comments, using the context and comments attributes. To make use
of this, the @xurl{gtk-builder} must have been given the gettext domain to use.

@c -----------------------------------------------------------------------------
@node Simple Application, , Application Window, Top
@section Simple Application

@ifnotinfo
@float Figure, figure-bloatpad
@caption{Simple Application}
@center @image{figures/bloatpad, 335pt}
@end float
@end ifnotinfo

@float Example, example-application
@caption{Simple GTk+ Application}
@end float
@verbatim
(defclass bloat-pad (gtk-application)
  ()
  (:metaclass gobject-class)
  (:g-type-name . "BloatPad"))

(register-object-type-implementation "BloatPad"
                                     bloat-pad
                                     "GtkApplication"
                                     nil
                                     nil)

(defun new-window (application file)
  (declare (ignore file))
    (let (;; Create the application window
          (window (make-instance 'gtk-application-window
                                 :application application
                                 :title "Bloatpad"
                                 :border-width 12
                                 :default-width 500
                                 :default-height 400))
          (grid (make-instance 'gtk-grid))
          (toolbar (make-instance 'gtk-toolbar)))

      ;; Connect signal "destroy" to the application window
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)
                          (if (zerop gtk::*main-thread-level*)
                              (g-application-quit application))))

      ;; Add action "copy" to the application window
      (let ((action (g-simple-action-new "copy" nil)))
        (g-action-map-add-action window action)
        (g-signal-connect action "activate"
           (lambda (action parameter)
             (declare (ignore action parameter))
             (let ((view (gobject::get-g-object-for-pointer
                           (g-object-get-data window "bloatpad-text"))))
               (gtk-text-buffer-copy-clipboard
                                  (gtk-text-view-buffer view)
                                  (gtk-widget-get-clipboard view
                                                            "CLIPBOARD"))))))

      ;; Add action "paste" to the application window
      (let ((action (g-simple-action-new "paste" nil)))
        (g-action-map-add-action window action)
        (g-signal-connect action "activate"
           (lambda (action parameter)
             (declare (ignore action parameter))
             (let ((view (gobject::get-g-object-for-pointer
                           (g-object-get-data window "bloatpad-text"))))
               (gtk-text-buffer-paste-clipboard
                                       (gtk-text-view-buffer view)
                                       (gtk-widget-get-clipboard view
                                                                 "CLIPBOARD")
                                       :default-editable t)))))

      ;; Add action "fullscreen" to the application window
      (let ((action (g-simple-action-new-stateful
                                               "fullscreen"
                                               nil
                                               (g-variant-new-boolean nil))))
        (g-action-map-add-action window action)
        (g-signal-connect action "activate"
           (lambda (action parameter)
             (declare (ignore parameter))
             (let* ((state (g-action-state action))
                    (value (g-variant-get-boolean state)))
               (g-action-change-state action
                                      (g-variant-new-boolean (not value))))))
        (g-signal-connect action "change-state"
           (lambda (action parameter)
             (if (g-variant-get-boolean parameter)
                 (gtk-window-fullscreen window)
                 (gtk-window-unfullscreen window))
             (g-simple-action-set-state action parameter))))

      ;; Add action "justify" to the application window
      (let ((action (g-simple-action-new-stateful
                                             "justify"
                                             (g-variant-type-new "s")
                                             (g-variant-new-string "left"))))
        (g-action-map-add-action window action)
        (g-signal-connect action "activate"
           (lambda (action parameter)
             (g-action-change-state action parameter)))
        (g-signal-connect action "change-state"
           (lambda (action parameter)
             (let ((view (gobject::get-g-object-for-pointer
                           (g-object-get-data window "bloatpad-text")))
                   (str (g-variant-get-string parameter)))
               (cond ((equal str "left")
                      (setf (gtk-text-view-justification view) :left))
                     ((equal str "center")
                      (setf (gtk-text-view-justification view) :center))
                     (t
                      (setf (gtk-text-view-justification view) :right)))
               (g-simple-action-set-state action parameter)))))

      (let ((button (make-instance 'gtk-toggle-tool-button
                                   :stock-id "gtk-justify-left")))
        (gtk-actionable-set-detailed-action-name button "win.justify::left")
        (gtk-container-add toolbar button))
      (let ((button (make-instance 'gtk-toggle-tool-button
                                   :stock-id "gtk-justify-center")))
        (gtk-actionable-set-detailed-action-name button
                                                 "win.justify::center")
        (gtk-container-add toolbar button))
      (let ((button (make-instance 'gtk-toggle-tool-button
                                   :stock-id "gtk-justify-right")))
        (gtk-actionable-set-detailed-action-name button "win.justify::right")
        (gtk-container-add toolbar button))
      (let ((button (make-instance 'gtk-separator-tool-item
                                   :draw nil)))
        (gtk-tool-item-set-expand button t)
        (gtk-container-add toolbar button))
      (let ((button (make-instance 'gtk-tool-item))
            (box (make-instance 'gtk-box
                                :orientation :horizontal
                                :spacing 6))
            (label (make-instance 'gtk-label
                                  :label "Fullscreen:"))
            (switch (make-instance 'gtk-switch)))
        (setf (gtk-actionable-action-name switch) "win.fullscreen")
        (gtk-container-add box label)
        (gtk-container-add box switch)
        (gtk-container-add button box)
        (gtk-container-add toolbar button))
      (gtk-grid-attach grid toolbar 0 0 1 1)
      (let ((scrolled (make-instance 'gtk-scrolled-window
                                     :hexpand t
                                     :vexpand t))
            (view (make-instance 'gtk-text-view)))
        (g-object-set-data window "bloatpad-text" (pointer view))
        (gtk-container-add scrolled view)
        (gtk-grid-attach grid scrolled 0 1 1 1))
      (gtk-container-add window grid)
      (gtk-widget-show-all window)))

(defun bloat-pad-activate (application)
  ;; Start a main loop and create an application window
  (within-main-loop
    (new-window application nil))
  ;; Wait until the main loop has finished
  (join-gtk-main))

(defun create-about-dialog ()
  (let (;; Create an about dialog
        (dialog (make-instance 'gtk-about-dialog
                               :program-name "Example Dialog"
                               :version "0.00"
                               :copyright "(c) Dieter Kaiser"
                               :website
                               "github.com/crategus/cl-cffi-gtk"
                               :website-label "Project web site"
                               :license "LLGPL"
                               :authors '("Dieter Kaiser")
                               :documenters '("Dieter Kaiser")
                               :artists '("None")
                               :logo-icon-name
                               "applications-development"
                               :wrap-license t)))
    ;; Run the about dialog
    (gtk-dialog-run dialog)
    ;; Destroy the about dialog
    (gtk-widget-destroy dialog)))

(defvar *menu*
  "<interface>
    <menu id='app-menu'>
     <section>
      <item>
       <attribute name='label' translatable='yes'>_New Window</attribute>
       <attribute name='action'>app.new</attribute>
       <attribute name='accel'>&lt;Primary&gt;n</attribute>
      </item>
     </section>
     <section>
      <item>
       <attribute name='label' translatable='yes'>_About Bloatpad</attribute>
       <attribute name='action'>app.about</attribute>
      </item>
     </section>
     <section>
      <item>
       <attribute name='label' translatable='yes'>_Quit</attribute>
       <attribute name='action'>app.quit</attribute>
       <attribute name='accel'>&lt;Primary&gt;q</attribute>
      </item>
     </section>
     </menu>
    <menu id='menubar'>
     <submenu>
      <attribute name='label' translatable='yes'>_Edit</attribute>
      <section>
       <item>
        <attribute name='label' translatable='yes'>_Copy</attribute>
        <attribute name='action'>win.copy</attribute>
        <attribute name='accel'>&lt;Primary&gt;c</attribute>
       </item>
       <item>
        <attribute name='label' translatable='yes'>_Paste</attribute>
        <attribute name='action'>win.paste</attribute>
        <attribute name='accel'>&lt;Primary&gt;v</attribute>
       </item>
      </section>
     </submenu>
     <submenu>
      <attribute name='label' translatable='yes'>_View</attribute>
      <section>
       <item>
        <attribute name='label' translatable='yes'>_Fullscreen</attribute>
        <attribute name='action'>win.fullscreen</attribute>
        <attribute name='accel'>F11</attribute>
       </item>
      </section>
     </submenu>
    </menu>
   </interface>")

(defun bloat-pad-startup (application)
  ;; Add action "new" to the application
  (let ((action (g-simple-action-new "new" nil)))
    ;; Connect a handler to the signal "activate"
    (g-signal-connect action "activate"
       (lambda (action parameter)
         (declare (ignore action parameter))
         ;; ensure-gtk-main increases the thread level for the new window
         (ensure-gtk-main)
         (new-window application nil)))
    ;; Add the action to the action map of the application
    (g-action-map-add-action application action))

  ;; Add action "about" to the application
  (let ((action (g-simple-action-new "about" nil)))
    ;; Connect a handler to the signal "activate"
    (g-signal-connect action "activate"
       (lambda (action parameter)
         (declare (ignore action parameter))
         (create-about-dialog)))
    ;; Add the action to the action map of the application
    (g-action-map-add-action application action))

  ;; Add action "quit" to the application
  (let ((action (g-simple-action-new "quit" nil)))
    ;; Connect a handler to the signal activate
    (g-signal-connect action "activate"
       (lambda (action parameter)
         (declare (ignore action parameter))
         ;; Destroy all windows of the application
         (dolist (window (gtk-application-get-windows application))
           (gtk-widget-destroy window))
         ;; Quit the main loop
         (leave-gtk-main)
         ;; Quit the application
         (g-application-quit application)))
    ;; Add the action to action map of the application
    (g-action-map-add-action application action))

  ;; Intitialize the application menu and the menubar
  (let ((builder (make-instance 'gtk-builder)))
    ;; Read the menus from a string
    (gtk-builder-add-from-string builder *menu*)
    ;; Set the application menu
    (setf (gtk-application-app-menu application)
          (gtk-builder-get-object builder "app-menu"))
    ;; Set the menubar
    (setf (gtk-application-menubar application)
          (gtk-builder-get-object builder "menubar"))))

(defun bloat-pad-open (application)
  (declare (ignore application))
  ;; Executed when the application is opened
  nil)

(defun bloat-pad-shutdown (application)
  (declare (ignore application))
  ;; Executed when the application is shut down
  nil)

(defmethod initialize-instance :after
    ((app bloat-pad) &key &allow-other-keys)
  (g-signal-connect app "activate" #'bloat-pad-activate)
  (g-signal-connect app "startup" #'bloat-pad-startup)
  (g-signal-connect app "open" #'bloat-pad-open)
  (g-signal-connect app "shutdown" #'bloat-pad-shutdown))

(defun bloat-pad-new ()
  (g-set-application-name "Bloatpad")
  (setf (gtk-settings-gtk-shell-shows-app-menu (gtk-settings-get-default))
        nil)
  (setf (gtk-settings-gtk-shell-shows-menubar (gtk-settings-get-default))
        nil)
  (make-instance 'bloat-pad
                 :application-id "org.gtk.Test.bloatpad"
                 :flags :handles-open
                 :inactivity-timeout 30000
                 :register-session t))

(defun example-application (&optional (argc 0) (argv (null-pointer)))
  (let (;; Create an instance of the application Bloat Pad
        (bloat-pad (bloat-pad-new)))
    (format t "call G-APPLICATION-RUN.~%")
    ;; Run the application
    (g-application-run bloat-pad argc argv)
    (format t "back from G-APPLICATION-RUN.~%")
    ;; Destroy the application
    (g-object-unref (pointer bloat-pad))))
@end verbatim

