------------------------------------------------------------------------------
--                  GtkAda - Ada95 binding for Gtk+/Gnome                   --
--                                                                          --
--                     Copyright (C) 2001-2022, AdaCore                     --
--                                                                          --
-- This library is free software;  you can redistribute it and/or modify it --
-- under terms of the  GNU General Public License  as published by the Free --
-- Software  Foundation;  either version 3,  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 MERCHAN- --
-- TABILITY or FITNESS FOR A PARTICULAR PURPOSE.                            --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
------------------------------------------------------------------------------

--  <description>
--  This widget organizes its children into resizable panes. Within each
--  pane, multiple children can be put, and they will be accessible through
--  a notebook.
--  </description>
--  <group>Layout containers</group>

with Ada.Containers.Indefinite_Hashed_Maps;
with Ada.Strings.Hash;
with Ada.Tags;
with Cairo;
with GNAT.Strings;
with Glib;                use Glib;
with Glib.Simple_Action;
with Glib.Menu;
with Glib.Object;
with Glib.Xml_Int;
with Glib.Values;
with Gdk.Event;
with Gdk.Pixbuf;
with Gdk.RGBA;
with Gdk.Rectangle;
with Gtk.Accel_Group;
with Gtk.Application;
with Gtk.Box;
with Gtk.Container;
with Gtk.Enums;
with Gtk.Event_Box;
with Gtk.Image;
with Gtk.Label;
with Gtk.Menu;
with Gtk.Notebook;
with Gtk.Radio_Menu_Item;
with Gtk.Css_Provider;
with Gtk.Widget;          use Gtk.Widget;
with Gtk.Window;
with Gtkada.Handlers;
with Gtkada.Multi_Paned;
with Pango.Font;

package Gtkada.MDI is

   type MDI_Window_Record is new Gtk.Widget.Gtk_Widget_Record with private;
   type MDI_Window is access all MDI_Window_Record'Class;
   --  Although this widget is implemented as a gtk_layout, you shouldn't
   --  use the standard Gtk_Layout functions like Put and Move yourself.

   type MDI_Child_Record is new Gtk.Event_Box.Gtk_Event_Box_Record
     with private;
   type MDI_Child is access all MDI_Child_Record'Class;
   pragma No_Strict_Aliasing (MDI_Child);
   --  A child of the MDI, that encapsulates the widgets you have put in the
   --  MDI window.
   --  You can easily convert from this to the initial widget using the
   --  functions Find_MDI_Child and Get_Widget.

   type MDI_Child_Array is array (Natural range <>) of MDI_Child;
   No_Children : constant MDI_Child_Array := (1 .. 0 => null);

   type State_Type is (Normal, Floating, Invisible);
   --  This type indicates the state of an item in the MDI:
   --  - Normal: the item can be manipulated (moved and resized) by the user.
   --      It is found either in the middle notebook (maximized items), or
   --      in the layout.
   --  - Floating: the item has its own toplevel window, and is thus managed
   --      by the window manager.
   --  - Invisible: the child was part of a previously displayed perspective,
   --      but is no longer in the current perspective. We still keep it to
   --      reuse it when switching back to the previous perspective.

   type Allowed_Areas is (Central_Only, Sides_Only, Both);
   --  Where is a child allowed to be moved ?

   procedure Gtk_New
     (MDI   : out MDI_Window;
      Group : access Gtk.Accel_Group.Gtk_Accel_Group_Record'Class;
      Independent_Perspectives : Boolean := False);
   --  Create a new MDI window.
   --  Note that it is recommended that you modify the style (Set_Background
   --  in State_Normal) to have a different color.
   --  You should call Setup_Toplevel_Window once you have added the MDI to a
   --  toplevel widget, so that focus is correctly handled when the toplevel
   --  window gains the focus
   --  When Independent_Perspectives is True, switching perspectives will not
   --  preserve any window. Otherwise, the windows that are in the central
   --  area will be preserved in the new perspective.

   procedure Initialize
     (MDI   : access MDI_Window_Record'Class;
      Group : access Gtk.Accel_Group.Gtk_Accel_Group_Record'Class;
      Independent_Perspectives : Boolean := False);
   --  Internal initialization function.
   --  See the section "Creating your own widgets" in the documentation.

   function Get_Type return Glib.GType;
   function Child_Get_Type return Glib.GType;
   --  Return the type describing the MDI or a MDI_Child

   procedure Setup_Toplevel_Window
     (MDI    : access MDI_Window_Record;
      Parent : access Gtk.Window.Gtk_Window_Record'Class);
   --  Setup Parent to properly handle focus when the window manager changes
   --  the window that currently has the focus.
   --  Parent must be the toplevel window that contains the MDI.

   type Show_Tabs_Policy_Enum is (Always, Never, Automatic);
   type Title_Bars_Policy     is (Always, Never, Central_Only);

   type Tab_Orientation_Type is
     (Automatic, Horizontal, Bottom_To_Top, Top_To_Bottom);

   procedure Configure
     (MDI                       : access MDI_Window_Record;
      Opaque_Resize             : Boolean := False;
      Close_Floating_Is_Unfloat : Boolean := True;
      Title_Font         : Pango.Font.Pango_Font_Description := null;
      Title_Bar_Color    : Gdk.RGBA.Gdk_RGBA := Gdk.RGBA.Null_RGBA;
      Focus_Title_Color  : Gdk.RGBA.Gdk_RGBA := Gdk.RGBA.Null_RGBA;
      Draw_Title_Bars    : Title_Bars_Policy   := Always;
      Tabs_Position      : Gtk.Enums.Gtk_Position_Type := Gtk.Enums.Pos_Top;
      Show_Tabs_Policy   : Show_Tabs_Policy_Enum := Automatic;
      Homogeneous_Tabs   : Boolean := True;
      Tabs_Orientation   : Tab_Orientation_Type := Automatic);
   --  Change the setup of the MDI.
   --  Close_Floating_Is_Unfloat, if True, means that closing a floating child
   --  will put it back in the MDI instead of destroying it (unless its flag
   --  Always_Destroy_Float is set).
   --  Title_Font is the font used in the title bars (if null, "sans 8"
   --  is used).
   --  The colors, when Null_Color, will not change the current setup.
   --  If Draw_Title_Bars is False, then no extra title bar will be displayed
   --  for the MDI children when they are maximized. This saves space on the
   --  screen. However, the notebook tabs will be highlighted with
   --  Title_Bar_Color in exchange.
   --  Tabs_Position indicates where the notebook tabs should be put.
   --  Tabs_Orientation indicates how the notebook tabs should be rotated.
   --  Show_Tabs_Policy indicates when the notebook tabs should be displayed.
   --
   --  If Homogeneous_Tabs is true, then the notebook tabs will only use their
   --  natural size when they all fit in the notebook's width. Otherwise, they
   --  are resized (and ellipsized as needed) so that more of them show.
   --  Changing this setup requires a restart of the MDI.
   --
   --  Calling configure will define a default CSS to properly theme the MDI.
   --  This CSS relies on mainly two widget classes to behave:
   --  * mdifocused, corresponding to the notebook having the focus
   --  * mdititle, corresponding to the title bar
   --  This default CSS renders the active tab of the active notebook with the
   --  color defined in Focus_Title_Color. Same for the color of the title
   --  bar (uses Title_Bar_Color for non-active title bar).
   --  This behavior can at any point be overridden by users by defining
   --  their own and load it at run-time.
   --
   --  Since the move to gtk3, the highlighting of the current tab is left to
   --  the gtk+ theme, rather than overwritten in this package. This permits a
   --  better integration with the user's theme. It is possible for
   --  applications to provide their own CSS, though, using
   --  Gtkada.Style.Load_Css_File. Here is an example of such a CSS, which
   --  highlights the current tab in the notebook that currently has the focus:
   --
   --     @define-color mdi-tab-color  grey;
   --     @define-color mdi-active-tab-color blue;
   --     .mdititle {
   --         border-width: 2px;
   --         background-image: -gtk-gradient(
   --             linear, left top, left bottom,
   --             from(@mdi-tab-color), to(shade(@mdi-tab-color, 1.1)));
   --     }
   --     .mdifocused .mdititle {
   --         background-image: -gtk-gradient(
   --             linear, left top, left bottom,
   --             from(@mdi-active-tab-color),
   --             to(shade(@mdi-active-tab-color, 1.1)));
   --     .mdi tab GtkLabel, .mdi tab GtkImage { opacity: 0.8 }
   --     .mdifocused tab GtkLabel, .mdifocused tab GtkImage { opacity: 1.0}

   function Independent_Perspectives
     (MDI : access MDI_Window_Record) return Boolean;
   --  Whether the MDI is using independent perspectives

   -------------
   -- Windows --
   -------------

   type Child_Flags is mod 2 ** 7;
   Destroy_Button       : constant Child_Flags := 2 ** 2;
   Float_As_Transient   : constant Child_Flags := 2 ** 3;
   Float_To_Main        : constant Child_Flags := 2 ** 5;
   Always_Destroy_Float : constant Child_Flags := 2 ** 4;
   All_Buttons          : constant Child_Flags := Destroy_Button;
   --  Special flags to set up the widgets: The first is the buttons
   --  that should be displayed in the title bar of the MDI children. If
   --  Float_As_Transient is set, then the child will be set up as a transient
   --  window when floating: on most window managers, it will stay on top of
   --  the MDI, but the window will have less decorations in its title bar,
   --  in particular no destroy button. In such a case, <Esc> will close the
   --  window, or unfloat it depending on the MDI's setup, as is the case for
   --  all dialogs in GtkAda. The MDI's setup will be ignored (and the child
   --  always destroyed when Esc is pressed) if Always_Destroy_Float is true.
   --  Float_To_Main yields the same behavior as Float_As_Transient, except
   --  that the floated child will always be attached to the main window.
   --  What this means is that the child will stay on top of the MDI, and will
   --  persist if you close the current MDI child.

   type Child_Group is new Positive;
   Group_Default : constant Child_Group := 1;
   Group_Any     : constant Child_Group := Child_Group'Last;
   --  This type can be used to help group windows by type within the MDI.
   --  Group_Default as a special status when computing the initial position
   --  for a window. But you can create your own groups as needed, so that for
   --  instance editors tend to be grouped with other editors, graphs with
   --  other graphs,... depending on your application.
   --  The group has an impact when the last window from a notebook is closed:
   --  If the window belongs to Group_Default, and it is the last of its group,
   --  then the space currently occupied by that window is not reclaimed, and
   --  therefore an empty area will exist in the MDI. The idea is that for
   --  instance editors typically play a special role in an integrated
   --  development environment, and the users like to have them in the center
   --  of the window. When closing the last editor, they do not want the side
   --  windows (browsers, consoles,...) to take up that space that should
   --  really only be used for editors.
   --  To get such a behavior, editors should belong to Group_Default, and all
   --  other windows to custom groups.
   --
   --  Do not use Group_Any, it is used internally with special meanings.

   procedure Gtk_New
     (Child        : out MDI_Child;
      Widget       : access Gtk.Widget.Gtk_Widget_Record'Class;
      Flags        : Child_Flags := All_Buttons;
      Group        : Child_Group := Group_Default;
      Focus_Widget : Gtk.Widget.Gtk_Widget := null;
      Areas        : Allowed_Areas := Both);
   --  Create a new MDI child that contains widget.
   --  Widget mustn't be of type Gtk_Window.
   --
   --  You shouldn't access Widget directly afterwards, but should manipulate
   --  Child only. However, as a special exception, you can still pass Widget
   --  as a parameter to the subprograms in this package to manipulate it (e.g.
   --  in Raise_Child, ...)
   --
   --  If Focus_Widget is not null, this is the widget that gets the keyboard
   --  focus when the child is selected.

   procedure Initialize
     (Child        : access MDI_Child_Record'Class;
      Widget       : access Gtk.Widget.Gtk_Widget_Record'Class;
      Flags        : Child_Flags := All_Buttons;
      Group        : Child_Group := Group_Default;
      Focus_Widget : Gtk.Widget.Gtk_Widget := null;
      Areas        : Allowed_Areas := Both);
   --  Internal initialization function.
   --  See the section "Creating your own widgets" in the documentation.

   procedure Change_Group
     (Child : not null access MDI_Child_Record'Class;
      Group : Child_Group);
   --  Change the child's MDI group.

   type Child_Position is
     (Position_Automatic,
      Position_Bottom,
      Position_Top,
      Position_Left,
      Position_Right,
      Position_Float);
   subtype Side_Position is Child_Position
      range Position_Bottom .. Position_Right;
   --  The initial position of windows within the MDI.
   --  In all cases, the initial location for a window is computed with the
   --  following algorithm. This algorithm is designed with the notion of
   --  groups of windows in mind, so that some windows (typically editors) have
   --  a special status.
   --     - If Position_Float, make the window floating
   --     - If another window with the same Group is already in the MDI, the
   --       new window is put on top of it.
   --     - Otherwise, if Position_Automatic, if an empty area exists within
   --       the MDI, the new window is put in that area.
   --     - Else if the Position is Bottom .. Right, the new window is put
   --       below all others (resp. to the top, left or right)
   --     - Else the window is put on top of the currently selected window

   procedure Put
     (MDI               : access MDI_Window_Record;
      Child             : access MDI_Child_Record'Class;
      Initial_Position  : Child_Position := Position_Automatic;
      Position_At_Mouse : Boolean := True;
      X, Y              : Gint := 0);
   --  Add a new child to the MDI window, and return its embedding widget.
   --  Calling Put does not give the focus to the newly inserted widget.
   --  To do that, you should call Set_Focus_Child.
   --  If Initial_Position is Position_Float, then position the floating window
   --  under the mouse cursor, unless Position_At_Mouse is False, in which
   --  case position the window at X, Y.

   function Save_Desktop
     (Self : not null access MDI_Child_Record) return Glib.Xml_Int.Node_Ptr;
   --  A function used to save a child into the desktop.
   --  This is similar to Save_Desktop_Function, but is specific to a child,
   --  and thus more efficient.
   --
   --  If this function returns some data, that data is saved into the XML
   --  for the desktop. If it returns null, however, the MDI will call each
   --  of the registered Save_Desktop_Function, until one returns some data.
   --
   --  The resulting node must have a tag that is unique for this type of
   --  widget, and this recognized by one of the Load_Desktop_Function so
   --  that the child can be recreated when the desktop is loaded from XML.

   procedure Set_Size
     (MDI        : access MDI_Window_Record;
      Child      : access MDI_Child_Record'Class;
      Width      : Glib.Gint;
      Height     : Glib.Gint;
      Fixed_Size : Boolean := False);
   --  Forces a new size for a child. If Width or Height is left to -1, the
   --  matching size will be computed from the child's requisition. If they are
   --  left to 0, the corresponding length is left to its current value.
   --  If Fixed_Size is True, then the widget will not be resized when the MDI
   --  itself is resized (unless the user has first moved one of the handles to
   --  manually resize it). Otherwise, it will grow proportionally with the
   --  rest of the MDI.

   procedure Close
     (MDI   : access MDI_Window_Record;
      Child : access Gtk.Widget.Gtk_Widget_Record'Class;
      Force : Boolean := False);
   --  Close the child that contains Child, and remove its window from the
   --  MDI. See also Close_Child if you need to close a MDI_Child itself.
   --  This first checks through a delete_event callback whether the child
   --  accepts to be closed.
   --  "delete_event" is not sent, and the child is automatically closed, if
   --  Force is set to True.

   procedure Set_Title
     (Child       : not null access MDI_Child_Record;
      Title       : String;
      Short_Title : String := "");
   --  Set the title for a child. Title is the title put in titlebar of
   --  the children, whereas Short_Title is the name of the notebook tab when
   --  children are maximized. By default, it is the same as Title.
   --
   --  The default title is the empty string.
   --  This title will be the one used for the window when the child is set to
   --  floating state.
   --
   --  Title and Short_Title should preferably be UTF8-encoded, although this
   --  procedure will attempt to guess the encoding if the string is not valid
   --  UTF8.
   --
   --  Child should already be in the MDI at this point (via a call to Put), so
   --  that the "use_short_titles" preference is taken into account.

   function Get_MDI
     (Child : not null access MDI_Child_Record) return MDI_Window;
   --  Return the MDI to which Child is associated. In Child is a floating
   --  child, it might not be in the MDI window itself.

   function Get_Title
     (Child : not null access MDI_Child_Record) return UTF8_String;
   --  Return the title for a specific child

   function Get_Short_Title
     (Child : not null access MDI_Child_Record) return UTF8_String;
   --  Return the name of the notebook tab used when children are maximized.

   function Has_Title_Bar
     (Child : not null access MDI_Child_Record) return Boolean;
   --  Whether a title bar is currently displayed for Child

   function Get_State
     (Child : not null access MDI_Child_Record) return State_Type;
   --  Return the current state of the child

   function Get_Allowed_Areas
     (Child : not null access MDI_Child_Record'Class) return Allowed_Areas;
   --  What areas the child can be put int

   function Get_Tab_Orientation
     (Child : not null access MDI_Child_Record'Class)
      return Tab_Orientation_Type;
   --  Return the child's tab orientation

   function Get_Child_Notebook
     (Child : access MDI_Child_Record'Class) return Gtk.Notebook.Gtk_Notebook;
   --  Return the notebook that directly contains Child

   function Get_Tooltip
     (Child : not null access MDI_Child_Record)
     return String;
   --  Return the text to use in tooltips for the notebook tabs.
   --  The default (or if this function returns the empty string) the title
   --  of the child (Get_Title above).

   function Get_Tooltip_Is_Markup
     (Child : not null access MDI_Child_Record)
     return Boolean;
   --  Should return True if Get_Tooltip returns a string with special
   --  markup like "<b>" for bold text. When this is True, special
   --  characters like "<" and ">" must be quoted with backslashes.

   procedure Set_Icon
     (Child : not null access MDI_Child_Record;
      Icon  : Gdk.Pixbuf.Gdk_Pixbuf);
   pragma Obsolescent (Set_Icon);
   procedure Set_Icon_Name
     (Child     : not null access MDI_Child_Record;
      Icon_Name : String);
   --  Associate an icon with Child. This icon is visible in the title bar, the
   --  notebook tabs, the Window menu and the interactive selection dialog.
   --  The icon is updated dynamically on the screen.
   --  The Icon can safely be unrefed by the caller (and should, if the pixbuf
   --  was newly allocated).

   function Get_Icon
     (Child : not null access MDI_Child_Record) return Gdk.Pixbuf.Gdk_Pixbuf;
   pragma Obsolescent (Get_Icon);
   function Get_Icon_Name
     (Child : not null access MDI_Child_Record) return String;
   --  Returns the icon associated with Child. The returned pixbuf is owned
   --  by the MDI and must not be freed by the caller.

   ---------------------------
   -- Drag and Drop support --
   ---------------------------

   function Dnd_Data
     (Child : not null access MDI_Child_Record; Copy : Boolean)
      return MDI_Child;
   --  When a drag-and-drop operation took place to move a child from one
   --  position to the next, this function is called to know what child should
   --  be moved.
   --  As a result, the implementor can choose whether a copy of the child
   --  should be returned (creating a new view for an editor for instance), or
   --  if the child itself should be moved (the default).
   --  The returned MDI_Child must have been added to the MDI before it is
   --  returned.
   --  Copy is set to true if a copy operation was requested, to False if a
   --  simple move operation was requested. It can be ignored if Child doesn't
   --  know how to create a copy of itself for instance.

   procedure Set_Dnd_Message
     (MDI     : access MDI_Window_Record;
      Message : String);
   --  Override the message that is displayed in the popup window while
   --  performing a drag. By default, this message mentions:
   --     "Use control to move the whole notebook"
   --     "Use shift to create a new view for editors"
   --  so might not be suitable for all applications.
   --  You can use markup like "<b>...</b>" to put keywords in bold.
   --
   --  Passing an empty string for Message will restore the default message.

   procedure Child_Drag_Begin
     (Child  : not null access MDI_Child_Record'Class;
      Event  : Gdk.Event.Gdk_Event_Button;
      Areas  : Allowed_Areas);
   --  Starts a drag-and-drop operation for the child, so that it can be put in
   --  some other place on the desktop. This should only be called when a
   --  handler for the "button_press_event" signal, passing the event itself in
   --  parameter.
   --  The Child is immediately raised and gains the focus.
   --  Areas indicates where the child can be dropped. This is in particular
   --  useful if Dnd_Data was overridden and some other child will be dropped
   --  instead.

   procedure Cancel_Child_Drag
     (Child : not null access MDI_Child_Record'Class);
   --  Cancel a drag operation started by Child_Drag_Begin.
   --  It doesn't call Child_Drag_Finished.

   procedure Child_Drag_Finished (Child : not null access MDI_Child_Record);
   --  Called when a drag operation is either aborted or completed. It should
   --  be overridden if special cleanup should be done.

   -----------
   -- Menus --
   -----------

   type Tab_Contextual_Menu_Factory is access procedure
     (Child : access MDI_Child_Record'Class;
      Menu  : access Gtk.Menu.Gtk_Menu_Record'Class);

   procedure Set_Tab_Contextual_Menu_Factory
     (MDI     : access MDI_Window_Record;
      Factory : Tab_Contextual_Menu_Factory);
   --  Set (or unset if Factory is null) the callback to create the contextual
   --  menu entries when the user clicks on a notebook tab.
   --  Factory should add entries to Menu (which already contains the default
   --  entries, but you can remove them if needed).

   ------------------------
   -- Selecting children --
   ------------------------

   procedure Highlight_Child
     (Child : not null access MDI_Child_Record; Highlight : Boolean := True);
   --  Highlight the child until it is selected by the user.
   --  The color of its menu label and of the text in the notebook tabs is
   --  changed.
   --  Nothing is done if the child is already fully visible (either in the
   --  active page in one of the notebooks, or the child that has the selection
   --  in the layout).
   --  This is meant to be used as a graphical note to the user that the child
   --  has been updated and the user should look at it.

   function Get_Focus_Child
     (MDI : access MDI_Window_Record) return MDI_Child;
   --  Return the child that currently has the MDI focus.
   --  null is returned if no child has the focus.

   procedure Set_Focus_Child
     (MDI        : access MDI_Window_Record;
      Containing : access Gtk.Widget.Gtk_Widget_Record'Class);
   --  Give the focus to the child containing Containing. This will not
   --  Grab_Focus for the child in all cases, since you might want to give the
   --  focus to some specific part of your widget (an entry field,...) in some
   --  cases.
   --  Call this with Containing = null to explicitly unset the current focus
   --  child.

   procedure Set_Focus_Child (Child : not null access MDI_Child_Record);
   --  Make Child the active widget, and raise it at the top.

   procedure Check_Interactive_Selection_Dialog
     (MDI          : access MDI_Window_Record;
      Event        : Gdk.Event.Gdk_Event;
      Move_To_Next : Boolean;
      Only_Group   : Child_Group := Group_Any);
   --  Open the interactive dialog for selecting windows.
   --  This dialog should be open as a result of a key press event.
   --  Move_To_Next indicates whether we want to select the next child (True)
   --  or the previous child (False).
   --  This dialog will be closed only when the key that opened it is fully
   --  released. For instance, if the dialog was opened as a result of
   --  pressing Ctrl-Tab, the dialog will only be closed when Ctrl itself is
   --  released.
   --  You can call this procedure even if a dialog is currently open. This
   --  simply forces a move to the next or previous child. In fact, it is
   --  your responsability to call this procedure when the user presses
   --  the keys to move between children.
   --
   --  If Event is null, then no dialog is displayed. Instead, the next or
   --  previous visible child is immediately selected. In such a mode, windows
   --  that are not on top of their respective notebook are ignored. This can
   --  be used to emulate Emacs's behavior for goto-other-window.
   --
   --  If Only_Group is specified, then only the windows from that group will
   --  be shown in the dialog.

   --  This function is not internal to the MDI since connecting to the
   --  key_press_event and key_release_event should be done in the gtk_window
   --  that contains the MDI. Otherwise, some events are intercepted by gtk+,
   --  for instance the key_release_events, and the key_press_events for some
   --  specified keys.
   --  It also gives the choice to the application of whether this feature is
   --  wanted or not.

   -----------------------------------------
   -- MDI_Child and encapsulated children --
   -----------------------------------------

   function Get_Widget
     (Child : access MDI_Child_Record) return Gtk.Widget.Gtk_Widget;
   --  Return the widget that Child encapsulates. This is the widget you
   --  initially Put() in MDI.

   function Find_MDI_Child
     (MDI    : access MDI_Window_Record;
      Widget : access Gtk.Widget.Gtk_Widget_Record'Class) return MDI_Child;
   --  Return the MDI_Child that encapsulates Widget.
   --  Widget must be the exact same one you gave in argument to Put.
   --  If the child is currently not visible in the perspective (for instance
   --  it was created for another perspective, but is not present in the
   --  current one), it is inserted automatically back in the MDI.

   function Find_MDI_Child_From_Widget
     (Widget : access Gtk.Widget.Gtk_Widget_Record'Class) return MDI_Child;
   --  Return the MDI child that encapsulate the parent of Widget.
   --  As opposed to Find_MDI_Child, Widget can be anywhere within the
   --  widget tree. This function properly handles floating children
   --  If the child is currently not visible in the perspective (for instance
   --  it was created for another perspective, but is not present in the
   --  current one), it is inserted automatically back in the MDI.

   function Find_MDI_Child_By_Tag
     (MDI : access MDI_Window_Record;
      Tag : Ada.Tags.Tag;
      Visible_Only : Boolean := False) return MDI_Child;
   --  Return the first child matching Tag
   --  If the child is currently not visible in the perspective (for instance
   --  it was created for another perspective, but is not present in the
   --  current one), it is inserted automatically back in the MDI.
   --  If Visible_Only is True, an invisible child is not returned. This is
   --  useful to check whether a child is currently visible.

   function Find_MDI_Child_By_Name
     (MDI  : access MDI_Window_Record;
      Name : String) return MDI_Child;
   --  Return the first child matching Name.
   --  If the child is currently not visible in the perspective (for instance
   --  it was created for another perspective, but is not present in the
   --  current one), it is inserted automatically back in the MDI.

   type Child_Iterator is private;

   function First_Child
     (MDI               : access MDI_Window_Record;
      Group_By_Notebook : Boolean := False;
      Visible_Only      : Boolean := True) return Child_Iterator;
   --  Return an access to the first child of the MDI.
   --
   --  If Group_By_Notebook is True, then the children are reported one after
   --  the other, but all the widget from the same notebook are reported in the
   --  same order as the notebook pages. Floating children do not belong to a
   --  notebook, and are also reported together. To find out to which notebook
   --  a child belongs, use Get_Notebook below.
   --
   --  If Group_By_Notebook is False, it is garanteed that the first child is
   --  the one that currently has the focus in the MDI. The children are
   --  returned in the order in which they last had the focus.
   --
   --  If Visible_Only is true, then only those children currently visible in
   --  the perspective are returned. The children that were part of a former
   --  perspective are not returned.

   procedure Next (Iterator : in out Child_Iterator);
   --  Move to the next child in the MDI

   function Get_Notebook
     (Iterator : Child_Iterator) return Gtk.Notebook.Gtk_Notebook;
   --  Return the notebook to which the current child belongs. null is returned
   --  for floating children

   function Get (Iterator : Child_Iterator) return MDI_Child;
   --  Return the child pointed to by Iterator.
   --  If Iterator is no longer valid, null is returned.

   -----------------------
   -- Floating children --
   -----------------------

   procedure Float_Child
     (Child             : not null access MDI_Child_Record'Class;
      Float             : Boolean;
      Position_At_Mouse : Boolean := True;
      X, Y              : Gint := 0);
   --  Change the floating state of a child.
   --  If Float is True:
   --     - if Position_At_Mouse is True, position the window
   --       under the mouse pointer.
   --     - if Position_At_Mouse is False, use position the window at X, Y.

   function Is_Floating
     (Child : not null access MDI_Child_Record'Class) return Boolean;
   --  Return True if Child is currently in a separate window

   procedure Set_Default_Size_For_Floating_Window
     (Child : not null access MDI_Child_Record;
      Win   : not null access Gtk.Window.Gtk_Window_Record'Class;
      Width, Height : Glib.Gint);
   --  Set the default size for a floating window.
   --  Width and Height are the values that have bee computed, from either
   --  the non-floating size of the child, or from the child's preferred
   --  size. This procedure can be overridden, though, to force other
   --  values, for instance if you would like to restore a specific size.

   procedure Set_All_Floating_Mode
     (MDI : access MDI_Window_Record; All_Floating : Boolean);
   --  If All_Floating is set to true, the MDI will have a size of 0x0, and all
   --  children are set to floating. This can be used if you wish to let the
   --  window manager handle the windows. If All_Floating is True, children
   --  can no longer be maximized.

   procedure Use_Short_Titles_For_Floats
     (MDI : access MDI_Window_Record; Short_Titles : Boolean);
   --  If Short_Titles is set to true, only short titles will ever be used
   --  either in the title bars (in notebooks) or as the title for floating
   --  windows.

   procedure Create_Float_Window_For_Child
      (Child     : not null access MDI_Child_Record;
       Win       : out Gtk.Window.Gtk_Window;
       Container : out Gtk.Container.Gtk_Container);
   --  Creates the window to use when a child is made floating.
   --  This procedure provides reasonable default, but can be overridden in
   --  your application if you have special needs (like creating a window
   --  with specific menubar or decorations for instance).
   --  Win must be set to the window that has been created, and Container
   --  to the area of the window that will contain Child.
   --  The window's title will be set automatically.
   --  The window's size will be set by the MDI by calling
   --  Child.Set_Default_Size_For_Floating_Window.

   ----------------------
   -- Closing children --
   ----------------------

   procedure Close_Child
     (Child           : not null access MDI_Child_Record'Class;
      Force           : Boolean := False;
      Focus_Same_Area : Boolean := True);
   --  Same as Close, but applies directly to a MDI_Child.
   --  If Focus_Same_Area is True, the focus will return to the previously
   --  focused child in the same area / notebook as Child, and which is not
   --  Child itself.
   --  Otherwise, the focus will return to the previously focused child, no
   --  matter its area/notebook.

   ---------------------------
   -- Reorganizing children --
   ---------------------------

   procedure Raise_Child
     (Child      : not null access MDI_Child_Record'Class;
      Give_Focus : Boolean := True);
   --  Put Child in the foreground.
   --  Note that this does not give the focus to this child, unless
   --  Give_Focus is set to True. If Child and the current focus child are in
   --  the same notebook, Child will always gain the focus, so that the focus
   --  is not left on an invisible window.

   function Is_Raised
     (Child : not null access MDI_Child_Record'Class) return Boolean;
   --  Whether the child is currently raised, ie fully visible to the user

   procedure Lower_Child (Child : not null access MDI_Child_Record'Class);
   --  Put Child in the background.
   --  If the children are maximized, this selected the next page from the
   --  notebook.

   procedure Give_Focus_To_Previous_Child
     (Child          : access MDI_Child_Record'Class;
      From_Same_Area : Boolean := True);
   --  Give focus to the last child.
   --  If From_Same_Area is True, it will return the previously focused child
   --  in the same area / notebook as Child, and which is not Child itself.
   --  Otherwise, it will return the previously focused child, no matter its
   --  area/notebook.

   type Split_Mode is
     (Before, Before_Reuse,
      After,  After_Reuse,
      Any_Side_Reuse);
   --  How a child should be split:
   --  If "Before", the child is put above or to the left of its current
   --  position. A new window is created to containing it. If the "_Reuse"
   --  version is used, and a window already exist at that position, the child
   --  will be put in it instead of creating a new one.
   --  Any_Side_Reuse indicates that the child will be put on either side,
   --  depending on where a window already exists. If there is no window on the
   --  side, a new one is created.

   procedure Split
     (MDI           : access MDI_Window_Record;
      Orientation   : Gtk.Enums.Gtk_Orientation;
      Child         : MDI_Child := null;
      Mode          : Split_Mode := Before;
      Width, Height : Glib.Gint := 0);
   --  Split the notebook containing Child (by default, the current focus
   --  child).
   --  Mode indicates in which direction the splitting should occur. If you
   --  are splitting a child in the central area, splitting will never reuse
   --  a window outside of the central area.
   --  Width and Height indicate the desired geometry for the splitted area,
   --  0 indicate a 50/50 split.

   ----------------------
   -- Desktop Handling --
   ----------------------
   --  The MDI provides a way to save desktops, i.e the list of children
   --  currently open in the MDI and their location. It can then restore the
   --  desktop at some later point.
   --
   --  Desktops require support from the widgets that are put in the MDI. They
   --  need to register a function to save them and a function to recreate
   --  them. Using Ada streams for this didn't prove workable since some
   --  children might need extra parameters not available to them through
   --  streams. This is why the following subprograms are in a generic package,
   --  so that you can pass whatever parameter(s) is needed in your
   --  application.
   --
   --  Desktops are saved and restored in XML trees.
   --
   --  If you need your application to load a "default desktop" when the user
   --  hasn't defined one, it is recommended that you distribute an actual
   --  file containing this desktop. You could also create the XML tree in
   --  memory yourself, and thus hard-code the default desktop if need be.

   generic
      type User_Data is private;
      --  Generic type of parameter that is passed to all the children's save
      --  and restore functions.

      --  This package needs to be instantiated at library level

   package Desktop is

      type Menu_Registration_Procedure is access procedure
        (User       : User_Data;
         Item_Name  : String;
         Accel_Path : String);
      --  Function used to register in the application a static menu
      --  created by the MDI.

      procedure Set_Menu_Model
         (MDI   : not null access MDI_Window_Record'Class;
          App   : not null access Gtk.Application.Gtk_Application_Record'Class;
          Model : access Glib.Menu.Gmenu_Record'Class;
          User  : User_Data);
      --  Associates a menu model with the MDI.
      --  This model will be filled and maintained by the MDI, to provide
      --  useful actions like floating/docking items, selecting items,...

      function Create_Menu
        (MDI               : access MDI_Window_Record'Class;
         Accel_Path_Prefix : String := "<gtkada>";
         User              : User_Data;
         Registration      : Menu_Registration_Procedure := null)
         return Gtk.Menu.Gtk_Menu;
      --  Create a dynamic menu that can then be inserted into a menu bar. This
      --  menu is dynamic, ie its content will changed based on the focus
      --  child.
      --  If this function is called several times, a new menu is created
      --  every time. Accel_Path_Prefix must be the same for every call.
      --  Accel_Path_Prefix is used so that the key shortcuts associated with
      --  these menu items can be changed dynamically by the user (see
      --  gtk-accel_map.ads). The prefix must start with "<" and end with ">".
      --  User is used for the callbacks on perspective changes, and passed to
      --  Load_Perspective
      --  With recent versions of gtk+, the use of Glib.Menu_Model.Gmenu_Model
      --  is encouraged, so you should use Set_Menu_Model above instead.

      type Save_Desktop_Function is access function
        (Widget : access Gtk.Widget.Gtk_Widget_Record'Class;
         User   : User_Data) return Glib.Xml_Int.Node_Ptr;
      --  A general function that dumps the parameters of a widget into an XML
      --  tree.
      --
      --  Note: you should register one such function for all the widget types
      --  you will put in the MDI and that need to be saved when a desktop is
      --  saved. The MDI will call all the registered functions one after the
      --  other. Therefore, your function should return null if Widget is not
      --  of a type that is it can handle.
      --
      --  Before calling the registered save_desktop_function, the MDI will
      --  first use the child's Save_Desktop primitive function.

      type Load_Desktop_Function is access function
        (MDI : MDI_Window; Node : Glib.Xml_Int.Node_Ptr; User : User_Data)
         return MDI_Child;
      --  A general function that loads a widget from an XML tree.
      --
      --  As for Save_Desktop_Function, this function should return null if it
      --  doesn't know how to handle Node or if Node doesn't describe a widget
      --  type that it can handle.
      --
      --  This function returns an MDI_Widget that has been put in the MDI.

      procedure Register_Desktop_Functions
        (Save : Save_Desktop_Function;
         Load : Load_Desktop_Function);
      --  Register a set of functions to save and load desktops for some
      --  specific widget types. This can be called multiple times.
      --  Save might be null.

      function Restore_Desktop
        (MDI          : access MDI_Window_Record'Class;
         Perspectives : Glib.Xml_Int.Node_Ptr;
         From_Tree    : Glib.Xml_Int.Node_Ptr;
         User         : User_Data) return Boolean;
      --  Restore the contents of the MDI from its saved XML tree.
      --  Perspectives is the list of perspectives. It is cloned as needed, so
      --  the caller is still responsible for freeing it. The first perspective
      --  is loaded.
      --  From_Tree is the part of the desktop that describes the editor area.
      --  User is passed as a parameter to all of the Load_Desktop_Function
      --  registered by the widgets.
      --  Return False if the desktop couldn't be loaded
      --  It also restores the size and position of the toplevel window that
      --  contains the MDI

      procedure Load_Perspective
        (MDI          : access MDI_Window_Record'Class;
         Name         : String;
         User         : User_Data);
      --  Replace the current perspective by another one. This preserves the
      --  editor area.
      --  If the perspective does not exist, nothing is done, unless no
      --  perspective is currently loaded (in which case we load the first
      --  on in the list).

      procedure Create_Perspective
        (MDI          : access MDI_Window_Record'Class;
         Name         : String;
         User         : User_Data);
      --  Create a new perspective with the current desktop layout. If another
      --  perspective with the same name exists, it is replaced.

      procedure Define_Perspective
        (MDI          : access MDI_Window_Record'Class;
         XML          : Glib.Xml_Int.Node_Ptr;
         User         : User_Data);
      --  Define a new perspective (in the same format as returned by
      --  Save_Desktop, the central area is under control of the user so you
      --  cannot change it).
      --  If such a perspective already exists, nothing is done (since the user
      --  might have modified it already).
      --  XML's root node is the <perspective> node, including its "name"
      --  attribute.
      --  XML must be freed by the caller.

      procedure Save_Desktop
        (MDI          : access MDI_Window_Record'Class;
         User         : User_Data;
         Perspectives : out Glib.Xml_Int.Node_Ptr;
         Central      : out Glib.Xml_Int.Node_Ptr);
      --  Return XML representations of the perspectives and central area. Both
      --  nodes need to be freed by the caller, and can be saved in a file (to
      --  be passed to Restore_Desktop later on).
      --  This function calls each of the registered function for the children
      --  of the MDI.
      --  It also saves the size and position of the toplevel window that
      --  contains the MDI

      function Get_XML_Content
        (MDI : access MDI_Window_Record'Class;
         Tag : String) return Glib.Xml_Int.Node_Ptr;
      --  Return the first XML subtree starting with 'Tag'. This allows a
      --  module to retrieve its content after the 'Load_Desktop' call.

      procedure Free_Registered_Desktop_Functions;
      --  Free the memory allocated for the registered functions.

   private
      type Register_Node_Record;
      type Register_Node is access Register_Node_Record;
      type Register_Node_Record is record
         Save : Save_Desktop_Function;
         Load : Load_Desktop_Function;
         Next : Register_Node;
      end record;

      type Perspective_Menu_Item_Record
        is new Gtk.Radio_Menu_Item.Gtk_Radio_Menu_Item_Record
      with record
         MDI  : MDI_Window;
         Name : Natural;
         User : User_Data;
      end record;
      type Perspective_Menu_Item
        is access all Perspective_Menu_Item_Record'Class;

      procedure Change_Perspective
        (Item : access Gtk.Widget.Gtk_Widget_Record'Class);
      CP_Access : constant
        Gtkada.Handlers.Widget_Callback.Marshallers.Marshaller :=
        Gtkada.Handlers.Widget_Callback.To_Marshaller
          (Change_Perspective'Access);
      --  Internal, but needed so that we can have a 'Access on a callback

      procedure Create_Perspective_CB
        (Item : access Gtk.Widget.Gtk_Widget_Record'Class);
      CreateP_Access : constant
        Gtkada.Handlers.Widget_Callback.Marshallers.Marshaller :=
        Gtkada.Handlers.Widget_Callback.To_Marshaller
          (Create_Perspective_CB'Access);

      procedure On_Action_Select_Perspective
        (MDI       : access MDI_Window_Record'Class;
         Params    : Glib.Values.GValues;
         User      : User_Data);
      procedure On_Action_Create_Perspective
        (MDI       : access MDI_Window_Record'Class;
         Params    : Glib.Values.GValues;
         User      : User_Data);
      procedure On_Perspective_Changed_Update_Menu
         (Menu     : access Gtk.Widget.Gtk_Widget_Record'Class);

      On_Perspective_Changed_Update_Menu_Access : constant
         Gtkada.Handlers.Widget_Callback.Simple_Handler :=
            On_Perspective_Changed_Update_Menu'Access;

      Registers : Register_Node;
      --  Global variable that contains the list of functions that have been
      --  registered.
   end Desktop;

   function Desktop_Was_Loaded (MDI : access MDI_Window_Record) return Boolean;
   --  Return True if a desktop was loaded, False if the MDI is only the result
   --  of calls to Gtk_New and Put.

   function List_Of_Perspectives
     (MDI : access MDI_Window_Record)
      return GNAT.Strings.String_List_Access;
   --  Return the list of perspectives known to the MDI. The caller must not
   --  free the list

   function Current_Perspective
     (MDI : access MDI_Window_Record'Class) return String;
   --  Return the name of the currently displayed perspective

   -------------
   -- Signals --
   -------------

   --  <signals>
   --  The following new signals are defined for this widget:
   --
   --  - "child_selected"
   --    procedure Handler
   --       (MDI : access MDI_Window_Record'Class; Child : System.Address);
   --
   --    This signal is emitted when a new child has gained the focus. Convert
   --    Child to a MDI_Child by calling Gtk.Arguments.To_Object. This can be
   --    used to change some global information at the MDI level. You should
   --    connect to "selected" (see below) instead if you want to change some
   --    information at the child level.
   --    Child might be null if no child has the focus anymore
   --
   --  - "float_child"
   --  - "unfloat_child"
   --    procedure Handler
   --       (MDI : access MDI_Window_Record'Class; Child : System.Address);
   --
   --    A child was set as floating in the MDI. A similar signal is emitted on
   --    the child itself.
   --
   --  - "child_title_changed"
   --    procedure Handler
   --      (MDI : access MDI_Window_Record'Class; Child : System.Address);
   --
   --    Emitted when the title of a child is changed. This signal is not
   --    emitted if Set_Title is called for a child that hasn't been put in the
   --    MDI yet.
   --
   --  - "child_added"
   --     procedure Handler
   --       (MDI : access MDI_Window_Record'Class; Child : System.Address);
   --     Emitted when a new child is added. You cannot use the "add" signal
   --     since in fact the children are added to notebooks that are part of
   --     the MDI, and thus "add" is only emitted when a new notebook is
   --     created.
   --
   --  - "child_removed"
   --     procedure Handler
   --       (MDI : access MDI_Window_Record'Class; Child : System.Address);
   --     Emitted when a new child is removed. You cannot use the "remove"
   --     signal since in fact the children are removed from notebooks that are
   --     part of the MDI, and thus "remove" is only emitted when a new
   --     notebook is destroyed.
   --     When this signal is emitted, Child no longer contains a widget, and
   --     is no longer part of the children, although you can still access its
   --     titles.
   --
   --  - "child_icon_changed"
   --     procedure Handler
   --       (MDI : access MDI_Window_Record'Class; Child : System.Address);
   --     Emitted when the icon for Child has changed
   --
   --  - "children_reorganized"
   --     procedure Handler (MDI : access MDI_Window_Record'Class);
   --     Emitted when the children have been reorganized: either a split
   --     occurred, or a window was dropped into another position
   --
   --  - "perspective_changed"
   --     procedure Handler (MDI : access MDI_Window_Record'Class);
   --     Called when the user has selected a new perspective. One use is to
   --     save the new desktop to a file.
   --
   --  - "perspectives_added"
   --     procedure Handler
   --        (MDI : access MDI_Window_Record'Class; Name : String);
   --     Called when a new perspective is created
   --
   --  </signals>
   --
   --  <signals>
   --  The following new signals are defined for the MDI_Child_Record object:
   --
   --  - "delete_event"
   --    function Handler (Child : access Gtk_Widget_Record'Class)
   --                     return Boolean;
   --
   --    This signal is emitted for each item in the MDI window before it is
   --    actually deleted. The child is destroyed only if the handler returns
   --    False.
   --    Note that the Child passed in argument is exactly the one you passed
   --    to Put to insert it in the MDI window.
   --    Note that this is also the signal to use to prevent top level
   --    Gtk_Window from being destroyed.
   --
   --  - "selected"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    This is emitted when the child is selected, ie gains the
   --    MDI focus. You should probably also connect to the "grab_focus" signal
   --    to be informed when the child gets the keyboard focus. This can be
   --    used to transfer the focus to some specific part of the
   --    widget. Connecting to "grab_focus" should be done with the After
   --    parameter set to True.
   --
   --  - "float_child"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    Emitted when a child is set as floating
   --
   --  - "unfloat_child"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    Emitted when a child is put back in the main MDI window
   --
   --  - "before_unfloat_child"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    Emitted when a child is about to be put back in the main MDI window
   --
   --  - "child_state_changed"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    Emitted when the state of the child has changed. See the function
   --    Get_State. In particular, this signal can be detected when a child is
   --    removed from the current perspective (the new state is "invisible"),
   --    and when it is put back (the new state is "normal" or "floating").
   --
   --  - "before_destroy_child"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    Emitted when a child is about to be destroyed
   --
   --  - "before_remove_child"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    Emitted when a child is about to be removed from its MDI_Notebook
   --
   --  - "double_click_child_tab"
   --    procedure Handler (Child : access MDI_Child_Record'Class);
   --
   --    Emitted when double clicking on the notebook's tab of a MDI_Child
   --
   --  </signals>

   type Cb_Gtkada_MDI_Window_MDI_Child_Void is not null access procedure
     (Self  : access MDI_Window_Record'Class;
      Child : not null access MDI_Child_Record'Class);

   type Cb_GObject_MDI_Child_Void is not null access procedure
     (Self  : access Glib.Object.GObject_Record'Class;
      Child : not null access MDI_Child_Record'Class);

   Signal_Child_Selected       : constant Signal_Name := "child_selected";
   procedure On_Child_Selected
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Child_Selected
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Float_Child          : constant Signal_Name := "float_child";
   procedure On_Float_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Float_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Child_Title_Changed  : constant Signal_Name := "child_title_changed";
   procedure On_Child_Title_Changed
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Child_Title_Changed
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Child_Added          : constant Signal_Name := "child_added";
   procedure On_Child_Added
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Child_Added
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Child_Removed        : constant Signal_Name := "child_removed";
   procedure On_Child_Removed
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Child_Removed
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Child_Icon_Changed   : constant Signal_Name := "child_icon_changed";
   procedure On_Child_Icon_Changed
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Child_Icon_Changed
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Delete_Event         : constant Signal_Name := "delete_event";
   procedure On_Delete_Event
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Delete_Event
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Unfloat_Child        : constant Signal_Name := "unfloat_child";
   procedure On_Unfloat_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Unfloat_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Before_Unfloat_Child : constant Signal_Name :=
     "before_unfloat_child";
   procedure On_Before_Unfloat_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Before_Unfloat_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Before_Destroy_Child : constant Signal_Name :=
     "before_destroy_child";
   procedure On_Before_Destroy_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Before_Destroy_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Before_Remove_Child  : constant Signal_Name := "before_remove_child";
   procedure On_Before_Remove_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_Gtkada_MDI_Window_MDI_Child_Void;
      After : Boolean := False);
   procedure On_Before_Remove_Child
     (Self  : not null access MDI_Window_Record'Class;
      Call  : Cb_GObject_MDI_Child_Void;
      Slot  : not null access Glib.Object.GObject_Record'Class;
      After : Boolean := False);

   Signal_Selected             : constant Signal_Name := "selected";
   Signal_Perspective_Changed  : constant Signal_Name := "perspective_changed";
   Signal_Perspectives_Added   : constant Signal_Name := "perspectives_added";
   Signal_Children_Reorganized : constant Signal_Name :=
                                   "children_reorganized";
   Signal_Child_State_Changed  : constant Signal_Name := "child_state_changed";
   Signal_Maximize_Child       : constant Signal_Name := "maximize_child";
   Signal_Unmaximize           : constant Signal_Name := "unmaximize";

   procedure Child_Selected
      (Self  : not null access MDI_Window_Record'Class;
       Child : access MDI_Child_Record'Class := null);
   --  Emit the "child_selected" signal

private
   type Saved_Perspective_Record is record
      Height    : Gint;
      Width     : Gint;
      --  The sizes of the maximized container

      Container : Gtk.Widget.Gtk_Widget;
      --  The Container currently maximized
   end record;

   type Saved_Perspective is access all Saved_Perspective_Record;

   package Saved_Perspectives_Map is new Ada.Containers.Indefinite_Hashed_Maps
     (Key_Type        => String,
      Element_Type    => Saved_Perspective,
      Hash            => Ada.Strings.Hash,
      Equivalent_Keys => "=",
      "="             => "=");
   use Saved_Perspectives_Map;

   type String_Access is access all UTF8_String;

   type MDI_Child_Record is new Gtk.Event_Box.Gtk_Event_Box_Record with record
      Initial       : Gtk.Widget.Gtk_Widget;
      --  The widget we use to build this child.

      Main_Box      : Gtk.Box.Gtk_Box;
      --  The main container.

      State         : State_Type := Normal;
      Group         : Child_Group := Group_Default;
      Areas         : Allowed_Areas := Both;

      Title         : String_Access;
      Short_Title   : String_Access;
      --  Title of the item, as it appears in the title bar.
      --  These are UTF8-Encoded

      XML_Node_Name : String_Access;
      --  The name of the XML node when this child is saved in a desktop (if
      --  we know it). This is used to reuse a child when switching
      --  perspectives.

      MDI           : MDI_Window;
      --  The MDI to which the child belongs. We cannot get this information
      --  directly from Get_Parent since some children are actually floating
      --  and do not belong to the MDI anymore.

      Flags         : Child_Flags;

      Focus_Widget  : Gtk.Widget.Gtk_Widget;
      --  The widget which should actually get the keyboard focus

      Title_Box     : Gtk.Box.Gtk_Box;
      Title_Label   : Gtk.Label.Gtk_Label;
      Title_Icon    : Gtk.Image.Gtk_Image;
      --  Box that contains the title. It will be resized whenever the title
      --  font changes.

      Tab_Label     : Gtk.Label.Gtk_Label;
      Tab_Icon      : Gtk.Image.Gtk_Image;
      --  label used when child is in a notebook, null if not in a notebook

      Icon_Name     : GNAT.Strings.String_Access;

      Notebook_Before_Floating : Gtk.Notebook.Gtk_Notebook := null;
      --  The original notebook of a floating child. Used to put the child
      --  back in the same notebook when unfloating it.
   end record;

   type Child_Iterator (Group_By_Notebook : Boolean := False) is record
      Visible_Only : Boolean;

      case Group_By_Notebook is
         when False =>
            Iter : Gtk.Widget.Widget_List.Glist;

         when True =>
            MDI                 : MDI_Window;

            --  While iterating children
            Paned_Iter          : Gtkada.Multi_Paned.Child_Iterator;

            --  Whether we have already visited the children of the central
            --  area. This is True while iterating them, False afterward
            In_Central          : Boolean;

            --  While iterating the pages of a specific notebook (notebook is
            --  set to null when returning floating children)
            Notebook            : Gtk.Notebook.Gtk_Notebook;
            Notebook_Page       : Glib.Gint;

            --  While iterating the floating children
            Floating_Iter       : Gtk.Widget.Widget_List.Glist;
      end case;
   end record;

   type Drag_Status is (No_Drag, In_Pre_Drag, In_Drag);

   type MDI_Window_Record is new Gtkada.Multi_Paned.Gtkada_Multi_Paned_Record
   with record
      Items : Gtk.Widget.Widget_List.Glist := Gtk.Widget.Widget_List.Null_List;
      --  The list of all MDI children. This includes children in the editor
      --  area, even though they are technically in a separate multi_paned.
      --  Warning: this list might contain items which are in fact invisible in
      --  the MDI (in fact that are not even children of the MDI), if they
      --  existed in a previous perspective but no longer in the current one.

      Desktop_Was_Loaded : Boolean := False;
      --  True if a desktop was loaded

      Loading_Desktop : Boolean := False;
      --  Whether we are currently loading the desktop. This impacts a number
      --  of focus and sizing parameters, so that the desktop can be restored
      --  as accurately as possible.

      Focus_Child : MDI_Child := null;
      --  The child that currently has the focus. Some default actions will
      --  apply to this child only.

      Focus_Freeze : Natural := 0;
      --  We should not emit any focus change if this is > 0.

      Dnd_Message : String_Access;
      --  The message displayed during a dnd operation (see Set_Dnd_Message)

      Accel_Path_Prefix   : String_Access;
      --  The Accel path used for the dynamic menu

      Menu_Model          : Glib.Menu.Gmenu;
      Menu_Items_Section  : Glib.Menu.Gmenu;
      Application         : Gtk.Application.Gtk_Application;
      Action_Float        : Glib.Simple_Action.Gsimple_Action;
      Action_Select       : Glib.Simple_Action.Gsimple_Action;
      Action_Close        : Glib.Simple_Action.Gsimple_Action;
      Action_Select_Perspective : Glib.Simple_Action.Gsimple_Action;
      Perspectives_Menu   : Glib.Menu.Gmenu;

      Freeze_Float_Menu   : Boolean := False;

      Tab_Factory         : Tab_Contextual_Menu_Factory;
      --  Build the contextual menu when right-clicking on tabs

      Title_Bar_Height    : Glib.Gint;
      --  Height of the title bar for all the children

      Title_Font : Pango.Font.Pango_Font_Description;
      --  The font used for the titles of the children

      Close_Floating_Is_Unfloat : Boolean;
      --  True if destroying a floating window will put the child back in the
      --  MDI instead of destroying it. False if the child should be destroyed
      --  (provided it accepts so in its delete_event handler).

      Title_Bar_Color     : Gdk.RGBA.Gdk_RGBA := Gdk.RGBA.Null_RGBA;
      Focus_Title_Color   : Gdk.RGBA.Gdk_RGBA := Gdk.RGBA.Null_RGBA;
      Default_Title_Color : Gdk.RGBA.Gdk_RGBA := Gdk.RGBA.Null_RGBA;
      Css_Provider        : Gtk.Css_Provider.Gtk_Css_Provider;

      Cursor_Cross        : Gdk.Gdk_Cursor;
      Cursor_Fleur        : Gdk.Gdk_Cursor;
      --  Cached cursors

      Draw_Title_Bars   : Title_Bars_Policy := Always;
      Tabs_Position     : Gtk.Enums.Gtk_Position_Type := Gtk.Enums.Pos_Top;
      Tabs_Orientation  : Tab_Orientation_Type := Automatic;
      Show_Tabs_Policy  : Show_Tabs_Policy_Enum := Automatic;

      Selection_Dialog : Gtk.Widget.Gtk_Widget;
      --  The interactive dialog for selecting new children.

      Group : Gtk.Accel_Group.Gtk_Accel_Group;

      All_Floating_Mode : Boolean := False;
      --  Set to true if all windows should be set to floating

      Independent_Perspectives : Boolean := False;
      --  See documentation for Configure.

      Use_Short_Titles_For_Floats : Boolean := False;
      --  Set to true if all floating children should use their short titles.
      --  This also applies to docked windows.

      Homogeneous_Tabs : Boolean := True;

      --  Handling of Dnd
      Drag_Start_X, Drag_Start_Y : Gint;
      In_Drag           : Drag_Status := No_Drag;
      Dnd_Rectangle     : Gdk.Rectangle.Gdk_Rectangle;  --  Highlighted area
      Old_Dnd_Position  : Child_Position := Position_Automatic;

      Dnd_Rectangle_Real : Gdk.Rectangle.Gdk_Rectangle;
      --  Area to redraw to delete the Dnd overlay window

      Dnd_Target        : Gdk.Gdk_Window;     --  The current target for DND

      Dnd_Overlay : Cairo.Cairo_Surface := Cairo.Null_Surface;

      Drag_Areas : Allowed_Areas;
      --  The allowed areas during a drag

      --  Loaded perspectives
      Perspectives           : Glib.Xml_Int.Node_Ptr;
      View_Contents          : Glib.Xml_Int.Node_Ptr;
      Perspective_Names      : GNAT.Strings.String_List_Access;
      Central                : Gtkada.Multi_Paned.Gtkada_Multi_Paned;

      Current_Perspective    : Glib.Xml_Int.Node_Ptr;
      --  pointer into Perspectives
      Saved_Sizes            : Saved_Perspectives_Map.Map;
      --  Map containing the sizes of the MDI children before the last
      --  child maximization

      Internal_Updating_Menu : Boolean := False;
      --  Flag to prevent giving focus to a child in response to a menu being
      --  toggled when it is the MDI itself that toggles the menu.
   end record;

   pragma Inline (Get_Widget);
   pragma Inline (Get_Focus_Child);
   pragma Inline (Get);
   pragma Inline (Next);
   pragma Inline (First_Child);

end Gtkada.MDI;
