/* Copyright (C) 2005 to 2011 and 2013 to 2019 Chris Vine

The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 2.1 of
   the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License, version 2.1, for more details.

   You should have received a copy of the GNU Lesser General Public
   License, version 2.1, along with this library (see the file LGPL.TXT
   which came with this source code package in the c++-gtk-utils
   sub-directory); if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

*/

#ifndef CGU_WINDOW_H
#define CGU_WINDOW_H

#include <c++-gtk-utils/cgu_config.h>

#ifdef CGU_USE_GTK
#include <gtk/gtk.h>
#endif

/** 
 * @class Cgu::WinBase window.h c++-gtk-utils/window.h
 * @brief This is a class for managing the lifetime of top level
 * widgets.
 * @sa MainWidgetBase Application
 *
 * This class provides a base class for lifetime management of top
 * level windows which can make GTK exception safe.  It would be
 * possible to use GTK to control the lifetime of the contained GTK
 * window object, through the delete event handler and the destroy
 * signal.  However in the case of a blocking window (say a dialog)
 * this would result in an invalid object remaining in scope.  It is
 * better to use the C++ lifetime handling mechanisms to control
 * object status, and this class does that.  Where a Cgu::WinBase
 * object is created as an auto (local) object its contained GTK top
 * level window object will be valid while it remains in scope.  For a
 * Cgu::WinBase object which is not blocking (on which exec() is not
 * called) and which is therefore created on free store the WinBase
 * class will delete its own memory and destroy its contained GTK top
 * level window object when it is closed (see below about the close()
 * function).
 *
 * If a NULL pointer is passed as the last argument of its
 * constructor, a new window object will be created with
 * gtk_window_new(GTK_WINDOW_TOPLEVEL).  However, alternatively a
 * pre-formed object deriving from GtkWindow (such as a GtkDialog or
 * GtkMessageDialog object) can be passed as that argument, in which
 * case the class will manage that object.
 *
 * A window will block with a call to exec(), which returns an int
 * value.  The exec() method obtains its return value by calling the
 * virtual protected function get_exec_val(), which by default returns
 * 0 but can be overridden to return something more meaningful.  If
 * something other than an int needs to be returned, it will be
 * necessary for a derived class to provide an extractor function to
 * obtain the data from the object after exec() has returned.
 *
 * A convenience virtual protected on_delete_event() method is
 * provided for any derived class which needs to respond to that
 * signal.  It does not return a value (it does not cause a destroy
 * event to be emitted as in the case of a GTK delete event handler
 * returning false).
 *
 * If the class is constructed on free store and exec() has not been
 * called on it, it self-destroys and any memory allocated to it freed
 * by calling the protected close() function.  If exec() has been
 * called for an object, a call to close() will only terminate the
 * window event loop (that is, cause exec() to unblock), which is
 * correct for an auto (local) object as it will destroy itself when
 * it goes out of scope.  By default (that is, if not overridden by
 * any derived classes) on_delete_event() calls close().
 *
 * No special memory management for GTK widgets contained in the
 * WinBase object (or in an object of a class derived from WinBase) is
 * needed.  The GTK object system takes care of that, and they will
 * be released when the life of the WinBase object finishes.  For this
 * reason, the WinBase class can be used to make GTK exception safe:
 * as its constructor does not throw, exceptions thrown in the
 * constructors of classes derived from it will be correctly dealt
 * with without causing GTK to leak memory if a widget is put into
 * its container in the derived class's constructor immediately after
 * creation on a "top down" basis, or it is placed in another widget
 * class derived from MainWidgetBase.
 *
 * If a C string is passed to the caption argument of the constructor,
 * then the window will display that text as its caption.  A NULL
 * pointer can be passed if no caption is required.  Likewise if a
 * pointer to a GdkPixbuf object is passed as the second parameter, it
 * will be used to set a window icon in the window bar (if the user's
 * window manager supports this).
 *
 * The constructor of WinBase does not call gtk_widget_show() because
 * the constructor of a derived class may want to do things which
 * require the window not to be visible.  Accordingly the constructor
 * of the derived class (or the library user) should call the
 * WinBase::show_all() method(), or call
 * gtk_widget_show()/gtk_widget_show_all() via WinBase::get_win().
 *
 * See @ref Threading for particulars about GTK thread safety (and so
 * WinBase thread safety).
 *
 * WinBase objects can be used with widget heirarchies or top level
 * windows created using GtkBuilder.  See @ref GtkBuilder for
 * particulars about that.
 *
 * A small compilable example is as follows:
 *
 * @anchor WinBaseExampleAnchor
 * @code
 *   #include <gtk/gtk.h>
 *   #include <c++-gtk-utils/window.h>
 *
 *
 *   // *** class headers ***
 *
 *   extern "C" void message_button_clicked(GtkWidget*, void*);
 *
 *   class Message: public Cgu::WinBase {
 *   public:
 *     friend void message_button_clicked(GtkWidget*, void*);
 *     Message(const char* text);
 *   };
 *
 *   
 *   // *** class implementation ***
 *
 *   void message_button_clicked(GtkWidget*, void* data) {
 *     static_cast<Message*>(data)->close();
 *   }
 *
 *   Message::Message(const char* text): Cgu::WinBase("Message", 0, true) {
 *     GtkWidget* box = gtk_vbox_new(false, 2);
 *     gtk_container_add(GTK_CONTAINER(get_win()), box);
 *     GtkWidget* label = gtk_label_new(text);
 *     gtk_box_pack_start(GTK_BOX(box), label,
 *                        true, false, 0);
 *     GtkWidget* button_box = gtk_hbutton_box_new();
 *     gtk_box_pack_start(GTK_BOX(box), button_box,
 *                        false, false, 0);
 *     GtkWidget* button = gtk_button_new_from_stock(GTK_STOCK_OK);
 *     gtk_container_add(GTK_CONTAINER(button_box), button);
 *     g_signal_connect(G_OBJECT(button), "clicked",
 *                      G_CALLBACK(message_button_clicked), this);
 *     gtk_widget_set_can_default(button, true);
 *
 *     gtk_widget_show_all(GTK_WIDGET(get_win()));
 *   }
 *
 *
 *   // *** user code ***
 *
 *   int main(int argc, char* argv[]) {
 *     gtk_init(&argc, &argv);
 *     Message dialog("This is a message");
 *     dialog.exec();
 *     return 0;
 *   }
 * @endcode
 *
 * See @ref Linkage for further discussion of the
 * message_button_clicked() callback.
 *
 * @note The WinBase class will work fine if a GtkDialog object is
 * passed to the last argument of the class's constructor formed from
 * a call to gtk_dialog_new()/gtk_dialog_new_with_buttons(), but a few
 * points should be noted:
 *
 * @note 1. If the response signal has been connected to, a delete
 * event (or with GTK-4 a close request) will cause a response signal
 * to be emitted with the GTK_RESPONSE_DELETE_EVENT id, as well as
 * causing the normal WinBase::on_delete_event() or
 * WinBase::on_close_request() method to be called.  If the response
 * handler acts on the GTK_RESPONSE_DELETE_EVENT id by calling
 * close(), then WinBase::on_delete_event() or
 * WinBase::on_close_request() should normally be overridden to do
 * nothing so that a double call to close() is not made (although
 * c++-gtk-utils will check against and prevent such a double call of
 * close() from a single delete/close event if that is not done).
 *
 * @note 2. It is usually best not to call gtk_dialog_run().  Instead,
 * call WinBase::exec() for a blocking dialog, and override
 * WinBase::get_exec_val() to return any required button response id.
 *
 * @note 3. If creating a GtkDialog object with
 * gtk_dialog_new_with_buttons(), do not pass a GtkDialogFlags
 * argument of GTK_DIALOG_DESTROY_WITH_PARENT.  A GtkDialogFlags
 * argument of GTK_DIALOG_MODAL can be passed, but alternatively to
 * make the dialog modal the user can pass 'true' as the modal
 * argument of the WinBase constructor and pass the parent widget to
 * that constructor rather than to gtk_dialog_new_with_buttons() (so
 * that GtkDialogFlags(0) is passed as the third argument of
 * gtk_dialog_new_with_buttons()).  Either approach will work, but the
 * second has the advantage of consistency with the WinBase interface.
 *
 * @note 4. Likewise, if using gtk_dialog_new_with_buttons(), any
 * dialog caption can be passed either to the title argument of that
 * method or to the caption argument of the WinBase constructor.
 *
 * @note Similar principles apply to the management of objects derived from
 * GtkDialog.
 *
 * This class is not compiled into the library if the library is built
 * with the \--without-gtk configuration option.
 */

namespace Cgu {

#if defined(DOXYGEN_PARSING) || defined(CGU_USE_GTK)

#if GTK_CHECK_VERSION(2,99,0)
class Application;
#endif

class WinBase {

  // main class object
  GtkWindow* g_window_p;

  bool in_exec_loop;
  bool is_modal;
  bool close_guard;
  GtkWindow* parent_p;

#if GTK_CHECK_VERSION(2,99,0)
  Application* app_p;
  // only Cgu::Application can access these
  void set_application(Application* app)  {app_p = app;}
  void unset_application() {app_p = 0;}
#endif

protected:
  /**
   * A function for the use of derived classes which will cause the
   * window to unblock if exec() has been called.  If it is not a
   * blocking window (ie exec() has not been called so it has been
   * constructed on freestore), this function will cause the window to
   * delete itself.  By default it is called by on_delete_event().
   * This method will not throw (assuming, in a case where the
   * Cgu::WinBase object has been added to a Cgu::Application object,
   * that merely iterating through a list does not throw, as it would
   * not on any sane implementation), unless a derived class's
   * destructor throws, which it should not do.
   */
  void close();

  /**
   * Provides the value to be returned by exec().  This method will
   * not throw (unless it is overridden by a derived class's method
   * which throws).
   * @return By default returns 0.  It is intended to be overridden by
   * derived classes where relevant to provide a more meaningful
   * value.
   */
  virtual int get_exec_val() const;

  /**
   * Called when there is a close request on the managed window.
   * Unless overridden by derived classes it just calls close().
   *
   * This function is only available with GTK-4.  Where this library
   * has been compiled against GTK-2 or GTK-3, use on_delete_event().
   *
   * Since 2.2.16
   */
#if defined(DOXYGEN_PARSING) || GTK_CHECK_VERSION(3,94,0)
  virtual void on_close_request();
#endif

  /**
   * Called when there is a delete event on the managed window.
   * Unless overridden by derived classes it just calls close().
   *
   * This function is only available with GTK-2 and GTK-3.  Where this
   * library has been compiled against GTK-4, use on_close_request().
   */
#if defined(DOXYGEN_PARSING) || !GTK_CHECK_VERSION(3,94,0)
  virtual void on_delete_event();
#endif

public:
#if GTK_CHECK_VERSION(2,99,0)
  friend class Application;
#endif
#ifndef DOXYGEN_PARSING
  // this helper class avoids exposing GObject callbacks with C
  // linkage to the global namespace
  class CB;
  friend class CB;
#endif

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  WinBase(const WinBase&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  WinBase& operator=(const WinBase&) = delete;

  /**
   * Returns the GtkWindow object managed by this class.  This method
   * will not throw.
   * @return The managed GtkWindow object.
   */
  GtkWindow* get_win() const {return g_window_p;}

  /**
   * Makes the window block. Calls gtk_main().  It is usually a bad
   * idea to call this method (and so have nested loops) on a
   * non-modal dialog.  To cause the window to unblock, call close().
   * Although the call to gtk_main() made by this method causes the
   * nested main loop to block, callbacks such as timeouts, iowatches
   * and similar events will continue to be triggered.  This method
   * will not throw.
   * @return The value returned by get_exec_val().
   * @note If this library is compiled against GTK-3 or GTK-4 and the
   * WinBase object has been added to a Cgu::Application object, then
   * this method returns immediately with a value of -1.
   */
  int exec();

  /**
   * A convenience function which calls
   * gtk_widget_show_all((GtkWidget*)get_win()).  For classes which
   * are intended to be derived from, it may be undesirable to call
   * gtk_widget_show_all() in the class's constructor (a derived class
   * may add widgets of its own in a way which requires the base
   * class's widgets not to be realized).  In such a case, calling
   * gtk_widget_show_all() should be left to the user code.  This is a
   * function which makes that less verbose.  It will not throw.
   *
   * This function is not available where this library is compiled
   * against GTK-4 (gtk_widget_show_all() does not exist in GTK-4).
   */
#if defined(DOXYGEN_PARSING) || !GTK_CHECK_VERSION(3,90,0)
  void show_all() {gtk_widget_show_all((GtkWidget*)get_win());}
#endif

  /**
   * A convenience function which calls
   * gtk_widget_show((GtkWidget*)get_win()), so as to make calling
   * that function less verbose.  It will not throw.
   *
   * Since 2.2.15
   */
  void show() {gtk_widget_show((GtkWidget*)get_win());}

  /**
   * The constructor will not throw.
   * @param caption Window caption (optional).
   * @param icon An icon which will comprise the window icon (optional
   * \- NULL or nullptr may be passed).  For GTK-2 and GTK-3 this is
   * of type GdkPixbuf*.  For GTK-4 this is of type const char*,
   * representing an icon theme name rather than the icon itself.
   * @param modal Whether the window is to be modal.  If this argument
   * is false and the window is a dialog, the user may want to
   * consider calling gtk_window_set_type_hint() and possibly
   * gtk_window_set_transient_for() before it becomes visible.
   * @param parent The parent of a modal dialog or NULL.  This
   * argument is only acted on where the modal argument is true.  The
   * parent will be made insensitive while the modal dialog is in
   * existence, and automatically made sensitive again when the modal
   * dialog is finished with.  gtk_window_set_transient_for() is also
   * called, which means that the dialog will normally be kept on top
   * of its parent and/or centered over it by the window manager.  If
   * the user does not want the parent to appear as insensitive, pass
   * NULL to this argument and call gtk_window_set_transient_for() in
   * the derived class's constructor.
   * @param window A preformed GtkWindow object (such as a GtkDialog
   * object), or NULL.  If NULL, gtk_window_new(GTK_WINDOW_TOPLEVEL)
   * will be called.
   */
#ifdef DOXYGEN_PARSING
  WinBase(const char* caption = 0, GdkPixbuf*/const char* icon = 0, bool modal = false,
	  GtkWindow* parent = 0, GtkWindow* window = 0);
#elif GTK_CHECK_VERSION(3,96,0)
  WinBase(const char* caption = 0, const char* icon = 0, bool modal = false,
	  GtkWindow* parent = 0, GtkWindow* window = 0);
#else
  WinBase(const char* caption = 0, GdkPixbuf* icon = 0, bool modal = false,
	  GtkWindow* parent = 0, GtkWindow* window = 0);
#endif

  /**
   * The destructor will not throw assuming, in a case where the
   * Cgu::WinBase object has been added to a Cgu::Application object,
   * that merely iterating through a list does not throw (as it would
   * not on any sane implementation).  Amongst other things, the
   * destructor calls gtk_widget_destroy() on the managed top level
   * window object.
   */
  virtual ~WinBase();

#ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
  CGU_GLIB_MEMORY_SLICES_FUNCS
#endif
};

#endif // CGU_USE_GTK

} // namespace Cgu

#endif // CGU_WINDOW_H
