----------------------------------------------------------------------- -- GtkAda - Ada95 binding for Gtk+/Gnome -- -- -- -- Copyright (C) 2001-2011, AdaCore -- -- -- -- This library is free software; you can redistribute it and/or -- -- modify it under the terms of the GNU General Public -- -- License as published by the Free Software Foundation; either -- -- version 2 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 -- -- General Public License for more details. -- -- -- -- You should have received a copy of the GNU General Public -- -- License along with this library; if not, write to the -- -- Free Software Foundation, Inc., 59 Temple Place - Suite 330, -- -- Boston, MA 02111-1307, USA. -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ----------------------------------------------------------------------- -- TODO: -- - handles multiple views of the MDI (through several top-level windows) -- - Add support for groups (children are associated with groups, and groups -- can have special colors, can be minimized,...). Groups could be -- implemented as special MDI_Children ? -- - Manipulation of the title bar for children (adding buttons, adding -- pixmaps,...) -- - Automatically add a new menu bar when a child is floated (settable -- on a per-child basis). -- - contextual menu in the title bar of children to dock them, float them,... with Ada.Unchecked_Deallocation; with Ada.Strings.Fixed; use Ada.Strings.Fixed; with Ada.Characters.Handling; use Ada.Characters.Handling; with Ada.Tags; use Ada.Tags; with Ada.Exceptions; use Ada.Exceptions; with System; use System; with System.Address_Image; with Interfaces.C.Strings; use Interfaces.C.Strings; with GNAT.IO; use GNAT.IO; with GNAT.Strings; use GNAT.Strings; with Glib.Convert; use Glib.Convert; with Glib.Main; use Glib.Main; with Glib.Object; use Glib.Object; with Glib.Properties; use Glib.Properties; with Cairo; use Cairo; with Pango.Cairo; use Pango.Cairo; with Pango.Font; use Pango.Font; with Pango.Layout; use Pango.Layout; with Gdk; use Gdk; with Gdk.Cairo; use Gdk.Cairo; with Gdk.Color; use Gdk.Color; with Gdk.Cursor; use Gdk.Cursor; with Gdk.Drawable; use Gdk.Drawable; with Gdk.Event; use Gdk.Event; with Gdk.Main; use Gdk.Main; with Gdk.Pixbuf; use Gdk.Pixbuf; with Gdk.Rectangle; use Gdk.Rectangle; with Gdk.Screen; use Gdk.Screen; with Gdk.Types; use Gdk.Types; with Gdk.Types.Keysyms; with Gdk.Window; use Gdk.Window; with Gtk; use Gtk; with Gtk.Accel_Group; use Gtk.Accel_Group; with Gtk.Accel_Label; use Gtk.Accel_Label; with Gtk.Arguments; use Gtk.Arguments; with Gtk.Box; use Gtk.Box; with Gtk.Button; use Gtk.Button; with Gtk.Check_Menu_Item; use Gtk.Check_Menu_Item; with Gtk.Container; use Gtk.Container; with Gtk.Dialog; use Gtk.Dialog; with Gtk.Enums; use Gtk.Enums; with Gtk.Event_Box; use Gtk.Event_Box; with Gtk.Fixed; use Gtk.Fixed; with Gtk.Frame; use Gtk.Frame; with Gtk.GEntry; use Gtk.GEntry; with Gtk.Image; use Gtk.Image; with Gtk.Label; use Gtk.Label; with Gtk.Main; use Gtk.Main; pragma Elaborate_All (Gtk.Main); with Gtk.Menu; use Gtk.Menu; with Gtk.Menu_Item; use Gtk.Menu_Item; with Gtk.Notebook; use Gtk.Notebook; with Gtk.Object; use Gtk.Object; with Gtk.Radio_Menu_Item; use Gtk.Radio_Menu_Item; with Gtk.Rc; with Gtk.Separator_Menu_Item; use Gtk.Separator_Menu_Item; with Gtk.Stock; use Gtk.Stock; with Gtk.Style; use Gtk.Style; with Gtk.Widget; use Gtk.Widget; with Gtk.Window; use Gtk.Window; with Gtkada.Handlers; use Gtkada.Handlers; with Gtkada.Multi_Paned; use Gtkada.Multi_Paned; with Gtkada.Types; package body Gtkada.MDI is use Glib.Xml_Int; Traces : constant Boolean := False; -- True if traces should be activated Traces_Indent : Natural := 0; Default_Title_Bar_Focus_Color : constant String := "#000088"; -- Default color to use for the title bar of the child that has -- the focus. Default_Title_Bar_Color : constant String := "#AAAAAA"; -- Default color to use for the title bar of children that do not -- have the focus. Default_MDI_Background_Color : constant String := "#666666"; -- Default background color to use for the MDI window Default_Title_Font : constant String := "Sans 8"; -- Default title font for the children Max_Drag_Border_Width : constant Gint := 30; -- Width or height of the drag-and-drop borders for each notebook. On the -- sides of the MDI, half of it is dedicated to moving the window so that -- it occupies that whole side of the MDI Drag_Threshold : constant Gint := 20; -- Our own threshold (instead of Gtk.Dnd.Check_Threshold), since on -- Windows the later seems to be set to 0, and thus we can't change a -- notebook page by clicking on its tab without splitting the notebook MDI_Class_Record : Gtk.Object.GObject_Class := Gtk.Object.Uninitialized_Class; Child_Class_Record : Gtk.Object.GObject_Class := Gtk.Object.Uninitialized_Class; MDI_Signals : constant chars_ptr_array := (1 => New_String (String (Signal_Child_Selected)), 2 => New_String (String (Signal_Float_Child)), 3 => New_String (String (Signal_Child_Title_Changed)), 4 => New_String (String (Signal_Child_Added)), 5 => New_String (String (Signal_Child_Removed)), 6 => New_String (String (Signal_Child_Icon_Changed)), 7 => New_String (String (Signal_Children_Reorganized)), 8 => New_String (String (Signal_Perspective_Changed))); Child_Signals : constant chars_ptr_array := (1 => New_String (String (Signal_Float_Child)), 2 => New_String (String (Signal_Unfloat_Child)), 3 => New_String (String (Signal_Selected)), 4 => New_String (String (Signal_Child_State_Changed))); use Widget_List; type Selection_Dialog_Record is new Gtk_Window_Record with record Current_Child : Widget_List.Glist; Label : Gtk_Label; Ent : Gtk_Entry; Length : Natural := 0; Modifier : Gdk_Modifier_Type; Icon : Gtk.Image.Gtk_Image; end record; type Selection_Dialog_Access is access all Selection_Dialog_Record'Class; type MDI_Notebook_Record is new Gtk_Notebook_Record with null record; -- The type of notebooks used in the MDI. package Child_User_Data is new Glib.Object.User_Data (MDI_Child); type Children_Array is array (Natural range <>) of Widget_List.Glist; procedure Free is new Ada.Unchecked_Deallocation (UTF8_String, String_Access); function Button_Pressed (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Called when the user has pressed the mouse button in the canvas. -- Test whether an item was selected. function Button_Pressed_Forced (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Same as above, except we also act even if the event wasn't started in -- Child's window. function Button_Release (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Called when the user has released the mouse button. -- If an item was selected, refresh the canvas. function Button_Motion (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Called when the user moves the mouse while a button is pressed. -- If an item was selected, the item is moved. function On_Notebook_Button_Press (Child : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event) return Boolean; -- Manage the contextual menu on tabs procedure Child_Widget_Shown (Widget : access Gtk_Widget_Record'Class); procedure Child_Widget_Hidden (Widget : access Gtk_Widget_Record'Class); -- Called when the child widget is shown or hidden by the user, to reflect -- that fact at the MDI_Child level, no matter whether the child is -- currently floating or not. function Insert_Child_If_Needed (MDI : access MDI_Window_Record'Class; Child : MDI_Child) return MDI_Child; -- If the child is currently invisible in the perspective, insert it back -- in the MDI. In both case, return the child itself function Create_Notebook (MDI : access MDI_Window_Record'Class) return Gtk_Notebook; -- Create a notebook, and set it up for drag-and-drop procedure Configure_Notebook_Tabs (MDI : access MDI_Window_Record'Class; Notebook : access Gtk_Notebook_Record'Class; Hide_If_Empty : Boolean := False); -- Configure the visibility and position of notebook tabs. -- If there are no visible pages and Hide_If_Empty is true, then the -- notebook itself is hidden procedure Update_Tab_Color (Child : access MDI_Child_Record'Class); -- Change the background color of the notebook tab containing child, -- depending on whether the child is selected or not. procedure Set_State (Child : access MDI_Child_Record'Class; New_State : State_Type); -- Change the state of Child function Delete_Child (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Forward a delete_event from the toplevel window to the child procedure Destroy_Child (Child : access Gtk_Widget_Record'Class); procedure Destroy_Initial_Child (Child : access Gtk_Widget_Record'Class); -- Called when either the child itself, or the widget we initially put -- in it, are destroyed. Remove the child from the MDI properly. procedure Destroy_MDI (MDI : access Gtk_Widget_Record'Class); -- Called when the MDI is destroyed procedure Menu_Entry_Destroyed (Child : access Gtk_Widget_Record'Class); -- Called when the Menu_Item associated with a Child is destroyed procedure Menu_Destroyed (MDI : access Gtk_Widget_Record'Class); -- Called when the Menu associated with a MDI is destroyed function Draw_Child (Widget : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Draw the child (and the title bar) procedure Realize_MDI (MDI : access Gtk_Widget_Record'Class); -- Called when the child is realized procedure Set_Dnd_Source (Widget : access Gtk_Widget_Record'Class; Child : access Gtk_Widget_Record'Class); -- Setup a widget as either a source or a target for drag-and-drop ops. procedure Get_Dnd_Target (MDI : access MDI_Window_Record'Class; Parent : out Gtk_Widget; Position : out Child_Position; Rectangle : out Gdk_Rectangle); -- Return the widget that is the current target for dnd -- Position indicated where in the parent the child would be dropped: -- Position_Bottom .. Position_Right: To one of the sides -- Position_Automatic: In the center type Dnd_Rectangle_Mode is (Show, Hide, Destroy); procedure Draw_Dnd_Rectangle (MDI : access MDI_Window_Record'Class; Mode : Dnd_Rectangle_Mode; Ref_Window : Gdk.Gdk_Window := null); -- Draw the DND rectangle procedure Update_Float_Menu (Child : access MDI_Child_Record'Class); -- Update the state of the "Float" menu item associated with child procedure Put_In_Notebook (MDI : access MDI_Window_Record'Class; Child : access MDI_Child_Record'Class; Notebook : Gtk_Notebook := null; Initial_Position : Child_Position := Position_Automatic; Force_Parent_Destruction : Boolean := True); -- Remove Child from MDI, and put it under control of a notebook. -- Notebook can be used to specify a specific notebook to which the child -- should be added. If null, this function will compute what notebook -- should be used or created depending on the Child's position attribute. -- If Force_Parent_Destruction is True, then the notebook containing the -- Child will always be destroyed if Child was its last child. Otherwise, -- it is possible that the notebook will be kept, albeit empty. function Get_Notebook (Child : access MDI_Child_Record'Class) return Gtk_Notebook; -- Return the notebook that directly contains Child procedure Create_Menu_Entry (Child : access MDI_Child_Record'Class); -- Add an entry to the MDI menu that provides easy activation of Child procedure Split_H_Cb (MDI : access Gtk_Widget_Record'Class); procedure Split_V_Cb (MDI : access Gtk_Widget_Record'Class); procedure Float_Cb (MDI : access Gtk_Widget_Record'Class); procedure Close_Cb (MDI : access Gtk_Widget_Record'Class); procedure Focus_Cb (Child : access Gtk_Widget_Record'Class); -- Callbacks for the menu procedure Set_Focus_Child_MDI (MDI : access Gtk_Widget_Record'Class; Args : Gtk_Args); procedure Set_Focus_Child_Notebook (Note : access Gtk_Widget_Record'Class; Args : Gtk_Args); -- Called when the widget that has the keyboard focus has changed. This is -- used to automatically select its parent MDI_Child. package Widget_Sources is new Glib.Main.Generic_Sources (Gtk_Widget); function After_Focus_Child_MDI_Floating (Child : Gtk_Widget) return Boolean; -- Called after a short delay when a floating window has received the -- "focus_in" event. This ensure we only give the MDI focus if the user -- has left the mouse on top of that window, not just passing through. function Set_Focus_Child_MDI_Floating (Child : access Gtk_Widget_Record'Class) return Boolean; -- Same as Set_Focus_Child_MDI, but for floating windows function Set_Focus_Child_MDI_From_Tab (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Gives the focus to Child when the notebook tab associated with it is -- pressed. procedure Set_Focus_Child_Switch_Notebook_Page (Note : access Gtk_Widget_Record'Class; Args : Gtk_Args); -- Called when a new page from a notebook has been selected, in particular -- when using the scroll arrows when there are too many pages to be -- displayed function Toplevel_Focus_In (MDI : access Gtk_Widget_Record'Class) return Boolean; -- Called when the toplevel window that contains a the MDI gains the focus -- from the window manager procedure Give_Focus_To_Child (Child : MDI_Child); -- Give the focus to a specific MDI child -- You should never call Grab_Focus directly procedure Give_Focus_To_Previous_Child (Child : access MDI_Child_Record'Class); -- Give focus to the last child in the same area/notebook as Child, and -- which is not Child itself. function Matching_Children (MDI : access MDI_Window_Record'Class; Str : String) return Children_Array; -- Return the list of children of the MDI that match Str procedure Reset_Title_Bars_And_Colors (MDI : access MDI_Window_Record'Class); -- Reset the color and title bar of the MDI Child procedure Update_Selection_Dialog (MDI : access MDI_Window_Record'Class; Increment : Integer); -- Update the currently selected child in the selection dialog, so that it -- matches the filter. function Key_Event_In_Floating (Win : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Forward the key press event to the Win function Key_Event_Selection_Dialog (MDI : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean; -- Handle key events in the selection dialog procedure Update_Tab_Label (Child : access MDI_Child_Record'Class); -- Return the tab to use in the notebooks containing Child procedure Update_Menu_Item (Child : access MDI_Child_Record'Class); -- Update the menu entry for Child function Find_Current_In_Central (Pane : access Gtkada_Multi_Paned_Record'Class; MDI : access MDI_Window_Record'Class; Group : Child_Group := Group_Any; Initial_Position : Child_Position := Position_Automatic) return Gtk_Notebook; -- Return the first notebook that contains at least one child within the -- given Group. The search starts in the notebook that currently has the -- focus. -- A new notebook is created if needed (ie if no notebook has a child with -- the same attribute). procedure Removed_From_Notebook (Note : access Gtk_Widget_Record'Class; Args : Gtk_Args); -- Called when a child is removed from one of the notebooks procedure Update_Dnd_Window (MDI : access MDI_Window_Record'Class; Text : String; In_Central : Boolean); -- Create and update the contents of the small window displayed while a -- drag-and-drop operation is taking place. -- In_Central should be True if the window will be part of the central area procedure Destroy_Dnd_Window (MDI : access MDI_Window_Record'Class); -- Destroy the small window displayed while a drag-and-drop operation is -- taking place. procedure Emit_By_Name_Child (Object : System.Address; Name : String; Child : System.Address); pragma Import (C, Emit_By_Name_Child, "ada_g_signal_emit_by_name_ptr"); procedure Emit_By_Name (Object : System.Address; Name : String); pragma Import (C, Emit_By_Name, "ada_g_signal_emit_by_name"); procedure Internal_Float_Child (Child : access MDI_Child_Record'Class; Float : Boolean; Position_At_Mouse : Boolean; X, Y : Gint); -- Internal version of Float_Child, where the user can choose whether the -- new floating window should be located where the mouse is, or at -- coordinates specified by (X, Y) procedure Set_Child_Title_Bar (Child : access MDI_Child_Record'Class); -- Hide or display the title bar of the child, depending on its status. procedure Note_Notify (Data : System.Address; Where : System.Address); pragma Convention (C, Note_Notify); -- Notified if the old notebook that contained Child is destroyed procedure Print_Debug (Msg : String); procedure Indent_Debug (Amount : Integer); -- Debug support function In_Central_Area (MDI : access MDI_Window_Record'Class; Child : access Gtk_Widget_Record'Class) return Boolean; -- Whether Child is in the central area procedure Move_To_Next_Notebook (Iterator : in out Child_Iterator); -- Move to the next notebook for this iterator (does nothing if Iterator -- already points to a notebook). package Close_Button is -- We use an event box as a basis so that we have a gdk_window -- available for handling mouse events. We'll set this event box as -- transparent to be able to draw transparent buttons as we wish. type Gtkada_MDI_Close_Button_Record is new Gtk_Event_Box_Record with record Child : MDI_Child; -- The child this button is attached to Tab_Over : Boolean; -- Wether the mouse is over the button's container Over : Boolean; -- Wether the mouse is over the button Pressed : Boolean; -- Wether the button is pressed In_Titlebar : Boolean; -- Wether the button is in the title bar or in the tab Default_Size : Glib.Gint; -- The button's default size. The actual drawing depends on the final -- allocated space. end record; type Gtkada_MDI_Close_Button is access all Gtkada_MDI_Close_Button_Record'Class; procedure Gtk_New (Button : out Gtkada_MDI_Close_Button; Tab : access Gtk_Widget_Record'Class; Child : access MDI_Child_Record'Class; In_Titlebar : Boolean); -- Tab: the button's container. This container shall have a Gdk_Window -- to allow mouse motion event retrieval. -- Child: the MDI child that button is attached to. This child is closed -- upon button click. -- In_Titlebar: set to True if the button is in the title bar, to false -- if it's in the notebook tab. end Close_Button; package body Close_Button is separate; --------------------- -- In_Central_Area -- --------------------- function In_Central_Area (MDI : access MDI_Window_Record'Class; Child : access Gtk_Widget_Record'Class) return Boolean is P : Gtk_Widget := Get_Parent (Child); begin while P /= null and then P /= Gtk_Widget (MDI) loop if P = Gtk_Widget (MDI.Central) then return True; end if; P := Get_Parent (P); end loop; return False; end In_Central_Area; ----------------- -- Print_Debug -- ----------------- procedure Print_Debug (Msg : String) is begin if Traces then Put_Line ((1 .. Traces_Indent => ' ') & "MDI: " & Msg); end if; end Print_Debug; ------------------ -- Indent_Debug -- ------------------ procedure Indent_Debug (Amount : Integer) is begin Traces_Indent := Traces_Indent + Amount; end Indent_Debug; --------------- -- Set_State -- --------------- procedure Set_State (Child : access MDI_Child_Record'Class; New_State : State_Type) is Old_State : constant State_Type := Child.State; begin if New_State /= Old_State then Child.State := New_State; Widget_Callback.Emit_By_Name (Child, Signal_Child_State_Changed); end if; end Set_State; ------------------ -- Get_Notebook -- ------------------ function Get_Notebook (Child : access MDI_Child_Record'Class) return Gtk_Notebook is begin case Child.State is when Floating => return null; when Invisible => return null; when Normal => if Get_Parent (Child) /= null and then Get_Parent (Child).all in Gtk_Notebook_Record'Class then return Gtk_Notebook (Get_Parent (Child)); end if; return null; end case; end Get_Notebook; ------------------------- -- Set_Focus_Child_MDI -- ------------------------- procedure Set_Focus_Child_MDI (MDI : access Gtk_Widget_Record'Class; Args : Gtk_Args) is Widget : Gtk_Widget := Gtk_Widget (To_Object (Args, 1)); begin -- This code must always be executed (we cannot take into account -- whether the MDI has the focus or not). Otherwise, clicking -- inside an open editor in GPS, for instance, will not properly give -- the focus to the MDI child if Widget /= null then -- The widget is currently either a notebook or the Gtk_Fixed. Get -- its focus widget, which is the one we are really interested in. Widget := Get_Focus_Child (Gtk_Container (Widget)); if Widget /= null then Print_Debug ("Set_Focus_Child_MDI"); Set_Focus_Child (MDI_Window (MDI), Containing => Widget); end if; end if; -- No need to call the parent's set_focus_child, this is called -- automatically when the signal is propagated. end Set_Focus_Child_MDI; ------------------------------------------ -- Set_Focus_Child_Switch_Notebook_Page -- ------------------------------------------ procedure Set_Focus_Child_Switch_Notebook_Page (Note : access Gtk_Widget_Record'Class; Args : Gtk_Args) is N : constant Gtk_Notebook := Gtk_Notebook (Note); Page : constant Guint := To_Guint (Args, 2); Child : MDI_Child; begin Child := MDI_Child (Get_Nth_Page (N, Gint (Page))); if Child /= null then Print_Debug ("Set_Focus_Child_Switch_Notebook_Page " & Get_Title (Child)); Set_Focus_Child (Child); end if; end Set_Focus_Child_Switch_Notebook_Page; ------------------------------ -- Set_Focus_Child_Notebook -- ------------------------------ procedure Set_Focus_Child_Notebook (Note : access Gtk_Widget_Record'Class; Args : Gtk_Args) is pragma Unreferenced (Note); Widget : constant Gtk_Widget := Gtk_Widget (To_Object (Args, 1)); begin -- This code must always be executed (we cannot take into account -- whether the notebook has the focus or not). Otherwise, clicking -- inside an open editor in GPS, for instance, will not properly give -- the focus to the MDI child if Widget /= null and then Widget.all in MDI_Child_Record'Class then Print_Debug ("Set_Focus_Child_Notebook " & Get_Title (MDI_Child (Widget))); Set_Focus_Child (MDI_Child (Widget)); end if; end Set_Focus_Child_Notebook; ------------------------------------ -- After_Focus_Child_MDI_Floating -- ------------------------------------ function After_Focus_Child_MDI_Floating (Child : Gtk_Widget) return Boolean is C : constant MDI_Child := MDI_Child (Child); Top : constant Gtk_Widget := Get_Toplevel (Get_Widget (C)); begin if Top /= null and then Top.all in Gtk_Window_Record'Class and then Has_Toplevel_Focus (Gtk_Window (Top)) then Set_Focus_Child (C); end if; return False; end After_Focus_Child_MDI_Floating; ---------------------------------- -- Set_Focus_Child_MDI_Floating -- ---------------------------------- function Set_Focus_Child_MDI_Floating (Child : access Gtk_Widget_Record'Class) return Boolean is C : constant MDI_Child := MDI_Child (Child); begin Print_Debug ("Set_Focus_Child_MDI_Floating"); if C.MDI.Delay_Before_Focus = 0 then Set_Focus_Child (C); else if C.MDI.Delay_Before_Focus_Id /= No_Source_Id then Remove (C.MDI.Delay_Before_Focus_Id); end if; C.MDI.Delay_Before_Focus_Id := Widget_Sources.Timeout_Add (C.MDI.Delay_Before_Focus, After_Focus_Child_MDI_Floating'Access, Gtk_Widget (Child)); end if; return False; end Set_Focus_Child_MDI_Floating; ---------------------------------- -- Set_Focus_Child_MDI_From_Tab -- ---------------------------------- function Set_Focus_Child_MDI_From_Tab (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is Tmp : Boolean; pragma Unreferenced (Tmp); begin if Get_Event_Type (Event) = Button_Release then Tmp := Button_Release (Child => Child, Event => Event); return False; elsif Get_Button (Event) = 1 then -- Let the event through if the child already has the focus. This way -- the notebook tab of the focus child can still be used for -- drag-and-drop if MDI_Child (Child).MDI.Focus_Child = MDI_Child (Child) then return False; else -- Process the button press event to select the child and start a -- drag-and-drop operation Tmp := Button_Pressed_Forced (Child => Child, Event => Event); -- is called as a result of a button_press event in the -- notebook's. tabs The call to Set_Focus_Child above raises the -- child and gives it the focus appropriately. However, if we let -- the signal go through it will be handled by the notebook, which -- will not see change in the current page, and will give the -- focus to the tab itself, not to the page's contents. return True; end if; end if; return False; end Set_Focus_Child_MDI_From_Tab; ----------------------- -- Toplevel_Focus_In -- ----------------------- function Toplevel_Focus_In (MDI : access Gtk_Widget_Record'Class) return Boolean is M : constant MDI_Window := MDI_Window (MDI); begin Print_Debug ("Toplevel_Focus_In"); -- If the current child was a floating window, make sure it keeps the -- focus, and that no one gains the keyboard focus in the main window. -- This avoids a situation where an TextView has the keyboard focus, but -- isn't the MDI focus child. if M.Focus_Child = null then Set_Focus (Gtk_Window (Get_Toplevel (M)), null); elsif M.Focus_Child.State = Floating then Set_Focus (Gtk_Window (Get_Toplevel (M)), null); else -- Make sure the keyboard focus is correctly restored, for instance -- if we had open a temporary dialog and then closed it to go back -- to GPS. Give_Focus_To_Child (M.Focus_Child); end if; return False; end Toplevel_Focus_In; --------------------------- -- Setup_Toplevel_Window -- --------------------------- procedure Setup_Toplevel_Window (MDI : access MDI_Window_Record; Parent : access Gtk.Window.Gtk_Window_Record'Class) is begin Return_Callback.Object_Connect (Parent, Signal_Focus_In_Event, Return_Callback.To_Marshaller (Toplevel_Focus_In'Access), MDI); end Setup_Toplevel_Window; ------------- -- Gtk_New -- ------------- procedure Gtk_New (MDI : out MDI_Window; Group : access Gtk.Accel_Group.Gtk_Accel_Group_Record'Class; Independent_Perspectives : Boolean := False) is begin MDI := new MDI_Window_Record; Gtkada.MDI.Initialize (MDI, Group, Independent_Perspectives); end Gtk_New; ---------------- -- Initialize -- ---------------- procedure Initialize (MDI : access MDI_Window_Record'Class; Group : access Gtk.Accel_Group.Gtk_Accel_Group_Record'Class; Independent_Perspectives : Boolean := False) is Signal_Parameters : constant Glib.Object.Signal_Parameter_Types := (1 => (1 => GType_Pointer), 2 => (1 => GType_Pointer), 3 => (1 => GType_Pointer), 4 => (1 => GType_Pointer), 5 => (1 => GType_Pointer), 6 => (1 => GType_Pointer), 7 => (1 => GType_None), 8 => (1 => GType_None)); begin Gtkada.Multi_Paned.Initialize (MDI); -- Request a null size, so that the window can be resized at will, even -- though we have played with Set_Size_Request on the children. Set_Size_Request (MDI, 0, 0); -- The MDI must have a window, so that we can change the background -- color. No other notebook or paned inside has a window Set_Has_Window (MDI, True); MDI.Group := Gtk_Accel_Group (Group); MDI.Independent_Perspectives := Independent_Perspectives; Set_Dnd_Message (MDI, ""); MDI.Title_Layout := Create_Pango_Layout (MDI, "Ap"); -- compute width MDI.Background_Color := Parse (Default_MDI_Background_Color); Alloc (Get_Default_Colormap, MDI.Background_Color); MDI.Title_Bar_Color := Parse (Default_Title_Bar_Color); Alloc (Get_Default_Colormap, MDI.Title_Bar_Color); MDI.Focus_Title_Color := Parse (Default_Title_Bar_Focus_Color); Alloc (Get_Default_Colormap, MDI.Focus_Title_Color); MDI.Default_Title_Color := Get_Bg (Get_Default_Style, State_Normal); Gtk.Object.Initialize_Class_Record (MDI, Signals => MDI_Signals, Class_Record => MDI_Class_Record, Type_Name => "GtkAdaMDI", Parameters => Signal_Parameters); Configure (MDI, Background_Color => MDI.Background_Color, Title_Bar_Color => MDI.Title_Bar_Color, Focus_Title_Color => MDI.Focus_Title_Color); -- Create a default empty central area. That will be overridden if the -- user loads a perspective later on Gtk_New (MDI.Central); Add_Child (MDI, MDI.Central); -- Put an empty notebook in the MDI, which will act as a recipient for -- the Position_Default widgets Add_Child (MDI.Central, New_Child => Create_Notebook (MDI), Width => -1, Height => -1, Orientation => Orientation_Vertical); Widget_Callback.Connect (MDI, Gtk.Widget.Signal_Realize, Widget_Callback.To_Marshaller (Realize_MDI'Access)); Widget_Callback.Connect (MDI, Signal_Destroy, Widget_Callback.To_Marshaller (Destroy_MDI'Access)); Widget_Callback.Connect (MDI, Signal_Set_Focus_Child, Set_Focus_Child_MDI'Access); end Initialize; ----------------------- -- Matching_Children -- ----------------------- function Matching_Children (MDI : access MDI_Window_Record'Class; Str : String) return Children_Array is Count : constant Natural := Natural (Length (MDI.Items)); Children : Children_Array (1 .. Count); L : Widget_List.Glist := MDI.Items; Ind : Natural := Children'First; C : MDI_Child; begin while L /= Null_List loop C := MDI_Child (Get_Data (L)); if Str = "" or else Index (To_Lower (Get_Short_Title (C)), Str) /= 0 then Children (Ind) := L; Ind := Ind + 1; end if; L := Next (L); end loop; return Children (Children'First .. Ind - 1); end Matching_Children; ----------------------------- -- Update_Selection_Dialog -- ----------------------------- procedure Update_Selection_Dialog (MDI : access MDI_Window_Record'Class; Increment : Integer) is D : constant Selection_Dialog_Access := Selection_Dialog_Access (MDI.Selection_Dialog); Str : constant UTF8_String := Get_Text (D.Ent); Children : constant Children_Array := Matching_Children (MDI, To_Lower (Str)); Index : Integer := Children'First; Tmp : Integer; Pos : Gint := -1; begin -- Update graphically the list of children matching the filter D.Length := Str'Length; Insert_Text (D.Ent, " {", Pos); Set_Position (D.Ent, Gint (D.Length)); -- Find the index of the current child if Children'Length /= 0 then while Index <= Children'Last loop exit when Children (Index) = D.Current_Child; Index := Index + 1; end loop; Index := Index + Increment; if Index > Children'Last then Index := Children'First; elsif Index < Children'First then Index := Children'Last; end if; Tmp := Index; loop if Tmp /= Index then Insert_Text (D.Ent, ",", Pos); end if; Insert_Text (D.Ent, Get_Short_Title (MDI_Child (Get_Data (Children (Tmp)))), Pos); Tmp := (Tmp + 1 - Children'First) mod Children'Length + Children'First; exit when Tmp = Index; end loop; D.Current_Child := Children (Index); else D.Current_Child := Null_List; end if; Insert_Text (D.Ent, "}", Pos); if D.Current_Child = Null_List then Set_Text (D.Label, ""); Set_Child_Visible (D.Icon, False); else declare C : MDI_Child; Pixmap : Gdk_Pixmap; Mask : Gdk.Gdk_Bitmap; Scaled : Gdk_Pixbuf; begin C := MDI_Child (Get_Data (D.Current_Child)); Set_Text (D.Label, Get_Short_Title (C)); Set_Child_Visible (D.Icon, C.Icon /= null); if C.Icon /= null then Scaled := Scale_Simple (C.Icon, 32, 32); Render_Pixmap_And_Mask (Scaled, Pixmap, Mask, 128); Unref (Scaled); Set (D.Icon, Pixmap, Mask); Gdk.Drawable.Unref (Pixmap); Gdk.Drawable.Unref (Mask); end if; end; end if; end Update_Selection_Dialog; -------------------------------- -- Key_Event_Selection_Dialog -- -------------------------------- function Key_Event_Selection_Dialog (MDI : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is M : constant MDI_Window := MDI_Window (MDI); D : constant Selection_Dialog_Access := Selection_Dialog_Access (M.Selection_Dialog); Close : Boolean := False; Tmp : Boolean; Key : Gdk_Key_Type; pragma Unreferenced (Tmp); begin -- This isn't a key press for the next_child or previous_child -- functions, since those are handled by the outside application. if Get_Event_Type (Event) = Key_Press then Key := Get_Key_Val (Event); if Key = Gdk.Types.Keysyms.GDK_BackSpace or else Key = Gdk.Types.Keysyms.GDK_Delete then Delete_Text (D.Ent, Gint (D.Length) - 1, -1); else Delete_Text (D.Ent, Gint (D.Length), -1); Set_State (Event, 0); Tmp := Return_Callback.Emit_By_Name (D.Ent, "key_press_event", Event); end if; Update_Selection_Dialog (M, 0); return True; elsif Get_Event_Type (Event) = Key_Release then Key := Get_Key_Val (Event); -- As soon as one of the modifiers of the initial key is released, -- we close the dialog if (D.Modifier and Control_Mask) /= 0 and then (Key = Gdk.Types.Keysyms.GDK_Control_L or else Key = Gdk.Types.Keysyms.GDK_Control_R or else Key = Gdk.Types.Keysyms.GDK_ISO_Next_Group) then Close := True; elsif (D.Modifier and Mod1_Mask) /= 0 and then (Key = Gdk.Types.Keysyms.GDK_Meta_L or else Key = Gdk.Types.Keysyms.GDK_Meta_R or else Key = Gdk.Types.Keysyms.GDK_Alt_L or else Key = Gdk.Types.Keysyms.GDK_Alt_R) then Close := True; elsif (D.Modifier and Shift_Mask) /= 0 and then (Key = Gdk.Types.Keysyms.GDK_Shift_L or else Key = Gdk.Types.Keysyms.GDK_Shift_R) then Close := True; elsif Key = Gdk.Types.Keysyms.GDK_Escape then Close := True; end if; if Close then if D.Current_Child /= Null_List then Set_Focus_Child (MDI_Child (Widget_List.Get_Data (D.Current_Child))); end if; Keyboard_Ungrab (Time => 0); Grab_Remove (M.Selection_Dialog); Destroy (M.Selection_Dialog); M.Selection_Dialog := null; end if; return True; end if; return False; end Key_Event_Selection_Dialog; ---------------------------------------- -- Check_Interactive_Selection_Dialog -- ---------------------------------------- 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) is D : Selection_Dialog_Access; Box, HBox : Gtk_Box; Frame : Gtk_Frame; Tmp : Gdk_Grab_Status; pragma Unreferenced (Tmp); begin if MDI.Items = Null_List then return; end if; if Event = null or else (Get_Event_Type (Event) /= Key_Press and then Get_Event_Type (Event) /= Key_Release) then declare List : Widget_List.Glist; Current : Gtk_Notebook; Child : MDI_Child; begin if MDI.Focus_Child /= null then Current := Get_Notebook (MDI.Focus_Child); end if; if Move_To_Next then List := Next (First (MDI.Items)); else List := Last (MDI.Items); end if; while List /= Null_List loop -- Return the first window from another notebook that belongs -- to the same group Child := MDI_Child (Get_Data (List)); if Get_Notebook (Child) /= Current and then (Only_Group = Child_Group'Last or else Child.Group = Only_Group) then Set_Focus_Child (Child); exit; end if; if Move_To_Next then List := Next (List); else List := Prev (List); end if; end loop; end; return; end if; if MDI.Selection_Dialog = null then D := new Selection_Dialog_Record; Initialize (D, Window_Popup); if MDI.All_Floating_Mode then Set_Position (D, Win_Pos_Mouse); else Set_Transient_For (D, Gtk_Window (Get_Toplevel (MDI))); Set_Position (D, Win_Pos_Center_On_Parent); end if; Set_Default_Size (D, 300, 70); Gtk_New (Frame); Add (D, Frame); -- By default, switch between the last two selected items D.Current_Child := First (MDI.Items); Gtk_New_Vbox (Box, Homogeneous => False); Add (Frame, Box); Gtk_New_Hbox (HBox, Homogeneous => False); Pack_Start (Box, HBox, Expand => False); Gtk_New (D.Icon); Pack_Start (HBox, D.Icon, Expand => False); Gtk_New (D.Label); Pack_Start (HBox, D.Label, Expand => True, Fill => True); Gtk_New (D.Ent); Pack_Start (Box, D.Ent, Expand => True); Show_All (D); D.Modifier := Get_State (Event); MDI.Selection_Dialog := Gtk_Widget (D); -- Make sure all the key events are forwarded to us, as otherwise -- if the mouse was moving out of the window we wouldn't the -- events Tmp := Keyboard_Grab (Get_Window (D), True, Time => 0); Grab_Add (D); Grab_Focus (D.Ent); Return_Callback.Object_Connect (D, Signal_Key_Release_Event, Return_Callback.To_Marshaller (Key_Event_Selection_Dialog'Access), MDI); Return_Callback.Object_Connect (D, Signal_Key_Press_Event, Return_Callback.To_Marshaller (Key_Event_Selection_Dialog'Access), MDI); else D := Selection_Dialog_Access (MDI.Selection_Dialog); Delete_Text (D.Ent, Gint (D.Length), -1); end if; if Move_To_Next then Update_Selection_Dialog (MDI, +1); else Update_Selection_Dialog (MDI, -1); end if; end Check_Interactive_Selection_Dialog; --------------- -- Configure -- --------------- procedure Configure (MDI : access MDI_Window_Record; Opaque_Resize : Boolean := False; Close_Floating_Is_Unfloat : Boolean := True; Title_Font : Pango_Font_Description := null; Background_Color : Gdk.Color.Gdk_Color := Gdk.Color.Null_Color; Title_Bar_Color : Gdk.Color.Gdk_Color := Gdk.Color.Null_Color; Focus_Title_Color : Gdk.Color.Gdk_Color := Gdk.Color.Null_Color; Draw_Title_Bars : Title_Bars_Policy := Always; Tabs_Position : Gtk.Enums.Gtk_Position_Type := Gtk.Enums.Pos_Bottom; Show_Tabs_Policy : Show_Tabs_Policy_Enum := Automatic) is Desc : Pango_Font_Description; W, H : Gint; C : MDI_Child; Need_Redraw : Boolean := MDI.Draw_Title_Bars /= Draw_Title_Bars; Iter : Child_Iterator; Old_Tabs_Pos : constant Gtk_Position_Type := MDI.Tabs_Position; Pos_Changed : constant Boolean := Old_Tabs_Pos /= Tabs_Position; Note : Gtk_Notebook; begin MDI.Close_Floating_Is_Unfloat := Close_Floating_Is_Unfloat; MDI.Draw_Title_Bars := Draw_Title_Bars; MDI.Tabs_Position := Tabs_Position; MDI.Show_Tabs_Policy := Show_Tabs_Policy; Set_Opaque_Resizing (MDI, Opaque_Resize); if Title_Font /= null then Set_Font_Description (MDI.Title_Layout, Title_Font); else Desc := From_String (Default_Title_Font); Set_Font_Description (MDI.Title_Layout, Desc); Free (Desc); end if; Get_Pixel_Size (MDI.Title_Layout, W, H); MDI.Title_Bar_Height := 2 + H; -- Ignore changes in colors, unless the MDI is realized if Background_Color /= Null_Color then MDI.Background_Color := Background_Color; end if; if Title_Bar_Color /= Null_Color then MDI.Title_Bar_Color := Title_Bar_Color; end if; if MDI.Highlight_Style /= null then Unref (MDI.Highlight_Style); end if; Gtk_New (MDI.Highlight_Style); if Focus_Title_Color /= Null_Color then MDI.Focus_Title_Color := Focus_Title_Color; Set_Foreground (MDI.Highlight_Style, State_Normal, MDI.Focus_Title_Color); Set_Foreground (MDI.Highlight_Style, State_Active, MDI.Focus_Title_Color); Set_Foreground (MDI.Highlight_Style, State_Selected, MDI.Focus_Title_Color); Set_Foreground (MDI.Highlight_Style, State_Prelight, MDI.Focus_Title_Color); Set_Foreground (MDI.Highlight_Style, State_Insensitive, MDI.Focus_Title_Color); end if; Iter := First_Child (MDI, Group_By_Notebook => True, Visible_Only => True); loop C := Get (Iter); exit when C = null; if Get_Notebook (Iter) /= Note then Note := Get_Notebook (Iter); if Note /= null then -- Unless we had a specific position for tabs in this notebook if Pos_Changed and then Get_Tab_Pos (Note) = Old_Tabs_Pos then Set_Tab_Pos (Note, MDI.Tabs_Position); end if; Configure_Notebook_Tabs (MDI, Note); end if; end if; Next (Iter); end loop; if Realized_Is_Set (MDI) then if Background_Color /= Null_Color then Set_Background (Get_Window (MDI), Background_Color); Need_Redraw := True; end if; if Title_Bar_Color /= Null_Color then Need_Redraw := True; end if; if Focus_Title_Color /= Null_Color then Need_Redraw := True; end if; end if; Reset_Title_Bars_And_Colors (MDI); if Need_Redraw then Queue_Draw (MDI); end if; end Configure; ------------------------------ -- Independent_Perspectives -- ------------------------------ function Independent_Perspectives (MDI : access MDI_Window_Record) return Boolean is begin return MDI.Independent_Perspectives; end Independent_Perspectives; --------------------------------- -- Reset_Title_Bars_And_Colors -- --------------------------------- procedure Reset_Title_Bars_And_Colors (MDI : access MDI_Window_Record'Class) is List : Widget_List.Glist; C : MDI_Child; begin List := First (MDI.Items); while List /= Null_List loop C := MDI_Child (Get_Data (List)); Set_Child_Title_Bar (C); Update_Tab_Color (C); List := Widget_List.Next (List); end loop; end Reset_Title_Bars_And_Colors; ----------------- -- Realize_MDI -- ----------------- procedure Realize_MDI (MDI : access Gtk_Widget_Record'Class) is M : constant MDI_Window := MDI_Window (MDI); begin Gdk.Window.Set_Background (Get_Window (M), M.Background_Color); if M.Cursor_Cross = null then Gdk_New (M.Cursor_Cross, Cross); end if; Queue_Resize (MDI); end Realize_MDI; ----------------- -- Destroy_MDI -- ----------------- procedure Destroy_MDI (MDI : access Gtk_Widget_Record'Class) is M : constant MDI_Window := MDI_Window (MDI); Tmp : Widget_List.Glist := First (M.Items); N : Widget_List.Glist; C : MDI_Child; begin Print_Debug ("Destroy_MDI"); -- Note: we only destroy the floating children. Other children will be -- destroyed when their parent container is destroyed, so we have -- nothing to do for them. if M.Delay_Before_Focus_Id /= No_Source_Id then Remove (M.Delay_Before_Focus_Id); M.Delay_Before_Focus_Id := No_Source_Id; end if; while Tmp /= Null_List loop -- Get the next field first, since Destroy will actually destroy Tmp C := MDI_Child (Get_Data (Tmp)); N := Next (Tmp); if C.State = Floating then Print_Debug ("Destroy_MDI => Destroying floating " & Get_Title (C)); Destroy (C); elsif C.State = Invisible then Print_Debug ("Destroy_MDI => Unref invisible " & Get_Title (C)); Set_State (C, Normal); Unref (C); else Print_Debug ("Destroy_MDI => Do nothing to " & Get_Title (C)); -- Pretend the child is not docked or floating. Otherwise, -- Destroy_Child would try to undock the child. Standard gtk+ -- containers handle this by having this destroy callback called -- last, but it isn't doable from GtkAda since it means modifying -- the pointer-to-subprogram in the Class struct. Set_State (C, Normal); end if; Tmp := N; end loop; Free (M.Items); Unref (M.Title_Layout); if M.Cursor_Cross /= null then Unref (M.Cursor_Cross); end if; if M.Highlight_Style /= null then Unref (M.Highlight_Style); end if; if M.Menu /= null then Destroy (M.Menu); end if; Free (M.Dnd_Message); Free (M.Perspectives); Free (M.View_Contents); Free (M.Perspective_Names); Free (M.Accel_Path_Prefix); end Destroy_MDI; ----------- -- Close -- ----------- procedure Close (MDI : access MDI_Window_Record; Child : access Gtk.Widget.Gtk_Widget_Record'Class; Force : Boolean := False) is C : constant MDI_Child := Find_MDI_Child (MDI, Child); begin if C /= null then Close_Child (C, Force); end if; end Close; ----------------- -- Close_Child -- ----------------- procedure Close_Child (Child : access MDI_Child_Record'Class; Force : Boolean := False) is MDI : constant MDI_Window := Child.MDI; Event : Gdk_Event; Prevent_Delete : Boolean; begin -- Don't do anything for now if the MDI isn't realized, since we -- can't send create the event anyway. Print_Debug ("Close_Child, " & Get_Title (Child) & " force=" & Boolean'Image (Force)); if Realized_Is_Set (MDI) then -- For a top-level window, we must rebuild the initial widget -- temporarily, so that the application can do all the test it wants. -- However, we need to restore the initial state before calling -- Dock_Child and Float_Child below. -- We should not test this when the MDI is being destroyed, though, -- to avoid memory leaks if Force or else In_Destruction_Is_Set (MDI) then Prevent_Delete := False; else Print_Debug ("Close_Child, emitting delete_event"); Indent_Debug (1); Allocate (Event, Delete, Get_Window (MDI)); Prevent_Delete := Return_Callback.Emit_By_Name (Child.Initial, "delete_event", Event); Free (Event); Indent_Debug (-1); Print_Debug ("Close_Child, done delete_event, prevent_delete ?" & Boolean'Image (Prevent_Delete)); end if; if not Prevent_Delete then -- Transfer the focus before unfloating, so that the parent in -- which the child is unfloated (which might be random from the -- user's point of view) doesn't influence who gets the focus. if MDI_Child (Child) = MDI.Focus_Child then Give_Focus_To_Previous_Child (Child); end if; Float_Child (Child, False); Print_Debug ("Close_Child: about to destroy " & Get_Title (Child)); Destroy (Child); end if; end if; exception when E : others => -- Silently ignore the exceptions for now, to avoid crashes. -- The application using the MDI can not do it, since this callback -- is called directly from the button in Initialize pragma Debug (Put_Line ("Unexpected exception: " & Exception_Information (E))); if Traces then Print_Debug ("Unexpected exception " & Exception_Information (E)); end if; end Close_Child; ------------------- -- Destroy_Child -- ------------------- procedure Destroy_Child (Child : access Gtk_Widget_Record'Class) is use type Widget_SList.GSlist; C : constant MDI_Child := MDI_Child (Child); MDI : constant MDI_Window := C.MDI; In_Selection_Dialog : Boolean := False; begin -- We know at that stage that Child has already been unparent-ed pragma Assert (Get_Parent (Child) = null); Print_Debug ("Destroy_Child " & Get_Title (C)); Ref (C); if MDI.Delay_Before_Focus_Id /= No_Source_Id then Remove (MDI.Delay_Before_Focus_Id); MDI.Delay_Before_Focus_Id := No_Source_Id; end if; C.Tab_Label := null; -- The child of the MDI_Child has now been taken care of, thus we need -- to take care of the MDI_Child itself now. if C.Menu_Item /= null then Destroy (C.Menu_Item); end if; if not Gtk.Object.In_Destruction_Is_Set (C.MDI) then -- Do not unfloat the child, since the toplevel is no longer a -- Gtk_Window, and we would get a CE in Float_Child. if Get_Parent (C) /= null then Remove (Gtk_Container (Get_Parent (C)), C); end if; end if; if Get_Parent (C.Initial) /= null then Print_Debug ("Destroy_Child removing initial child from parent"); Remove (Gtk_Container (Get_Parent (C.Initial)), C.Initial); end if; C.Initial := null; -- Do not transfer the focus elsewhere: for an interactive close, this -- is done in Close_Child, otherwise we do not want to change the focus. -- No need to send a signal to signal that a new child has been selected -- since Give_Focus_To_Previous_Child has been called already if C = MDI.Focus_Child then MDI.Focus_Child := null; end if; In_Selection_Dialog := MDI.Selection_Dialog /= null and then C = MDI_Child (Get_Data (Selection_Dialog_Access (MDI.Selection_Dialog).Current_Child)); -- Only remove it from the list of children at the end, since some of -- calls above might result in calls to Raise_Child_Idle, which tries -- to manipulate that list. Widget_List.Remove (MDI.Items, Gtk_Widget (C)); -- Report that the child has been removed only after it has indeed be -- fully removed, but before we actually free it Emit_By_Name_Child (Get_Object (MDI), String (Signal_Child_Removed) & ASCII.NUL, Get_Object (C)); -- If we are currently displaying the window selection dialog, update it -- so that the widget that has been destroyed does not show up in the -- selection window. if In_Selection_Dialog then Update_Selection_Dialog (MDI, +1); end if; Free (C.Title); Free (C.Short_Title); Free (C.XML_Node_Name); if C.State = Invisible then -- We owned an extra reference in this case Unref (C); end if; -- Destroy the child, unless the user has explicitely kept a Ref on it -- (therefore, do not use Destroy, only Unref). In all cases, it should -- be hidden on the screen Unref (C); end Destroy_Child; --------------------------- -- Destroy_Initial_Child -- --------------------------- procedure Destroy_Initial_Child (Child : access Gtk_Widget_Record'Class) is begin if not Gtk.Object.Destroyed_Is_Set (Child) then Destroy (Child); end if; end Destroy_Initial_Child; ------------------------- -- Set_Child_Title_Bar -- ------------------------- procedure Set_Child_Title_Bar (Child : access MDI_Child_Record'Class) is begin if not Has_Title_Bar (Child) then Hide (Child.Title_Box); Set_Child_Visible (Child.Title_Box, False); Set_USize (Child.Title_Box, -1, 0); Set_Size_Request (Child.Title_Box, -1, 0); else Show (Child.Title_Box); Set_Child_Visible (Child.Title_Box, True); Set_USize (Child.Title_Box, -1, -1); Set_Size_Request (Child.Title_Box, -1, Child.MDI.Title_Bar_Height); end if; end Set_Child_Title_Bar; ---------------- -- Draw_Child -- ---------------- function Draw_Child (Widget : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is pragma Unreferenced (Event); Child : constant MDI_Child := MDI_Child (Widget); Border_Thickness : constant Gint := Gint (Get_Border_Width (Child.Main_Box)); Color : Gdk_Color := Child.MDI.Title_Bar_Color; Cr : Cairo_Context; W, H : Gint; X : Gint := 1; begin -- Call this function so that for a dock item is highlighted if the -- current page is linked to the focus child. if Child.MDI.Focus_Child = Child then Color := Child.MDI.Focus_Title_Color; end if; -- Set the color of the notebook page and label. Update_Tab_Color (Child); if Realized_Is_Set (Child.Title_Box) then Cr := Create (Get_Window (Child.Title_Box)); Set_Source_Color (Cr, Color); Cairo.Rectangle (Cr, 0.0, 0.0, Gdouble (Get_Allocation_Width (Child.Title_Box)), Gdouble (Get_Allocation_Height (Child.Title_Box))); Cairo.Fill (Cr); if Child.Icon /= null then W := Get_Width (Child.Icon); H := Get_Height (Child.Icon); Set_Source_Pixbuf (Cr, Child.Icon, 0.0, 0.0); Save (Cr); Translate (Cr, Gdouble (X), Gdouble ((Get_Allocation_Height (Child.Title_Box) - H) / 2)); Paint (Cr); Restore (Cr); X := X + W + 1; end if; if Child.MDI.Use_Short_Titles_For_Floats then Set_Text (Child.MDI.Title_Layout, Child.Short_Title.all); else Set_Text (Child.MDI.Title_Layout, Child.Title.all); end if; Get_Pixel_Size (Child.MDI.Title_Layout, W, H); Set_Source_Color (Cr, Get_White (Get_Style (Child.MDI))); Move_To (Cr, Gdouble (X), 0.0); Show_Layout (Cr, Child.MDI.Title_Layout); if Border_Thickness /= 0 then Paint_Shadow (Style => Get_Style (Child), Window => Get_Window (Child), State_Type => State_Normal, Shadow_Type => Shadow_Out, Widget => Child, X => 0, Y => 0, Width => Gint (Get_Allocation_Width (Child)), Height => Gint (Get_Allocation_Height (Child))); end if; Destroy (Cr); end if; return False; end Draw_Child; ----------------------- -- Update_Dnd_Window -- ----------------------- procedure Update_Dnd_Window (MDI : access MDI_Window_Record'Class; Text : String; In_Central : Boolean) is In_Perspective_Txt : aliased constant String := "hidden"; In_Central_Txt : aliased constant String := "preserved"; type Cst_String_Access is access constant String; Loc : Cst_String_Access; Frame : Gtk_Frame; Box : Gtk_Box; Pos : constant Integer := Ada.Strings.Fixed.Index (MDI.Dnd_Message.all, "(#)"); begin if MDI.Dnd_Window = null then Gtk_New (MDI.Dnd_Window, Window_Popup); Set_Transient_For (MDI.Dnd_Window, Gtk_Window (Get_Toplevel (MDI))); Set_Position (MDI.Dnd_Window, Win_Pos_Center_On_Parent); Modify_Bg (MDI.Dnd_Window, State_Normal, MDI.Focus_Title_Color); Set_Keep_Above (MDI.Dnd_Window, True); Gtk_New (Frame); Add (MDI.Dnd_Window, Frame); Gtk_New_Vbox (Box, Homogeneous => False); Add (Frame, Box); Set_Border_Width (Box, 10); Gtk_New (MDI.Dnd_Window_Label, ""); Set_Use_Markup (MDI.Dnd_Window_Label, True); Pack_Start (Box, MDI.Dnd_Window_Label, Expand => True); Show_All (MDI.Dnd_Window); end if; if In_Central then Loc := In_Central_Txt'Access; else Loc := In_Perspective_Txt'Access; end if; if Pos < MDI.Dnd_Message'First then Set_Label (MDI.Dnd_Window_Label, ASCII.HT & Text & ASCII.LF & MDI.Dnd_Message.all); else Set_Label (MDI.Dnd_Window_Label, ASCII.HT & Text & ASCII.LF & MDI.Dnd_Message (MDI.Dnd_Message'First .. Pos - 1) & Loc.all & MDI.Dnd_Message (Pos + 3 .. MDI.Dnd_Message'Last)); end if; end Update_Dnd_Window; --------------------- -- Set_Dnd_Message -- --------------------- procedure Set_Dnd_Message (MDI : access MDI_Window_Record; Message : String) is begin Free (MDI.Dnd_Message); if Message /= "" then MDI.Dnd_Message := new String'(Message); elsif MDI.Independent_Perspectives then MDI.Dnd_Message := new String' ("Use control to move the whole notebook" & ASCII.LF & "Use shift to create a new view for editors"); else MDI.Dnd_Message := new String' ("Will be (#) when changing perspective" & ASCII.LF & "Use control to move the whole notebook" & ASCII.LF & "Use shift to create a new view for editors"); end if; end Set_Dnd_Message; ------------------------ -- Destroy_Dnd_Window -- ------------------------ procedure Destroy_Dnd_Window (MDI : access MDI_Window_Record'Class) is begin if MDI.Dnd_Window /= null then Destroy (MDI.Dnd_Window); MDI.Dnd_Window := null; end if; end Destroy_Dnd_Window; -------------------- -- Button_Pressed -- -------------------- function Button_Pressed (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is begin -- It sometimes happens that widgets let events pass through (for -- instance scrollbars do that), and thus wouldn't be useable anymore -- if we do a grab. if Get_Window (Child) /= Get_Window (Event) then return False; end if; return Button_Pressed_Forced (Child, Event); end Button_Pressed; --------------------------- -- Button_Pressed_Forced -- --------------------------- function Button_Pressed_Forced (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is C : constant MDI_Child := MDI_Child (Child); W, H : Gint; begin C.MDI.In_Drag := No_Drag; -- Double-click on left icon => close child if Get_Event_Type (Event) = Gdk_2button_Press and then Get_Button (Event) = 1 then if C.Icon /= null then W := Get_Width (C.Icon); H := Get_Height (C.Icon); if Gint (Get_X (Event)) <= W and then Gint (Get_Y (Event)) <= H then Close_Child (C); return True; end if; end if; return False; elsif Get_Event_Type (Event) /= Button_Press or else Get_Button (Event) /= 1 then return False; end if; -- Start a drag-and-drop operation. This won't be effective unless -- the user actually drags the mouse a while Print_Debug ("Button_Pressed_Forced"); Child_Drag_Begin (C, Event); -- Let the event through, the drag hasn't started yet return False; end Button_Pressed_Forced; -------------------- -- Button_Release -- -------------------- function Button_Release (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is C : constant MDI_Child := MDI_Child (Child); MDI : constant MDI_Window := C.MDI; Move_Whole_Notebook : constant Boolean := (Get_State (Event) and Control_Mask) /= 0; Copy_Instead_Of_Move : constant Boolean := (Get_State (Event) and Shift_Mask) /= 0; C2 : MDI_Child; Current : Gtk_Widget; Note : Gtk_Notebook; Position : Child_Position; Pane : Gtkada_Multi_Paned; begin Print_Debug ("Button release, drag=" & Drag_Status'Image (C.MDI.In_Drag)); Pointer_Ungrab (Time => 0); if Get_Window (Child) /= Get_Window (Event) then C.MDI.In_Drag := No_Drag; return False; end if; case C.MDI.In_Drag is when In_Pre_Drag => Destroy_Dnd_Window (C.MDI); Child_Drag_Finished (C); when In_Drag => if C.MDI.Central /= null then Set_Border_Width (C.MDI.Central, 0); end if; Destroy_Dnd_Window (C.MDI); Draw_Dnd_Rectangle (C.MDI, Mode => Destroy); Get_Dnd_Target (C.MDI, Current, Position, C.MDI.Dnd_Rectangle); if Current = null then -- outside of the main window ? Pane := null; elsif Current = Gtk_Widget (C.MDI) then Pane := Gtkada_Multi_Paned (C.MDI); elsif Get_Parent (Current) = Gtk_Widget (C.MDI.Central) then Pane := C.MDI.Central; else Pane := Gtkada_Multi_Paned (C.MDI); end if; C2 := Dnd_Data (C, Copy => Copy_Instead_Of_Move); if C2 = null then C2 := C; end if; if Current = null then -- Floating child ? Float_Child (C2, True); -- If the child is dropped at the same location, nothing to do elsif C2.State = Normal -- A floating child is always moved and then Current = Get_Parent (C2) -- same notebook ? and then (Position = Position_Automatic -- inside the nook or else Move_Whole_Notebook -- to one side but moving all or else Get_Nth_Page (Gtk_Notebook (Current), 1) = null) then null; -- Do the actual moving else -- In the notebook that contains the window we are moving, we -- now raise the last window that had the focus declare Item : Widget_List.Glist := MDI.Items; It : MDI_Child; begin if C /= C2 then Print_Debug ("Button_Release raising last1 " & Get_Title (C)); Raise_Child (C, False); else while Item /= Widget_List.Null_List loop It := MDI_Child (Get_Data (Item)); if It /= C2 and then Get_Parent (C2) = Get_Parent (It) then Print_Debug ("Button_Release raising last2 " & Get_Title (It)); Raise_Child (It, False); exit; end if; Item := Widget_List.Next (Item); end loop; end if; end; -- Find in which notebook the widget should be moved. if Current = Gtk_Widget (C.MDI.Central) or else Current = Gtk_Widget (C.MDI) then -- The central area is empty if Current has this value, we -- always create a new notebook Note := Create_Notebook (MDI); if Current = Gtk_Widget (C.MDI) then Current := null; end if; else -- We dropped in a notebook, should we reuse or create one ? if Position = Position_Automatic then Note := Gtk_Notebook (Current); else Note := Create_Notebook (MDI); end if; end if; -- Add to the contents of this notebook if Move_Whole_Notebook then declare Children : Widget_List.Glist := Get_Children (Get_Notebook (C2)); L : Widget_List.Glist := Children; begin while L /= Null_List loop Put_In_Notebook (C.MDI, MDI_Child (Get_Data (L)), Note, Force_Parent_Destruction => False); L := Next (L); end loop; Free (Children); end; else Put_In_Notebook (C.MDI, C2, Note, Force_Parent_Destruction => False); end if; case Position is when Position_Bottom => if Current = null then Split (Pane, Root_Pane, Note, Orientation_Vertical, Height => -1); else Split (Pane, Current, Note, Orientation_Vertical); end if; when Position_Top => if Current = null then Split (Pane, Root_Pane, Note, Orientation_Vertical, Height => -1, After => False); else Split (Pane, Current, Note, Orientation_Vertical, After => False); end if; when Position_Left => if Current = null then Split (Pane, Root_Pane, Note, Orientation_Horizontal, Width => -1, After => False); else Split (Pane, Current, Note, Orientation_Horizontal, After => False); end if; when Position_Right => if Current = null then Split (Pane, Root_Pane, Note, Orientation_Horizontal, Width => -1); else Split (Pane, Current, Note, Orientation_Horizontal); end if; when Position_Automatic => if C.MDI.Central /= null and then Current = Gtk_Widget (C.MDI.Central) then Add_Child (Win => C.MDI.Central, New_Child => Note, Orientation => Orientation_Horizontal, Width => 0, Height => 0); else Emit_By_Name (Get_Object (MDI), String (Signal_Children_Reorganized) & ASCII.NUL); end if; end case; end if; Child_Drag_Finished (C); Print_Debug ("Button_Release raising " & Get_Title (C2)); Raise_Child (C2, False); Print_Debug ("Button_Release, set_focus " & Get_Title (C2)); Set_Focus_Child (C2); when No_Drag => -- Let the even through, we have nothing to do here return False; end case; C.MDI.In_Drag := No_Drag; return True; end Button_Release; ------------------- -- Button_Motion -- ------------------- function Button_Motion (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is C : constant MDI_Child := MDI_Child (Child); Current : Gtk_Widget; C3 : MDI_Child; Note : Gtk_Notebook; Rect2 : Gdk_Rectangle; Tmp : Gdk_Grab_Status; Position : Child_Position; Delta_X, Delta_Y : Gint; pragma Unreferenced (Tmp); In_Central : Boolean; begin if Get_Window (Child) /= Get_Window (Event) then return False; end if; case C.MDI.In_Drag is when In_Drag => Get_Dnd_Target (C.MDI, Parent => Current, Position => Position, Rectangle => Rect2); -- Show the user what will happen if he drops at the current -- location if Current = null then Update_Dnd_Window (C.MDI, "Float", True); C.MDI.Dnd_Target := null; elsif Current = Gtk_Widget (C.MDI) then C.MDI.Dnd_Target := Get_Window (C.MDI); case Position is when Position_Bottom => Update_Dnd_Window (C.MDI, "Below all other windows", False); when Position_Top => Update_Dnd_Window (C.MDI, "Above all other windows", False); when Position_Left => Update_Dnd_Window (C.MDI, "On the left of all other windows", False); when Position_Right => Update_Dnd_Window (C.MDI, "On the right of all other windows", False); when others => -- Cannot occur null; end case; elsif Current = Gtk_Widget (C.MDI.Central) then C.MDI.Dnd_Target := Get_Window (C.MDI.Central); case Position is when Position_Bottom => Update_Dnd_Window (C.MDI, "Put below central area", False); when Position_Top => Update_Dnd_Window (C.MDI, "Put above central area", False); when Position_Left => Update_Dnd_Window (C.MDI, "Put on the left of central area", False); when Position_Right => Update_Dnd_Window (C.MDI, "Put on the right of central area", False); when others => Update_Dnd_Window (C.MDI, "Put in central area", True); end case; elsif Current = Get_Parent (C) and then Position = Position_Automatic then C.MDI.Dnd_Target := Get_Window (C); Update_Dnd_Window (C.MDI, "Leave at current position", In_Central_Area (C.MDI, C)); else Note := Gtk_Notebook (Current); C3 := MDI_Child (Get_Nth_Page (Note, Get_Current_Page (Note))); if C3 = null then Update_Dnd_Window (C.MDI, "Put in central area", True); C.MDI.Dnd_Target := Get_Window (C.MDI.Central); else C.MDI.Dnd_Target := Get_Window (C3); In_Central := In_Central_Area (C.MDI, C3); case Position is when Position_Bottom => Update_Dnd_Window (C.MDI, "Put below " & Get_Short_Title (C3) & "", In_Central); when Position_Top => Update_Dnd_Window (C.MDI, "Put above " & Get_Short_Title (C3) & "", In_Central); when Position_Left => Update_Dnd_Window (C.MDI, "Put on the left of " & Get_Short_Title (C3) & "", In_Central); when Position_Right => Update_Dnd_Window (C.MDI, "Put on the right of " & Get_Short_Title (C3) & "", In_Central); when others => Update_Dnd_Window (C.MDI, "Put on top of " & Get_Short_Title (C3) & "", In_Central); end case; end if; end if; if Current = null then Draw_Dnd_Rectangle (C.MDI, Mode => Hide); else C.MDI.Dnd_Rectangle := Rect2; Draw_Dnd_Rectangle (C.MDI, Mode => Show, Ref_Window => Get_Window (Current)); end if; return True; when In_Pre_Drag => -- If we are still in the tabs area, do nothing so that tabs can -- be reordered graphically Delta_X := abs (Gint (Get_X_Root (Event)) - C.MDI.Drag_Start_X); Delta_Y := abs (Gint (Get_Y_Root (Event)) - C.MDI.Drag_Start_Y); Note := Get_Notebook (C); if Note /= null and then Get_Show_Tabs (Note) then case Get_Tab_Pos (Note) is when Pos_Top | Pos_Bottom => if Delta_Y < Drag_Threshold / 2 and then Delta_Y < Delta_X then return False; end if; when Pos_Left | Pos_Right => if Delta_X < Drag_Threshold / 2 and then Delta_X < Delta_Y then return False; end if; end case; end if; -- Else start a drag operation if appropriate if Delta_X > Drag_Threshold or else Delta_Y > Drag_Threshold then -- If we had a tab reorder operation, but the tab was left at -- the same position, the signal "page_reordered" has not been -- emitted. Still, the pointer has been ungrabbed, so we do -- the following test below, so that we do not start our own -- dnd operation if not Pointer_Is_Grabbed then return False; end if; if C.MDI.Central /= null then Set_Border_Width (C.MDI.Central, 10); end if; C.MDI.In_Drag := In_Drag; Pointer_Ungrab (Time => 0); if C.MDI.Cursor_Fleur = null then Gdk_New (C.MDI.Cursor_Fleur, Fleur); end if; Tmp := Pointer_Grab (Get_Window (C), False, Button_Motion_Mask or Button_Release_Mask, Cursor => C.MDI.Cursor_Fleur, Time => 0); return True; end if; when others => null; end case; return True; end Button_Motion; -------------- -- Dnd_Data -- -------------- function Dnd_Data (Child : access MDI_Child_Record; Copy : Boolean) return MDI_Child is pragma Unreferenced (Copy); begin return MDI_Child (Child); end Dnd_Data; ------------------------- -- Child_Widget_Hidden -- ------------------------- procedure Child_Widget_Hidden (Widget : access Gtk_Widget_Record'Class) is Child : constant MDI_Child := MDI_Child (Widget); Note : Gtk_Notebook; begin if Child.State = Floating then Hide (Get_Toplevel (Get_Widget (Child))); else Hide (Child); -- At startup, the notebook might be null Note := Get_Notebook (Child); if Note /= null then Configure_Notebook_Tabs (Child.MDI, Note, Hide_If_Empty => True); end if; end if; end Child_Widget_Hidden; ------------------------ -- Child_Widget_Shown -- ------------------------ procedure Child_Widget_Shown (Widget : access Gtk_Widget_Record'Class) is Child : constant MDI_Child := MDI_Child (Widget); Note : Gtk_Notebook; begin if Child.State = Floating then Show (Get_Toplevel (Get_Widget (Child))); else Show (Child); -- At startup, the notebook might be null Note := Get_Notebook (Child); if Note /= null then Configure_Notebook_Tabs (Child.MDI, Note, Hide_If_Empty => True); end if; end if; end Child_Widget_Shown; ------------- -- Gtk_New -- ------------- 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) is begin Child := new MDI_Child_Record; Initialize (Child, Widget, Flags, Group, Focus_Widget); end Gtk_New; ---------------- -- Initialize -- ---------------- 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) is Signal_Parameters : constant Glib.Object.Signal_Parameter_Types := (1 => (1 => GType_None), 2 => (1 => GType_None), 3 => (1 => GType_None), 4 => (1 => GType_None)); Event : Gtk_Event_Box; Button : Close_Button.Gtkada_MDI_Close_Button; begin if Widget.all in Gtk_Window_Record'Class then raise Program_Error; end if; Gtk.Event_Box.Initialize (Child); Gtk.Object.Initialize_Class_Record (Child, Signals => Child_Signals, Class_Record => Child_Class_Record, Type_Name => "GtkAdaMDIChild", Parameters => Signal_Parameters); Set_Border_Width (Child, 0); Child.Initial := Gtk_Widget (Widget); Child.State := Normal; Child.Flags := Flags; Child.Group := Group; Child.Focus_Widget := Focus_Widget; Child.MDI := null; Child.Title := new UTF8_String'(" "); Child.Short_Title := new UTF8_String'(" "); Add_Events (Child, Button_Press_Mask or Button_Motion_Mask or Button_Release_Mask or Pointer_Motion_Mask); Return_Callback.Connect (Child, Signal_Button_Press_Event, Return_Callback.To_Marshaller (Button_Pressed'Access)); Return_Callback.Connect (Child, Signal_Button_Release_Event, Return_Callback.To_Marshaller (Button_Release'Access)); Return_Callback.Connect (Child, Signal_Motion_Notify_Event, Return_Callback.To_Marshaller (Button_Motion'Access)); Widget_Callback.Connect (Child, Signal_Destroy, Widget_Callback.To_Marshaller (Destroy_Child'Access)); Gtk_New_Vbox (Child.Main_Box, Homogeneous => False, Spacing => 0); Add (Child, Child.Main_Box); -- Buttons in the title bar Gtk_New_Hbox (Child.Title_Box, Homogeneous => False); Pack_Start (Child.Main_Box, Child.Title_Box, Expand => False, Fill => False); Return_Callback.Object_Connect (Child.Title_Box, Signal_Expose_Event, Return_Callback.To_Marshaller (Draw_Child'Access), Slot_Object => Child); if (Flags and Destroy_Button) /= 0 then Close_Button.Gtk_New (Button, Child, Child, True); Pack_End (Child.Title_Box, Button, Expand => False, Fill => False, Padding => 2); end if; -- This internal Event box is needed when the child is floated Gtk_New (Event); Add (Event, Widget); Pack_Start (Child.Main_Box, Event, Expand => True, Fill => True, Padding => 0); Widget_Callback.Object_Connect (Child.Initial, Signal_Destroy, Widget_Callback.To_Marshaller (Destroy_Initial_Child'Access), Child); Widget_Callback.Connect (Child, Signal_Hide, Child_Widget_Hidden'Access); Widget_Callback.Connect (Child, Signal_Show, Child_Widget_Shown'Access); Widget_Callback.Object_Connect (Child.Initial, Signal_Hide, Child_Widget_Hidden'Access, Child); Widget_Callback.Object_Connect (Child.Initial, Signal_Show, Child_Widget_Shown'Access, Child); end Initialize; ------------------------- -- Give_Focus_To_Child -- ------------------------- procedure Give_Focus_To_Child (Child : MDI_Child) is F : Gtk_Widget := Gtk_Widget (Child); begin if Child /= null then if Child.Focus_Widget /= null then F := Child.Focus_Widget; end if; -- If we can't give the focus to the focus widget, give it to -- child itself. This is better than keeping it on the previous -- child. Grab_Focus (F); end if; end Give_Focus_To_Child; ---------------------------------- -- Give_Focus_To_Previous_Child -- ---------------------------------- procedure Give_Focus_To_Previous_Child (Child : access MDI_Child_Record'Class) is Item : Widget_List.Glist; It : MDI_Child; Last : MDI_Child; begin -- Set the focus on the child that had the focus just before, -- and in the same notebook, and still visible Item := Child.MDI.Items; while Item /= Widget_List.Null_List loop It := MDI_Child (Get_Data (Item)); if Visible_Is_Set (It) and then It /= MDI_Child (Child) then if Last = null then Last := It; end if; if It.State = Child.State and then Get_Parent (It) = Get_Parent (Child) then Print_Debug ("Give_Focus_To_Previous_Child " & Get_Title (It)); Set_Focus_Child (It); return; end if; end if; Item := Widget_List.Next (Item); end loop; -- No such child, give it to the last child that had the focus if Last = null then Print_Debug ("Give_Focus_To_Previous_Child: no one"); Child.MDI.Focus_Child := null; Emit_By_Name_Child (Get_Object (Child.MDI), String (Signal_Child_Selected) & ASCII.NUL, System.Null_Address); else Set_Focus_Child (Last); end if; end Give_Focus_To_Previous_Child; --------- -- Put -- --------- procedure Put (MDI : access MDI_Window_Record; Child : access MDI_Child_Record'Class; Initial_Position : Child_Position := Position_Automatic) is begin Child.MDI := MDI_Window (MDI); -- We need to show the widget before inserting it in a notebook, -- otherwise the notebook page will not be made visible. Ref (Child); Show_All (Child); if Child.State = Invisible then Unref (Child); -- Set in Remove_All_Items end if; Set_State (Child, Normal); Float_Child (Child, MDI.All_Floating_Mode); if not MDI.All_Floating_Mode then Put_In_Notebook (MDI, Child, Initial_Position => Initial_Position); end if; Set_Child_Title_Bar (Child); -- Add the child to the list of widgets. It could in fact already be in -- the list if we are reusing a Invisible child from a previous -- perspective. We however want to move it to the front of the list Remove (MDI.Items, Gtk_Widget (Child)); Prepend (MDI.Items, Gtk_Widget (Child)); Unref (Child); if MDI.Menu /= null then Create_Menu_Entry (Child); end if; -- Restore the keyboard focus, which might have been stolen if the new -- child was added to a notebook. Give_Focus_To_Child (MDI.Focus_Child); Emit_By_Name_Child (Get_Object (MDI), String (Signal_Child_Added) & ASCII.NUL, Get_Object (Child)); end Put; -------------- -- Set_Size -- -------------- procedure Set_Size (MDI : access MDI_Window_Record; Child : access MDI_Child_Record'Class; Width : Glib.Gint; Height : Glib.Gint; Fixed_Size : Boolean := False) is Notebook : constant Gtk_Notebook := Get_Notebook (Child); begin -- Ignore specific size requests while loading the desktop, since the -- latter should force the size if not MDI.Loading_Desktop and then Notebook /= null then -- Only take this into account if we have a single page if Get_Nth_Page (Notebook, 1) = null and then MDI.Show_Tabs_Policy /= Always then Set_Size (MDI, Widget => Notebook, Width => Width, Height => Height, Fixed_Size => Fixed_Size); else Set_Size_Request (Child, Width, Height); Set_Size (MDI, Widget => Notebook, Width => Width, Height => Height + Get_Allocation_Height (Notebook) - Get_Allocation_Height (Child), Fixed_Size => Fixed_Size); end if; end if; end Set_Size; --------------- -- Get_Title -- --------------- function Get_Title (Child : access MDI_Child_Record) return UTF8_String is begin return Child.Title.all; end Get_Title; --------------------- -- Get_Short_Title -- --------------------- function Get_Short_Title (Child : access MDI_Child_Record) return UTF8_String is begin return Child.Short_Title.all; end Get_Short_Title; ---------------------- -- Update_Menu_Item -- ---------------------- procedure Update_Menu_Item (Child : access MDI_Child_Record'Class) is Label : Gtk_Accel_Label; Pixmap : Gtk_Image; Pix : Gdk_Pixmap; Mask : Gdk_Bitmap; Box : Gtk_Box; begin if Child.Menu_Item /= null then if Get_Child (Child.Menu_Item) /= null then Remove (Child.Menu_Item, Get_Child (Child.Menu_Item)); end if; Gtk_New_Hbox (Box, Homogeneous => False, Spacing => 5); if Child.Icon /= null then Render_Pixmap_And_Mask (Child.Icon, Pix, Mask, 128); Gtk_New (Pixmap, Pix, Mask); Pack_Start (Box, Pixmap, Expand => False); Gdk.Drawable.Unref (Pix); Gdk.Drawable.Unref (Mask); end if; Gtk_New (Label, Child.Short_Title.all); Set_Alignment (Label, 0.0, 0.5); Set_Accel_Widget (Label, Child.Menu_Item); Pack_Start (Box, Label, Expand => True, Fill => True); Show_All (Box); Add (Child.Menu_Item, Box); Set_Accel_Path (Child.Menu_Item, Child.MDI.Accel_Path_Prefix.all & "/window/child/" & Child.Short_Title.all, Child.MDI.Group); end if; end Update_Menu_Item; -------------- -- Set_Icon -- -------------- procedure Set_Icon (Child : access MDI_Child_Record; Icon : Gdk.Pixbuf.Gdk_Pixbuf) is begin if Child.Icon /= null then Unref (Child.Icon); end if; Child.Icon := Icon; if Realized_Is_Set (Child) then -- Force a refresh of the title bar Draw (Child, (0, 0, Get_Allocation_Width (Child), Get_Allocation_Height (Child.Title_Box))); end if; Update_Menu_Item (Child); Update_Tab_Label (Child); Emit_By_Name_Child (Get_Object (Child.MDI), String (Signal_Child_Icon_Changed) & ASCII.NUL, Get_Object (Child)); end Set_Icon; -------------- -- Get_Icon -- -------------- function Get_Icon (Child : access MDI_Child_Record) return Gdk.Pixbuf.Gdk_Pixbuf is begin return Child.Icon; end Get_Icon; --------------- -- Set_Title -- --------------- procedure Set_Title (Child : access MDI_Child_Record; Title : UTF8_String; Short_Title : UTF8_String := "") is Title_Changed : constant Boolean := Child.Title = null or else Child.Title.all /= Title; Short_Title_Changed : constant Boolean := Child.Short_Title = null or else Child.Short_Title.all /= Short_Title; The_Title : String_Access; The_Short_Title : String_Access; -- Those pointers are used to prevent problems when -- the Title parameter is in fact Child.Title begin The_Title := new UTF8_String'(Title); if Short_Title /= "" then The_Short_Title := new UTF8_String'(Short_Title); else The_Short_Title := new UTF8_String'(Title); end if; Free (Child.Title); Free (Child.Short_Title); Child.Title := The_Title; Child.Short_Title := The_Short_Title; if Title_Changed and then Child.State = Floating then Set_Title (Gtk_Window (Get_Toplevel (Child.Initial)), Locale_From_UTF8 (Title)); end if; if Short_Title_Changed then Update_Tab_Label (Child); -- Update the menu, if it exists. We need to recreate the menu item -- to keep it sorted if Child.Menu_Item /= null then Destroy (Child.Menu_Item); Create_Menu_Entry (Child); end if; end if; if Title_Changed or else Short_Title_Changed then if Get_Window (Child) /= Null_Window then Queue_Draw (Child); end if; if Child.MDI /= null then Emit_By_Name_Child (Get_Object (Child.MDI), String (Signal_Child_Title_Changed) & ASCII.NUL, Get_Object (Child)); end if; end if; end Set_Title; ---------------------------- -- Insert_Child_If_Needed -- ---------------------------- function Insert_Child_If_Needed (MDI : access MDI_Window_Record'Class; Child : MDI_Child) return MDI_Child is begin if Child /= null and then Child.State = Invisible then Put (MDI, Child); end if; return Child; end Insert_Child_If_Needed; -------------------- -- Find_MDI_Child -- -------------------- function Find_MDI_Child (MDI : access MDI_Window_Record; Widget : access Gtk.Widget.Gtk_Widget_Record'Class) return MDI_Child is Tmp : Widget_List.Glist; begin Tmp := First (MDI.Items); while Tmp /= Null_List loop if MDI_Child (Get_Data (Tmp)).Initial = Gtk_Widget (Widget) then return Insert_Child_If_Needed (MDI, MDI_Child (Get_Data (Tmp))); end if; Tmp := Next (Tmp); end loop; return null; end Find_MDI_Child; -------------------------------- -- Find_MDI_Child_From_Widget -- -------------------------------- function Find_MDI_Child_From_Widget (Widget : access Gtk.Widget.Gtk_Widget_Record'Class) return MDI_Child is W : Gtk_Widget := Gtk_Widget (Widget); Win : Gtk_Window; C : MDI_Child; begin -- As a special case, if the widget's parent is a notebook, we check -- whether the associated page is a MDI child, and behave as if that -- child had the focus (EC19-008) while W /= null loop if W.all in MDI_Child_Record'Class then return Insert_Child_If_Needed (MDI_Child (W).MDI, MDI_Child (W)); elsif W.all in Gtk_Notebook_Record'Class and then Get_Nth_Page (Gtk_Notebook (W), Get_Current_Page (Gtk_Notebook (W))).all in MDI_Child_Record'Class then C := MDI_Child (Get_Nth_Page (Gtk_Notebook (W), Get_Current_Page (Gtk_Notebook (W)))); return Insert_Child_If_Needed (C.MDI, C); end if; W := Get_Parent (W); end loop; -- Not found ? We might have a floating window. Unfortunately, these -- windows do not keep track of the MDI child they belong to... Win := Gtk_Window (Get_Toplevel (Widget)); if Win /= null then begin C := Child_User_Data.Get (Win, "parent_mdi_child"); return Insert_Child_If_Needed (C.MDI, C); exception when Gtkada.Types.Data_Error => return null; end; else return null; end if; end Find_MDI_Child_From_Widget; --------------------------- -- Find_MDI_Child_By_Tag -- --------------------------- function Find_MDI_Child_By_Tag (MDI : access MDI_Window_Record; Tag : Ada.Tags.Tag; Visible_Only : Boolean := False) return MDI_Child is Child : MDI_Child; Iter : Child_Iterator := First_Child (MDI, Visible_Only => Visible_Only); begin loop Child := Get (Iter); exit when Child = null or else Child.Initial'Tag = Tag; Next (Iter); end loop; if Child /= null then return Insert_Child_If_Needed (MDI, Child); else return null; end if; end Find_MDI_Child_By_Tag; ---------------------------- -- Find_MDI_Child_By_Name -- ---------------------------- function Find_MDI_Child_By_Name (MDI : access MDI_Window_Record; Name : String) return MDI_Child is Child : MDI_Child; Iter : Child_Iterator := First_Child (MDI, Visible_Only => False); begin loop Child := Get (Iter); exit when Child = null or else Child.Title.all = Name or else Child.Short_Title.all = Name; Next (Iter); end loop; return Insert_Child_If_Needed (MDI, Get (Iter)); end Find_MDI_Child_By_Name; ----------------- -- Lower_Child -- ----------------- procedure Lower_Child (Child : access MDI_Child_Record'Class) is Note : Gtk_Notebook; begin Ref (Child); Remove (Child.MDI.Items, Gtk_Widget (Child)); Append (Child.MDI.Items, Gtk_Widget (Child)); Unref (Child); if Child.State = Normal then Note := Get_Notebook (Child); Set_Current_Page (Note, Page_Num (Note, Child)); elsif Realized_Is_Set (Child) then Gdk.Window.Lower (Get_Window (Child)); if Child.State = Floating then Gdk.Window.Lower (Get_Window (Gtk_Window (Get_Toplevel (Child.Initial)))); end if; end if; end Lower_Child; --------------- -- Is_Raised -- --------------- function Is_Raised (Child : access MDI_Child_Record'Class) return Boolean is Note : Gtk_Notebook; begin case Child.State is when Floating => return True; when Invisible => return False; when Normal => Note := Get_Notebook (Child); return Get_Nth_Page (Note, Get_Current_Page (Note)) = Gtk_Widget (Child); end case; end Is_Raised; ----------------- -- Raise_Child -- ----------------- procedure Raise_Child (Child : access MDI_Child_Record'Class; Give_Focus : Boolean := True) is Old_Focus : constant MDI_Child := Child.MDI.Focus_Child; Note : Gtk_Notebook; Current_Focus : MDI_Child; Give : Boolean := Give_Focus; begin Show (Child); -- Make sure the child is visible -- For a docked item, we in fact want to raise its parent dock, -- and make sure the current page in that dock is the correct one. if Child.State = Normal then Note := Get_Notebook (Child); Current_Focus := Child.MDI.Focus_Child; -- We'll have to transfer the focus if the current focus window is in -- the same dock, since otherwise that means an invisible window -- would have the focus. if Current_Focus /= null and then Current_Focus.State = Normal and then Get_Notebook (Current_Focus) = Note then Give := True; end if; -- Temporary fool the system, so that the child doesn't necessarily -- gain the focus. Otherwise, switching a notebook page gives the -- child the focus. Child.MDI.Focus_Child := MDI_Child (Child); -- There could be no parent if we are in all-floating mode if Note /= null then Set_Current_Page (Note, Page_Num (Note, Child)); end if; Child.MDI.Focus_Child := Current_Focus; elsif Child.State = Floating and then Give_Focus and then Realized_Is_Set (Child.Initial) then Present (Gtk_Window (Get_Toplevel (Child.Initial))); elsif Realized_Is_Set (Child) then Gdk.Window.Gdk_Raise (Get_Window (Child)); else -- We still need to raise the window, but don't give it the focus -- on systems where it is possible. Gdk.Window.Gdk_Raise (Get_Window (Gtk_Window (Get_Toplevel (Child.Initial)))); end if; -- Give the focus to the Focus_Child, since the notebook page switch -- might have changed that. if not Child.MDI.Loading_Desktop then if not Give then -- This must be done even if Old_Focus = MDI.Focus_Child. -- Otherwise, clicking inside an editor in GPS for instance will -- not properly refresh the outline view Give_Focus_To_Child (Old_Focus); else Print_Debug ("Raise_Child, give focus to " & Get_Title (Child)); Set_Focus_Child (Child); end if; end if; end Raise_Child; ----------------------- -- Update_Float_Menu -- ----------------------- procedure Update_Float_Menu (Child : access MDI_Child_Record'Class) is begin if Child.MDI.Float_Menu_Item /= null then Gtk.Handlers.Handler_Block (Child.MDI.Float_Menu_Item, Child.MDI.Float_Menu_Item_Id); Set_Active (Child.MDI.Float_Menu_Item, Child.State = Floating); Gtk.Handlers.Handler_Unblock (Child.MDI.Float_Menu_Item, Child.MDI.Float_Menu_Item_Id); end if; end Update_Float_Menu; ------------------- -- Has_Title_Bar -- ------------------- function Has_Title_Bar (Child : access MDI_Child_Record) return Boolean is begin case Child.MDI.Draw_Title_Bars is when Always => return True; when Never => return False; when Central_Only => return In_Central_Area (Child.MDI, Child); end case; end Has_Title_Bar; ---------------------- -- Update_Tab_Color -- ---------------------- procedure Update_Tab_Color (Child : access MDI_Child_Record'Class) is Color : Gdk_Color := Get_Bg (Get_Default_Style, State_Normal); Note : constant Gtk_Notebook := Get_Notebook (Child); Label : Gtk_Widget; function Color_Equal (A, B : Gdk_Color) return Boolean; -- Coloc comparison not taking into account the Pixel value. ----------------- -- Color_Equal -- ----------------- function Color_Equal (A, B : Gdk_Color) return Boolean is begin return Red (A) = Red (B) and then Green (A) = Green (B) and then Blue (A) = Blue (B); end Color_Equal; begin if Note /= null then if MDI_Child (Child) = Child.MDI.Focus_Child then Color := Child.MDI.Focus_Title_Color; end if; -- If the color is already being applied to this notebook, avoid -- the call to Modify_BG, which is quite costly since it causes -- a queue_resize on the notebook. -- Also avoids a potential loop caused by the behavior above. if not Color_Equal (Get_Bg (Get_Style (Note), State_Normal), Color) and then not Color_Equal (Get_Bg (Gtk.Rc.Get_Style (Note), State_Normal), Color) then Modify_Bg (Note, State_Normal, Color); Label := Get_Tab_Label (Note, Child); if Label /= null then Modify_Bg (Label, State_Normal, Color); end if; end if; end if; end Update_Tab_Color; --------------------- -- Set_Focus_Child -- --------------------- procedure Set_Focus_Child (Child : access MDI_Child_Record) is Old : constant MDI_Child := Child.MDI.Focus_Child; C : constant MDI_Child := MDI_Child (Child); Tmp : Boolean; pragma Unreferenced (Tmp); Previous_Focus_Child : constant MDI_Child := Child.MDI.Focus_Child; begin if Child.MDI.Loading_Desktop then return; end if; -- Be lazy. And avoid infinite loop when updating the MDI menu... if C = Old or else Gtk.Object.In_Destruction_Is_Set (C.MDI) then return; end if; -- It is possible that this function is called before the child is -- even in the list of items. In this case, we do nothing at this -- point (might be called because we insert the child in a notebook -- first for instance) if Widget_List.Find (C.MDI.Items, Gtk_Widget (Child)) = Null_List then return; end if; Show (C); -- Make sure the child is visible Child.MDI.Focus_Child := C; Print_Debug ("Set_Focus_Child on " & Get_Title (C)); if Previous_Focus_Child /= null then Update_Tab_Color (Previous_Focus_Child); end if; Update_Tab_Color (C); Ref (C); Remove (C.MDI.Items, Gtk_Widget (Child)); Prepend (C.MDI.Items, Gtk_Widget (Child)); Unref (C); -- Make sure the page containing Child in a notebook is put on top. -- Do not raise floating children, since this is the role of the window -- manager. if C.State /= Floating then Print_Debug ("Set_Focus_Child, raise child " & Get_Title (C)); Raise_Child (C, False); end if; -- Give the actual keyboard focus to the appropriate subwindow of -- the focus child. Give_Focus_To_Child (Child.MDI.Focus_Child); if Old /= null and then Realized_Is_Set (Old) then Queue_Draw_Area (Old.Title_Box, 0, 0, Gint (Get_Allocation_Width (Old.Title_Box)), Gint (Get_Allocation_Width (Old.Title_Box))); end if; if Realized_Is_Set (C.Initial) then Queue_Draw_Area (C.Title_Box, 0, 0, Gint (Get_Allocation_Width (C.Title_Box)), Gint (Get_Allocation_Height (C.Title_Box))); -- Give the focus to the window containing the child. -- Giving the focus to a window has the side effect of moving the -- window to the current desktop. Therefore, we only do this when the -- input focus was already on a window of the MDI. if not Child.MDI.Loading_Desktop and then Previous_Focus_Child /= null and then Realized_Is_Set (Get_Toplevel (Previous_Focus_Child.Initial)) and then Get_Property (Get_Toplevel (Previous_Focus_Child.Initial), Has_Toplevel_Focus_Property) then Raise_Child (C); end if; end if; Update_Float_Menu (C); if C.MDI.Close_Menu_Item /= null then Set_Sensitive (C.MDI.Close_Menu_Item, (C.Flags and Destroy_Button) /= 0); end if; if C.Menu_Item /= null then Set_Active (C.Menu_Item, True); end if; -- It would be nice to find the first child of C.Initial that -- accepts the keyboard focus. However, in the meantime, we at least -- want to make sure that no other widget has the focus. As a result, -- focus_in events will always be sent the next time the user selects a -- widget. Highlight_Child (C, False); Widget_Callback.Emit_By_Name (C, "selected"); Emit_By_Name_Child (Get_Object (C.MDI), String (Signal_Child_Selected) & ASCII.NUL, Get_Object (C)); end Set_Focus_Child; ------------------ -- Delete_Child -- ------------------ function Delete_Child (Child : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is begin if In_Destruction_Is_Set (MDI_Child (Child).MDI) then -- We can always close a child when the MDI is being destroyed return False; elsif MDI_Child (Child).MDI.Close_Floating_Is_Unfloat and then (MDI_Child (Child).Flags and Always_Destroy_Float) = 0 and then not MDI_Child (Child).MDI.All_Floating_Mode then Float_Child (MDI_Child (Child), False); Print_Debug ("Delete_Child, raising " & Get_Title (MDI_Child (Child))); Raise_Child (MDI_Child (Child), False); return True; else return Return_Callback.Emit_By_Name (MDI_Child (Child).Initial, "delete_event", Event); end if; end Delete_Child; --------------------------- -- Key_Event_In_Floating -- --------------------------- function Key_Event_In_Floating (Win : access Gtk_Widget_Record'Class; Event : Gdk_Event) return Boolean is begin -- Note: the following works because we are connected after the standard -- keypress event. Otherwise, standard keys in the child (space in -- editors most notably) will not work as expected. if Get_Event_Type (Event) = Key_Press then return Return_Callback.Emit_By_Name (Win, Signal_Key_Press_Event, Event); else return Return_Callback.Emit_By_Name (Win, Signal_Key_Release_Event, Event); end if; end Key_Event_In_Floating; ----------------- -- Float_Child -- ----------------- procedure Float_Child (Child : access MDI_Child_Record'Class; Float : Boolean) is begin Internal_Float_Child (Child, Float, Position_At_Mouse => True, X => 0, Y => 0); end Float_Child; -------------------------- -- Internal_Float_Child -- -------------------------- procedure Internal_Float_Child (Child : access MDI_Child_Record'Class; Float : Boolean; Position_At_Mouse : Boolean; X, Y : Gint) is use Object_List; Diag : Gtk_Dialog; Win : Gtk_Window; Cont : Gtk_Container; Requisition : Gtk_Requisition; Groups : Object_List.GSlist; W, H : Gint; begin Print_Debug ("Float_Child " & Get_Title (Child) & " State=" & State_Type'Image (Child.State) & " Float=" & Boolean'Image (Float)); -- If the Child already has a window, the resulting floating window -- should have the same size. -- Otherwise, ask the Child for its requisiton. if Mapped_Is_Set (Child) then W := Get_Allocation_Width (Child); H := Get_Allocation_Height (Child); else Size_Request (Child, Requisition); W := Requisition.Width; H := Requisition.Height; end if; if Child.State /= Floating and then Float then -- Ref is removed when the child is unfloated Ref (Child); -- This could be called before the child even has a parent if -- All_Floating_Mode is set. if Get_Parent (Child) /= null then Remove (Gtk_Container (Get_Parent (Child)), Child); end if; if (Child.Flags and Float_As_Transient) /= 0 then declare Parent : Gtk_Window; Item : Widget_List.Glist; It : MDI_Child; begin -- If the current child is floating, we do not want to float -- the dialog as transient for the main window, but for the -- current child. -- ??? Should we introduce a flag for childs that are allways -- transient for the main window ? Item := Child.MDI.Items; while Item /= Widget_List.Null_List loop It := MDI_Child (Get_Data (Item)); if It /= MDI_Child (Child) then if It.State = Floating and then Realized_Is_Set (It.Initial) then Parent := Gtk_Window (Get_Toplevel (It.Initial)); else Parent := Gtk_Window (Get_Toplevel (Child.MDI)); end if; exit; end if; Item := Widget_List.Next (Item); end loop; Gtk_New (Diag, Title => "", Parent => Parent, Flags => No_Separator or Destroy_With_Parent); end; Win := Gtk_Window (Diag); Cont := Gtk_Container (Get_Vbox (Diag)); else Gtk_New (Win); Cont := Gtk_Container (Win); end if; if Child.MDI.Use_Short_Titles_For_Floats then Set_Title (Win, Locale_From_UTF8 (Child.Short_Title.all)); else Set_Title (Win, Locale_From_UTF8 (Child.Title.all)); end if; Set_Default_Size (Win, W, H); -- Memorize the MDI_Child associated with the window, for faster -- lookup for instance in Find_MDI_Child_From_Widget. Child_User_Data.Set (Win, MDI_Child (Child), "parent_mdi_child"); -- Set the accelerators for this window, so that menu key shortcuts -- behave the same as in the main window. -- ??? Should we do the same for mnemonics, even though the menu -- bar isn't available on this floating window. Groups := From_Object (Get_Toplevel (Child.MDI)); while Groups /= Object_List.Null_List loop Add_Accel_Group (Win, Gtk_Accel_Group (Get_Data (Groups))); Groups := Next (Groups); end loop; if Position_At_Mouse then Set_Position (Win, Win_Pos_Mouse); else Set_UPosition (Win, X, Y); Set_Position (Win, Win_Pos_None); end if; -- Delete_Event should be forwarded to the child, not to the -- toplevel window Return_Callback.Object_Connect (Win, Signal_Delete_Event, Return_Callback.To_Marshaller (Delete_Child'Access), Child); Add_Events (Win, Enter_Notify_Mask); Return_Callback.Object_Connect (Win, Signal_Focus_In_Event, Return_Callback.To_Marshaller (Set_Focus_Child_MDI_Floating'Access), Child); -- Forward all key events to the toplevel of the MDI. This provides -- proper handling of menu key shortcuts. Return_Callback.Object_Connect (Win, Signal_Key_Press_Event, Return_Callback.To_Marshaller (Key_Event_In_Floating'Access), Gtk_Window (Get_Toplevel (Child.MDI)), After => True); Return_Callback.Object_Connect (Win, Signal_Key_Release_Event, Return_Callback.To_Marshaller (Key_Event_In_Floating'Access), Gtk_Window (Get_Toplevel (Child.MDI)), After => True); Reparent (Get_Parent (Child.Initial), Cont); Set_State (Child, Floating); Update_Float_Menu (Child); Emit_By_Name_Child (Get_Object (Child.MDI), String (Signal_Float_Child) & ASCII.NUL, Get_Object (Child)); Widget_Callback.Emit_By_Name (Child, Signal_Float_Child); Show_All (Win); elsif Child.State = Floating and then not Float then -- Reassign the widget to Child instead of the notebook Win := Gtk_Window (Get_Toplevel (Child.Initial)); Reparent (Get_Parent (Child.Initial), New_Parent => Gtk_Box (Get_Child (Child))); Set_State (Child, Normal); Destroy (Win); Put_In_Notebook (Child.MDI, Child); Update_Float_Menu (Child); Unref (Child); Widget_Callback.Emit_By_Name (Child, Signal_Unfloat_Child); end if; end Internal_Float_Child; ----------------- -- Is_Floating -- ----------------- function Is_Floating (Child : access MDI_Child_Record'Class) return Boolean is begin return Child.State = Floating; end Is_Floating; ---------------- -- On_Tab_Pos -- ---------------- package Tab_Pos_Callback is new Gtk.Handlers.User_Callback (Gtk_Notebook_Record, Gtk.Enums.Gtk_Position_Type); procedure On_Tab_Pos (Note : access Gtk_Notebook_Record'Class; Pos : Gtk.Enums.Gtk_Position_Type); procedure On_Tab_Pos (Note : access Gtk_Notebook_Record'Class; Pos : Gtk.Enums.Gtk_Position_Type) is begin Set_Tab_Pos (Note, Pos); end On_Tab_Pos; ------------- -- Get_MDI -- ------------- function Get_MDI (Child : access MDI_Child_Record) return MDI_Window is begin return Child.MDI; end Get_MDI; ------------------------------------- -- Set_Tab_Contextual_Menu_Factory -- ------------------------------------- procedure Set_Tab_Contextual_Menu_Factory (MDI : access MDI_Window_Record; Factory : Tab_Contextual_Menu_Factory) is begin MDI.Tab_Factory := Factory; end Set_Tab_Contextual_Menu_Factory; ------------------------------ -- On_Notebook_Button_Press -- ------------------------------ function On_Notebook_Button_Press (Child : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event) return Boolean is C : constant MDI_Child := MDI_Child (Child); Note : constant Gtk_Notebook := Get_Notebook (C); Menu : Gtk_Menu; Submenu : Gtk_Menu; Item : Gtk_Menu_Item; begin if Get_Button (Event) = 3 then Gtk_New (Menu); Gtk_New (Item, "Close"); Widget_Callback.Object_Connect (Item, Gtk.Menu_Item.Signal_Activate, Close_Cb'Access, Child); Append (Menu, Item); Gtk_New (Item, "Tabs location"); Append (Menu, Item); Gtk_New (Submenu); Set_Submenu (Item, Submenu); Gtk_New (Item, "Top"); Tab_Pos_Callback.Object_Connect (Item, Gtk.Menu_Item.Signal_Activate, On_Tab_Pos'Access, Note, Pos_Top); Append (Submenu, Item); Gtk_New (Item, "Bottom"); Tab_Pos_Callback.Object_Connect (Item, Gtk.Menu_Item.Signal_Activate, On_Tab_Pos'Access, Note, Pos_Bottom); Append (Submenu, Item); Gtk_New (Item, "Left"); Tab_Pos_Callback.Object_Connect (Item, Gtk.Menu_Item.Signal_Activate, On_Tab_Pos'Access, Note, Pos_Left); Append (Submenu, Item); Gtk_New (Item, "Right"); Tab_Pos_Callback.Object_Connect (Item, Gtk.Menu_Item.Signal_Activate, On_Tab_Pos'Access, Note, Pos_Right); Append (Submenu, Item); if C.MDI.Tab_Factory /= null then C.MDI.Tab_Factory (C, Menu); end if; Show_All (Menu); Popup (Menu, Button => 3, Activate_Time => Gdk.Event.Get_Time (Event)); return True; end if; return False; end On_Notebook_Button_Press; --------------------- -- Create_Notebook -- --------------------- function Create_Notebook (MDI : access MDI_Window_Record'Class) return Gtk_Notebook is Notebook : Gtk_Notebook; begin Notebook := new MDI_Notebook_Record; Gtk.Notebook.Initialize (Notebook); Configure_Notebook_Tabs (MDI, Notebook); Set_Show_Border (Notebook, False); Set_Border_Width (Notebook, 0); Set_Scrollable (Notebook); Set_Tab_Pos (Notebook, MDI.Tabs_Position); Widget_Callback.Connect (Notebook, Signal_Remove, Removed_From_Notebook'Access); Widget_Callback.Connect (Notebook, Signal_Set_Focus_Child, Set_Focus_Child_Notebook'Access); Widget_Callback.Connect (Notebook, Signal_Switch_Page, Set_Focus_Child_Switch_Notebook_Page'Access); return Notebook; end Create_Notebook; ----------------------------- -- Configure_Notebook_Tabs -- ----------------------------- procedure Configure_Notebook_Tabs (MDI : access MDI_Window_Record'Class; Notebook : access Gtk_Notebook_Record'Class; Hide_If_Empty : Boolean := False) is Child : MDI_Child; Page_Count : constant Gint := Get_N_Pages (Notebook); Visible_Page_Count : Natural := 0; begin Set_Property (Notebook, Tab_Border_Property, 0); -- Some pages might be hidden, in which case they should not be counted -- when we compute whether the tabs should be made visible for P in 0 .. Page_Count - 1 loop if Visible_Is_Set (Get_Nth_Page (Notebook, P)) then Visible_Page_Count := Visible_Page_Count + 1; end if; end loop; if Visible_Page_Count >= 2 then Set_Show_Tabs (Notebook, MDI.Show_Tabs_Policy /= Never); else Set_Show_Tabs (Notebook, MDI.Show_Tabs_Policy = Always); end if; Child := MDI_Child (Get_Nth_Page (Notebook, 0)); if Child = null then null; elsif Get_Nth_Page (Notebook, 1) /= null or else MDI.Show_Tabs_Policy = Always then Set_Property (Notebook, Show_Border_Property, False); else Set_Property (Notebook, Show_Border_Property, True); end if; if Hide_If_Empty then if Visible_Page_Count = 0 then Hide (Notebook); else Show (Notebook); end if; end if; end Configure_Notebook_Tabs; ---------------------- -- Update_Tab_Label -- ---------------------- procedure Update_Tab_Label (Child : access MDI_Child_Record'Class) is Note : constant Gtk_Notebook := Get_Notebook (Child); Event : Gtk_Event_Box; Box : Gtk_Box; Pix : Gdk_Pixmap; Mask : Gdk_Bitmap; Pixmap : Gtk_Image; Close : Close_Button.Gtkada_MDI_Close_Button; Fixed : Gtk_Fixed; begin if Note /= null and then Child.State = Normal then Gtk_New (Event); -- This fails with gtk+ 2.2.0, -- Set_Flags (Event, No_Window); -- Instead, for 2.4.0, we use the following proper call, -- even though the corresponding function doesn't exist in -- 2.2. This means that 2.2 will have a bug that sometimes -- the background color of tabs, when no title bars are -- displayed, will not be correct, with a grey rectangle -- where the label is. Set_Visible_Window (Event, False); Gtk_New_Hbox (Box, Homogeneous => False); if Child.Icon /= null then Render_Pixmap_And_Mask_For_Colormap (Child.Icon, Get_Default_Colormap, Pix, Mask, 128); Gtk_New (Pixmap, Pix, Mask); Gdk.Drawable.Unref (Pix); Gdk.Drawable.Unref (Mask); Pack_Start (Box, Pixmap, Expand => False); elsif (Child.Flags and Destroy_Button) /= 0 then -- No pixmap but we will display a close button, let's add an -- empty space to center the label. Gtk.Fixed.Gtk_New (Fixed); Set_USize (Fixed, 12, 1); Pack_Start (Box, Fixed, Expand => False, Padding => 2); end if; Gtk_New (Child.Tab_Label, Child.Short_Title.all); Pack_Start (Box, Child.Tab_Label, Expand => True, Fill => True); if (Child.Flags and Destroy_Button) /= 0 then Close_Button.Gtk_New (Close, Event, Child, False); Pack_End (Box, Close, Expand => False, Padding => 2); end if; Add (Event, Box); Set_Tab_Label (Note, Child, Event); Show_All (Event); Update_Tab_Color (Child); Return_Callback.Object_Connect (Event, Signal_Button_Press_Event, Return_Callback.To_Marshaller (Set_Focus_Child_MDI_From_Tab'Access), Child); Return_Callback.Object_Connect (Event, Signal_Button_Press_Event, Return_Callback.To_Marshaller (On_Notebook_Button_Press'Access), Child); Return_Callback.Object_Connect (Event, Signal_Button_Release_Event, Return_Callback.To_Marshaller (Set_Focus_Child_MDI_From_Tab'Access), Child); -- Setup drag-and-drop, so that items can be moved from one location -- to another. Set_Dnd_Source (Event, Child); end if; end Update_Tab_Label; ----------------- -- Note_Notify -- ----------------- procedure Note_Notify (Data : System.Address; Where : System.Address) is pragma Unreferenced (Where); Old_Note_Was_Destroyed : aliased Boolean; for Old_Note_Was_Destroyed'Address use Data; begin Old_Note_Was_Destroyed := True; end Note_Notify; --------------------- -- Put_In_Notebook -- --------------------- procedure Put_In_Notebook (MDI : access MDI_Window_Record'Class; Child : access MDI_Child_Record'Class; Notebook : Gtk_Notebook := null; Initial_Position : Child_Position := Position_Automatic; Force_Parent_Destruction : Boolean := True) is Note : Gtk_Notebook; Old_Parent : Gtk_Container; Destroy_Old : Boolean := False; Old_Note_Was_Destroyed : aliased Boolean := False; begin -- Embed the contents of the child into the notebook if Notebook /= null then Note := Notebook; elsif Child.Group = Group_Default and then not MDI.Independent_Perspectives and then MDI.Central /= null then Note := Find_Current_In_Central (MDI.Central, MDI, Child.Group, Initial_Position); else Note := Find_Current_In_Central (MDI, MDI, Child.Group, Initial_Position); end if; if Get_Parent (Child) = Gtk_Widget (Note) then return; end if; Ref (Child); if Get_Parent (Child) /= null then Old_Parent := Gtk_Container (Get_Parent (Child)); -- Always destroy the notebook we were in, since we are -- putting the item elsewhere anyway, there will still be -- a notebook for items in the same position. Destroy_Old := Force_Parent_Destruction and then Old_Parent.all in Gtk_Notebook_Record'Class and then Get_Nth_Page (Gtk_Notebook (Old_Parent), 1) = null; Weak_Ref (Old_Parent, Note_Notify'Access, Old_Note_Was_Destroyed'Address); Remove (Old_Parent, Child); if not Old_Note_Was_Destroyed then Weak_Unref (Old_Parent, Note_Notify'Access, Old_Note_Was_Destroyed'Address); end if; -- Problem: Old_Note might no longer exist not, since -- Removed_From_Notebook might have destroyed it. if Destroy_Old and then not Old_Note_Was_Destroyed then Destroy (Old_Parent); end if; end if; Set_State (Child, Normal); Append_Page (Note, Child); Set_Tab_Reorderable (Note, Child, Reorderable => True); Configure_Notebook_Tabs (MDI, Note); Update_Tab_Label (Child); -- In case the user displays title bars only in the central area, we -- might need to change its visibility when moving in or out of the -- central area Set_Child_Title_Bar (Child); Set_Child_Visible (Note, True); Show (Note); Queue_Resize (Note); Unref (Child); end Put_In_Notebook; ----------------------------- -- Find_Current_In_Central -- ----------------------------- function Find_Current_In_Central (Pane : access Gtkada_Multi_Paned_Record'Class; MDI : access MDI_Window_Record'Class; Group : Child_Group := Group_Any; Initial_Position : Child_Position := Position_Automatic) return Gtk_Notebook is List : Widget_List.Glist := MDI.Items; C : MDI_Child; Note : Gtk_Notebook; Current : Gtk_Notebook; Default_Current_Found : Boolean := False; begin if Gtkada_Multi_Paned (Pane) = Gtkada_Multi_Paned (MDI) then -- Do we already have a child within the same group ? while List /= Widget_List.Null_List loop C := MDI_Child (Get_Data (List)); if C.State = Normal then Note := Get_Notebook (C); if Current = null then Current := Note; end if; if not Default_Current_Found and then C.Group = Group_Default then Default_Current_Found := True; Current := Note; end if; exit when Note /= null and then (Group = Group_Any or else C.Group = Group); Note := null; end if; List := Next (List); end loop; else -- In the central area, look for the last child used, and put the new -- window on top of it if not MDI.Independent_Perspectives then while List /= Widget_List.Null_List loop C := MDI_Child (Get_Data (List)); if In_Central_Area (MDI, C) then Note := Get_Notebook (C); Current := Note; exit; end if; List := Next (List); end loop; end if; -- No last child ? It means the central area is empty (or contains -- an empty notebook, in case we could not reload the desktop, for -- instance because a file previously edited no longer exists). if Current = null then if Traces then Print_Debug ("Find_Current_In_Central: no last child in ," & " checking whether we have an empty notebook"); end if; if not MDI.Independent_Perspectives then declare Iter : Gtkada.Multi_Paned.Child_Iterator := Start (MDI.Central); begin while not At_End (Iter) and then Get_Widget (Iter) = null loop Next (Iter); end loop; if not At_End (Iter) then Print_Debug ("Found empty notebook, using it"); Note := Gtk_Notebook (Get_Widget (Iter)); Current := Note; end if; end; end if; -- Current might still be null if the central area really is empty end if; end if; if Note = null then if Traces then Print_Debug ("no notebook yet, Position=" & Child_Position'Image (Initial_Position)); end if; case Initial_Position is when Position_Bottom => Note := Create_Notebook (MDI); Split (Pane, New_Child => Note, Ref_Pane => Root_Pane, Orientation => Orientation_Vertical, Width => -1, Height => -1, After => True); when Position_Top => Note := Create_Notebook (MDI); Split (Pane, New_Child => Note, Ref_Pane => Root_Pane, Orientation => Orientation_Vertical, Width => -1, Height => -1, After => False); when Position_Left => Note := Create_Notebook (MDI); Split (Pane, New_Child => Note, Ref_Pane => Root_Pane, Orientation => Orientation_Horizontal, Width => -1, Height => -1, After => False); when Position_Right => Note := Create_Notebook (MDI); Split (Pane, New_Child => Note, Ref_Pane => Root_Pane, Orientation => Orientation_Horizontal, Width => -1, Height => -1, After => True); when others => if Current /= null then Note := Current; else Note := Create_Notebook (MDI); Add_Child (Pane, New_Child => Note, Width => -1, Height => -1); end if; end case; end if; return Note; end Find_Current_In_Central; --------------------------- -- Set_All_Floating_Mode -- --------------------------- procedure Set_All_Floating_Mode (MDI : access MDI_Window_Record; All_Floating : Boolean) is List : Widget_List.Glist := First (MDI.Items); C : MDI_Child; begin if All_Floating /= MDI.All_Floating_Mode then -- We cannot do a simple loop here. When a child is floated, it -- can happen that the mouse enters the window, and the focus changes -- immediately, resulting in a change in the order of children in the -- list, even though not all windows have been floated yet. MDI.All_Floating_Mode := All_Floating; loop List := First (MDI.Items); while List /= Null_List loop C := MDI_Child (Get_Data (List)); if (C.State /= Floating and then All_Floating) or else (C.State = Floating and then not All_Floating) then Float_Child (C, All_Floating); exit; end if; List := Next (List); end loop; exit when List = Null_List; end loop; Set_Sensitive (MDI.Float_Menu_Item, not All_Floating); Set_Child_Visible (MDI, not All_Floating); -- Force a recomputation of the size Resize (Gtk_Window (Get_Toplevel (MDI)), -1, -1); end if; end Set_All_Floating_Mode; --------------------------------- -- Use_Short_Titles_For_Floats -- --------------------------------- procedure Use_Short_Titles_For_Floats (MDI : access MDI_Window_Record; Short_Titles : Boolean) is List : Widget_List.Glist := First (MDI.Items); Child : MDI_Child; begin if MDI.Use_Short_Titles_For_Floats = Short_Titles then -- Nothing to be changed return; end if; MDI.Use_Short_Titles_For_Floats := Short_Titles; -- The property has been changed. We need to walk though all children -- and enforce the title to the short one for floating children. loop List := First (MDI.Items); while List /= Null_List loop Child := MDI_Child (Get_Data (List)); if Child.State = Floating then if Short_Titles then Set_Title (Gtk_Window (Get_Toplevel (Child.Initial)), Locale_From_UTF8 (Child.Short_Title.all)); else Set_Title (Gtk_Window (Get_Toplevel (Child.Initial)), Locale_From_UTF8 (Child.Title.all)); end if; end if; List := Next (List); end loop; exit when List = Null_List; end loop; end Use_Short_Titles_For_Floats; ---------------- -- Get_Widget -- ---------------- function Get_Widget (Child : access MDI_Child_Record) return Gtk_Widget is begin return Child.Initial; end Get_Widget; --------------------- -- Get_Focus_Child -- --------------------- function Get_Focus_Child (MDI : access MDI_Window_Record) return MDI_Child is begin return MDI.Focus_Child; end Get_Focus_Child; --------------------------- -- Removed_From_Notebook -- --------------------------- procedure Removed_From_Notebook (Note : access Gtk_Widget_Record'Class; Args : Gtk_Args) is C : constant Gtk_Widget := Gtk_Widget (To_Object (Args, 1)); Child : MDI_Child; begin if C.all not in MDI_Child_Record'Class then return; end if; Child := MDI_Child (C); Child.Tab_Label := null; Set_State (Child, Normal); if not Gtk.Object.In_Destruction_Is_Set (Note) then Print_Debug ("Removed_From_Notebook: " & Get_Title (Child)); -- No more pages in the notebook ? => Destroy it if Get_Nth_Page (Gtk_Notebook (Note), 0) = null then Destroy (Note); else Configure_Notebook_Tabs (Child.MDI, Gtk_Notebook (Note), Hide_If_Empty => True); end if; if Traces then Print_Debug ("Removed_From_Notebook: desktop is now"); Dump (Child.MDI); end if; end if; exception when E : others => -- Silently ignore the exceptions for now, to avoid crashes. -- The application using the MDI can not do it, since this callback -- is called directly from the menu in Create_Menu. pragma Debug (Put_Line ("Unexpected exception: " & Exception_Information (E))); null; end Removed_From_Notebook; ----------- -- Split -- ----------- 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) is Note, Note2 : Gtk_Notebook; Target : MDI_Child; Pane : Gtkada_Multi_Paned; W : Gtk_Widget; After : Boolean := True; begin if Child /= null then Target := Child; elsif MDI.Focus_Child /= null then Target := MDI.Focus_Child; elsif MDI.Items = Widget_List.Null_List then return; else Target := MDI_Child (Get_Data (MDI.Items)); end if; Note := Get_Notebook (Target); -- Only split if there are at least two children if Note /= null and then Get_Nth_Page (Note, 1) /= null then if In_Central_Area (MDI, Target) then Pane := MDI.Central; else Pane := Gtkada_Multi_Paned (MDI); end if; case Mode is when Before => Note2 := null; After := False; when Gtkada.MDI.After => Note2 := null; After := True; when Before_Reuse => W := Splitted_Area (Pane, Note, Orientation, After => False); After := False; when After_Reuse => W := Splitted_Area (Pane, Note, Orientation, After => True); After := True; when Any_Side_Reuse => W := Splitted_Area (Pane, Note, Orientation, After => True); if W = null then W := Splitted_Area (Pane, Note, Orientation, After => False); end if; After := True; end case; if W /= null and then W.all in Gtk_Notebook_Record'Class then Note2 := Gtk_Notebook (W); end if; if Note2 = null then Note2 := Create_Notebook (MDI); Show_All (Note2); Split (Pane, Ref_Widget => Note, New_Child => Note2, Width => Width, Height => Height, Orientation => Orientation, After => After); end if; Show (Note2); Ref (Target); Give_Focus_To_Previous_Child (Target); Remove (Note, Target); Put_In_Notebook (MDI, Target, Note2); Unref (Target); Set_Focus_Child (Target); Emit_By_Name (Get_Object (MDI), String (Signal_Children_Reorganized) & ASCII.NUL); end if; if Traces then Print_Debug ("After split " & Gtk_Orientation'Image (Orientation)); Dump (MDI); end if; end Split; ---------------- -- Split_H_Cb -- ---------------- procedure Split_H_Cb (MDI : access Gtk_Widget_Record'Class) is M : constant MDI_Window := MDI_Window (MDI); begin -- Do nothing unless the current child is in the central area, since -- otherwise this is disturbing for the user if M.Focus_Child /= null and then M.Focus_Child.State = Normal then Split (M, Orientation => Orientation_Horizontal); end if; exception when E : others => pragma Debug (Put_Line ("Unexpected exception: " & Exception_Information (E))); null; end Split_H_Cb; ---------------- -- Split_V_Cb -- ---------------- procedure Split_V_Cb (MDI : access Gtk_Widget_Record'Class) is begin -- Do nothing unless the current child is in the central area, since -- otherwise this is disturbing for the user if MDI_Window (MDI).Focus_Child /= null and then MDI_Window (MDI).Focus_Child.State = Normal then Split (MDI_Window (MDI), Orientation => Orientation_Vertical); end if; exception when E : others => pragma Debug (Put_Line ("Unexpected exception: " & Exception_Information (E))); null; end Split_V_Cb; -------------- -- Float_Cb -- -------------- procedure Float_Cb (MDI : access Gtk_Widget_Record'Class) is C : MDI_Child; begin if MDI.all in MDI_Window_Record'Class then C := Get_Focus_Child (MDI_Window (MDI)); else C := MDI_Child (MDI); end if; if C /= null then Float_Child (C, C.State /= Floating); Set_Focus_Child (C); Raise_Child (C, False); end if; exception when E : others => -- Silently ignore the exceptions for now, to avoid crashes. -- The application using the MDI can not do it, since this callback -- is called directly from the menu in Create_Menu. pragma Debug (Put_Line ("Unexpected exception: " & Exception_Information (E))); null; end Float_Cb; -------------- -- Close_Cb -- -------------- procedure Close_Cb (MDI : access Gtk_Widget_Record'Class) is C : MDI_Child; begin if MDI.all in MDI_Window_Record'Class then C := MDI_Window (MDI).Focus_Child; -- Close automatically gets the contents of docks, instead of the -- dock itself else C := MDI_Child (MDI); end if; if C /= null then Close_Child (C); end if; exception when E : others => -- Silently ignore the exceptions for now, to avoid crashes. -- The application using the MDI can not do it, since this callback -- is called directly from the menu in Create_Menu. pragma Debug (Put_Line ("Unexpected exception: " & Exception_Information (E))); null; end Close_Cb; -------------- -- Focus_Cb -- -------------- procedure Focus_Cb (Child : access Gtk_Widget_Record'Class) is C : constant MDI_Child := MDI_Child (Child); begin if Get_Active (C.Menu_Item) then -- If C is floating, raise the window. if C.State = Floating then Raise_Child (C, True); end if; Set_Focus_Child (C); end if; end Focus_Cb; -------------------------- -- Menu_Entry_Destroyed -- -------------------------- procedure Menu_Entry_Destroyed (Child : access Gtk_Widget_Record'Class) is begin MDI_Child (Child).Menu_Item := null; end Menu_Entry_Destroyed; ----------------------- -- Create_Menu_Entry -- ----------------------- procedure Create_Menu_Entry (Child : access MDI_Child_Record'Class) is use Widget_SList; G : Widget_SList.GSlist := Widget_SList.Null_List; First_Child : MDI_Child; Tmp : Widget_List.Glist; Position : Gint; Children : Widget_List.Glist; Item : Gtk_Menu_Item; Ref : String_Access; begin if Child.Menu_Item = null and then Child.Short_Title.all /= "" then -- Find the group to which the radio menu items should belong. We -- cannot save this group into a variable, since it might change when -- the first child is removed from the MDI. Tmp := Child.MDI.Items; while Tmp /= Widget_List.Null_List loop First_Child := MDI_Child (Get_Data (Tmp)); if First_Child.Menu_Item /= null then G := Get_Group (First_Child.Menu_Item); -- Find the closest menu item, to keep the Window menu sorted if First_Child.Short_Title.all > Child.Short_Title.all and then (Ref = null or else First_Child.Short_Title.all < Ref.all) then Ref := First_Child.Short_Title; Item := Gtk_Menu_Item (First_Child.Menu_Item); end if; end if; Tmp := Next (Tmp); end loop; -- Insert the new item sorted in the Window menu if Item = null then Position := -1; else Position := 0; Children := Get_Children (Child.MDI.Menu); Tmp := Children; while Tmp /= Widget_List.Null_List loop exit when Gtk_Menu_Item (Get_Data (Tmp)) = Item; Position := Position + 1; Tmp := Next (Tmp); end loop; Free (Children); end if; Gtk_New (Child.Menu_Item, G, ""); Update_Menu_Item (Child); Insert (Child.MDI.Menu, Child.Menu_Item, Position); Set_Active (Child.Menu_Item, MDI_Child (Child) = Child.MDI.Focus_Child); Show_All (Child.Menu_Item); Widget_Callback.Object_Connect (Child.Menu_Item, Gtk.Menu_Item.Signal_Activate, Widget_Callback.To_Marshaller (Focus_Cb'Access), Child, After => True); Widget_Callback.Object_Connect (Child.Menu_Item, Signal_Destroy, Widget_Callback.To_Marshaller (Menu_Entry_Destroyed'Access), Child); end if; end Create_Menu_Entry; -------------------- -- Menu_Destroyed -- -------------------- procedure Menu_Destroyed (MDI : access Gtk_Widget_Record'Class) is begin MDI_Window (MDI).Menu := null; MDI_Window (MDI).Float_Menu_Item := null; end Menu_Destroyed; --------------------- -- Set_Focus_Child -- --------------------- procedure Set_Focus_Child (MDI : access MDI_Window_Record; Containing : access Gtk.Widget.Gtk_Widget_Record'Class) is pragma Unreferenced (MDI); Parent : Gtk_Widget := Gtk_Widget (Containing); begin while Parent /= null and then not (Parent.all in MDI_Child_Record'Class) loop Parent := Get_Parent (Parent); end loop; if Parent /= null then Set_Focus_Child (MDI_Child (Parent)); end if; end Set_Focus_Child; ------------- -- Desktop -- ------------- package body Desktop is procedure Get_XML_For_Widget (Child : MDI_Child; User : User_Data; Widget_Is_Unique : out Boolean; Data : out Node_Ptr); -- Get the XML node for a given widget. This automatically sets -- Child.XML_Node_Name as well. -- Widget_Is_Unique is set to True if there can be only one of the -- corresponding widget. procedure Parse_Child_Node (MDI : access MDI_Window_Record'Class; Child_Node : Node_Ptr; User : User_Data; Focus_Child : in out MDI_Child; X : out Gint; Y : out Gint; Raised : out Boolean; State : out State_Type; Child : out MDI_Child; To_Hide : in out Gtk.Widget.Widget_List.Glist); -- Parse a node and return the corresponding Child. The latter -- has not been inserted in the MDI. procedure Parse_Notebook_Node (MDI : access MDI_Window_Record'Class; Child_Node : Node_Ptr; User : User_Data; Parent_Width, Parent_Height : Gint; Parent_Orientation : Gtk_Orientation; Focus_Child : in out MDI_Child; Width, Height : out Gint; Notebook : out Gtk_Notebook; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Empty_Notebook_Filler : in out MDI_Child); -- Parse a node. -- A new notebook is created and returned. -- If Reuse_Empty_If_Needed and we need to insert an empty notebook, -- we'll try and reuse an existing empty notebook. In this case, the -- variable is set to False. -- To_Raise is the children that are visible in the notebooks. It cannot -- be changed within this procedure, since when other items are loaded -- into the desktop, they might be put in the same notebook temporarily, -- before being moved to their actual location, and that would change -- the current page. -- Paned_Width and Paned_Height are the size of the multi_paned widget -- to which the "width" and "height" attributes are relative. procedure Parse_Pane_Node (Paned : access Gtkada_Multi_Paned_Record'Class; MDI : access MDI_Window_Record'Class; Node : Node_Ptr; Focus_Child : in out MDI_Child; Parent_Width, Parent_Height : Gint; Parent_Orientation : Gtk_Orientation; User : User_Data; Initial_Ref_Child : Gtk_Notebook := null; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Empty_Notebook_Filler : in out MDI_Child); -- Parse a node -- First_Child is the first notebook insert in pane (possibly inserted -- From_Tree points to the project-specific part of the desktop, where -- the contents of the children are saved. procedure Restore_Multi_Pane (Pane : access Gtkada_Multi_Paned_Record'Class; MDI : access MDI_Window_Record'Class; Focus_Child : in out MDI_Child; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Node : Node_Ptr; User : User_Data; Full_Width, Full_Height : Gint); -- Restore a multi paned widget (either the perspective or the contents -- of the editor area) -- From_Tree points to the project-specific part of the desktop, where -- the contents of the children are saved. procedure Internal_Load_Perspective (MDI : access MDI_Window_Record'Class; Name : String; User : User_Data; Focus_Child : in out MDI_Child; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Width, Height : Gint := 0; Do_Size_Allocate : Boolean); -- Internal version of Load_Perspective. -- If Name is "", the first perspective is loaded. procedure Compute_Size_From_Attributes (Node : Node_Ptr; Parent_Width, Parent_Height : Gint; Parent_Orientation : Gtk_Orientation; Width, Height : out Gint; Children_Count : Integer := 1); -- Compute the actual size of the widget represented by node, from the -- attributes of the node ("width" and "height", which use percent of -- the total pane size), and the attributes of the parent container. -- Children_Count is the number of children for the widget represented -- by Node, since the size returned is the one really available for -- sharing between the children (thus omitting the resize handles) procedure Create_Perspective_Menu (MDI : access MDI_Window_Record'Class; User : User_Data); -- Create the /Window/Perspectives submenu procedure Recompute_Perspective_Names (MDI : access MDI_Window_Record'Class); -- Recompute the name of all perspectives, and cache them ------------------------ -- Change_Perspective -- ------------------------ procedure Change_Perspective (Item : access Gtk_Widget_Record'Class) is Persp : constant Perspective_Menu_Item := Perspective_Menu_Item (Item); Name : constant String := Persp.MDI.Perspective_Names (Persp.Name).all; -- Make a copy of the name, since Load_Perspective changes -- Persp.MDI.Perspective_Names begin if Get_Active (Persp) then Print_Debug ("++++ Change_Perspective to " & Name & Integer'Image (Persp.Name)); if not Persp.MDI.Loading_Desktop then Load_Perspective (Persp.MDI, Name, Persp.User); end if; end if; end Change_Perspective; --------------------------- -- Create_Perspective_CB -- --------------------------- procedure Create_Perspective_CB (Item : access Gtk_Widget_Record'Class) is Persp : constant Perspective_Menu_Item := Perspective_Menu_Item (Item); Dialog : Gtk_Dialog; Label : Gtk_Label; Ent : Gtk_Entry; Button : Gtk_Widget; pragma Warnings (Off, Button); begin Gtk_New (Dialog, Title => "Enter perspective name", Parent => Gtk_Window (Get_Toplevel (Persp.MDI)), Flags => Modal and Destroy_With_Parent); Button := Add_Button (Dialog, Stock_Ok, Gtk_Response_OK); Button := Add_Button (Dialog, Stock_Cancel, Gtk_Response_Cancel); Set_Default_Response (Dialog, Gtk_Response_OK); Gtk_New (Label, "Enter name of new perspective:"); Pack_Start (Get_Vbox (Dialog), Label, Expand => False); Gtk_New (Ent); Set_Activates_Default (Ent, True); Pack_Start (Get_Vbox (Dialog), Ent, Expand => False); Show_All (Dialog); if Run (Dialog) = Gtk_Response_OK then Create_Perspective (Persp.MDI, Get_Text (Ent), Persp.User); end if; Destroy (Dialog); end Create_Perspective_CB; ------------------------ -- Create_Perspective -- ------------------------ procedure Create_Perspective (MDI : access MDI_Window_Record'Class; Name : String; User : User_Data) is Perspectives, Central : Node_Ptr; begin MDI.Current_Perspective := null; Save_Desktop (MDI, User, Perspectives, Central); Set_Attribute (MDI.Current_Perspective, "name", Name); Free (Perspectives); Free (Central); Recompute_Perspective_Names (MDI); Create_Perspective_Menu (MDI, User); Emit_By_Name (Get_Object (MDI), String (Signal_Perspective_Changed) & ASCII.NUL); end Create_Perspective; ------------------------ -- Define_Perspective -- ------------------------ procedure Define_Perspective (MDI : access MDI_Window_Record'Class; XML : Glib.Xml_Int.Node_Ptr; User : User_Data) is Name : constant String := Get_Attribute (XML, "name"); Tmp : Node_Ptr; begin if Name = "" or else MDI.Perspectives = null then return; end if; Tmp := MDI.Perspectives.Child; while Tmp /= null loop if Get_Attribute (Tmp, "name") = Name then -- Perspective already exists return; end if; Tmp := Tmp.Next; end loop; Add_Child (MDI.Perspectives, Deep_Copy (XML), Append => True); Create_Perspective_Menu (MDI, User); end Define_Perspective; ----------------------------- -- Create_Perspective_Menu -- ----------------------------- procedure Create_Perspective_Menu (MDI : access MDI_Window_Record'Class; User : User_Data) is Submenu : Gtk_Menu; Persp : Perspective_Menu_Item; Group : Widget_SList.GSlist := Widget_SList.Null_List; begin Print_Debug ("Create_Perspective_Menu"); Indent_Debug (1); -- Prevent changing perspective when setting "Active" on the buttons MDI.Loading_Desktop := True; Gtk_New (Submenu); Set_Submenu (MDI.Perspective_Menu_Item, Submenu); if MDI.Perspective_Names /= null then for N in MDI.Perspective_Names'Range loop Persp := new Perspective_Menu_Item_Record; Persp.MDI := MDI_Window (MDI); Persp.Name := N; Persp.User := User; Initialize (Persp, Group, MDI.Perspective_Names (N).all); Set_Active (Persp, MDI.Current_Perspective /= null and then MDI.Perspective_Names (N).all = Get_Attribute (MDI.Current_Perspective, "name")); Group := Get_Group (Persp); Append (Submenu, Persp); Widget_Callback.Connect (Persp, Gtk.Menu_Item.Signal_Activate, CP_Access); end loop; end if; Persp := new Perspective_Menu_Item_Record; Persp.MDI := MDI_Window (MDI); Persp.User := User; Gtk.Menu_Item.Initialize (Persp, ""); Widget_Callback.Connect (Persp, Gtk.Menu_Item.Signal_Activate, CreateP_Access); Append (Submenu, Persp); Show_All (Submenu); Show (MDI.Perspective_Menu_Item); MDI.Loading_Desktop := False; Indent_Debug (-1); end Create_Perspective_Menu; ----------------- -- Create_Menu -- ----------------- function Create_Menu (MDI : access MDI_Window_Record'Class; Accel_Path_Prefix : String := ""; User : User_Data) return Gtk.Menu.Gtk_Menu is Item : Gtk_Menu_Item; Child : MDI_Child; Tmp : Widget_List.Glist; Sep : Gtk_Separator_Menu_Item; begin if MDI.Menu = null then MDI.Accel_Path_Prefix := new String'(Accel_Path_Prefix); Gtk_New (MDI.Menu); Gtk_New (MDI.Perspective_Menu_Item, "Perspectives"); Append (MDI.Menu, MDI.Perspective_Menu_Item); Create_Perspective_Menu (MDI, User); Gtk_New (Item, "Split Side-by-Side"); Append (MDI.Menu, Item); Widget_Callback.Object_Connect (Item, Gtk.Menu_Item.Signal_Activate, Widget_Callback.To_Marshaller (Split_H_Cb'Access), MDI); Set_Accel_Path (Item, Accel_Path_Prefix & "/window/split_horizontal", MDI.Group); Gtk_New (Item, "Split Up-Down"); Append (MDI.Menu, Item); Widget_Callback.Object_Connect (Item, Gtk.Menu_Item.Signal_Activate, Widget_Callback.To_Marshaller (Split_V_Cb'Access), MDI); Set_Accel_Path (Item, Accel_Path_Prefix & "/window/split_vertical", MDI.Group); Gtk_New (Sep); Append (MDI.Menu, Sep); Gtk_New (MDI.Float_Menu_Item, "Floating"); Append (MDI.Menu, MDI.Float_Menu_Item); Set_Active (MDI.Float_Menu_Item, MDI.Focus_Child /= null and then MDI.Focus_Child.State = Floating); MDI.Float_Menu_Item_Id := Widget_Callback.Object_Connect (MDI.Float_Menu_Item, Signal_Toggled, Widget_Callback.To_Marshaller (Float_Cb'Access), MDI); Set_Accel_Path (MDI.Float_Menu_Item, Accel_Path_Prefix & "/window/floating", MDI.Group); Gtk_New (Sep); Append (MDI.Menu, Sep); Gtk_New (MDI.Close_Menu_Item, "Close"); Append (MDI.Menu, MDI.Close_Menu_Item); Widget_Callback.Object_Connect (MDI.Close_Menu_Item, Gtk.Menu_Item.Signal_Activate, Widget_Callback.To_Marshaller (Close_Cb'Access), MDI); Set_Accel_Path (Item, Accel_Path_Prefix & "/window/close", MDI.Group); Gtk_New (Sep); Append (MDI.Menu, Sep); Tmp := First (MDI.Items); while Tmp /= Null_List loop Child := MDI_Child (Get_Data (Tmp)); Create_Menu_Entry (Child); Tmp := Next (Tmp); end loop; Widget_Callback.Object_Connect (MDI.Menu, Signal_Destroy, Widget_Callback.To_Marshaller (Menu_Destroyed'Access), MDI); elsif Accel_Path_Prefix /= MDI.Accel_Path_Prefix.all then Put_Line ("Accel_Path_Prefix must have the same prefix across calls" & " to Create_Menu"); end if; Show_All (MDI.Menu); return MDI.Menu; end Create_Menu; -------------------------------- -- Register_Desktop_Functions -- -------------------------------- procedure Register_Desktop_Functions (Save : Save_Desktop_Function; Load : Load_Desktop_Function) is begin Registers := new Register_Node_Record' (Save => Save, Load => Load, Next => Registers); end Register_Desktop_Functions; ---------------------------------- -- Compute_Size_From_Attributes -- ---------------------------------- procedure Compute_Size_From_Attributes (Node : Node_Ptr; Parent_Width, Parent_Height : Gint; Parent_Orientation : Gtk_Orientation; Width, Height : out Gint; Children_Count : Integer := 1) is WAttr : constant String := Get_Attribute (Node, "width", "100%"); HAttr : constant String := Get_Attribute (Node, "height", "100%"); Tmp : Gint; begin -- For backward compatibility, we accept absolute sizes in the XML -- nodes, but that might lead to inconsistencies (and incorrect -- reload of desktop) if the user modifies this by hand -- -- Depending on the orientation of the parent, one of the dimensions -- is in fact fixed (the full height or width of the parent). -- -- If there are multiple children (case of panes for instance), the -- size we return is the one really available for children, not the -- physical size of the pane itself. case Parent_Orientation is when Orientation_Horizontal => Height := Parent_Height; if WAttr (WAttr'Last) = '%' then Tmp := Parent_Width - Gint (Children_Count - 1) * Handle_Width; Width := Gint (Float'Value (WAttr (WAttr'First .. WAttr'Last - 1)) * Float (Tmp) / 100.0); else Width := Gint'Value (WAttr); end if; when Orientation_Vertical => Width := Parent_Width; if HAttr (HAttr'Last) = '%' then Tmp := Parent_Height - Gint (Children_Count - 1) * Handle_Width; Height := Gint (Float'Value (HAttr (HAttr'First .. HAttr'Last - 1)) * Float (Tmp) / 100.0); else Height := Gint'Value (HAttr); end if; end case; Print_Debug ("Compute_Size_From_Attributes WAttr=" & WAttr & " HAttr=" & HAttr & " => size " & Gint'Image (Width) & Gint'Image (Height)); end Compute_Size_From_Attributes; ------------------------- -- Parse_Notebook_Node -- ------------------------- procedure Parse_Notebook_Node (MDI : access MDI_Window_Record'Class; Child_Node : Node_Ptr; User : User_Data; Parent_Width, Parent_Height : Gint; Parent_Orientation : Gtk_Orientation; Focus_Child : in out MDI_Child; Width, Height : out Gint; Notebook : out Gtk_Notebook; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Empty_Notebook_Filler : in out MDI_Child) is N : Node_Ptr := Child_Node.Child; State : State_Type; Raised : Boolean; Raised_Child : MDI_Child; Child : MDI_Child; X, Y : Gint; Dummy : Gtk_Label; Pos : Gtk_Position_Type; begin if Traces then Print_Debug ("Parse_Notebook_Node Parent_Width=" & Gint'Image (Parent_Width) & " Parent_Height=" & Gint'Image (Parent_Height) & " Parent_Orientation=" & Gtk_Orientation'Image (Parent_Orientation)); end if; Indent_Debug (1); Compute_Size_From_Attributes (Child_Node, Parent_Width, Parent_Height, Parent_Orientation, Width, Height, Children_Count => 1); Pos := Gtk_Position_Type'Value (Get_Attribute (Child_Node, "Tabs", Gtk_Position_Type'Image (MDI.Tabs_Position))); Print_Debug ("Parse_Notebook_Node: Width=" & Gint'Image (Width) & " Height=" & Gint'Image (Height)); Notebook := Create_Notebook (MDI); Print_Debug ("Parse_Notebook_Node: created new notebook " & System.Address_Image (Notebook.all'Address)); -- Make sure Width and Height are not too small: that could happen -- if the main window has not been resized yet (thus has a size 1x1) -- and we load a perspective (since keeping place for the children -- windows might end up with negative sizes. Width := Gint'Max (Width, -1); Height := Gint'Max (Height, -1); Set_Size_Request (Notebook, Width, Height); Set_Tab_Pos (Notebook, Pos); Set_Child_Visible (Notebook, True); Show_All (Notebook); while N /= null loop if N.Tag.all = "Child" then Parse_Child_Node (MDI, N, User, Focus_Child, X, Y, Raised, State, Child, To_Hide => To_Hide); -- Child cannot be floating while in a notebook if Child /= null then if Raised or else Raised_Child = null or else Focus_Child = Child then Raised_Child := Child; end if; Print_Debug ("Parse_Notebook_Node, moving child into the" & " the notebook"); Float_Child (Child, False); Put_In_Notebook (MDI, Child, Notebook); Print_Debug ("Parse_Notebook_Node, done moving child"); else Print_Debug ("Parse_Notebook_Node: no child created"); end if; else -- Invalid node null; end if; N := N.Next; end loop; Print_Debug ("Parse_Notebook_Node: done adding all children"); -- Create a dummy node if necessary, since otherwise the calls to -- Split afterward will simply discard that notebook. This dummy -- widget is destroyed at the end of restoring the desktop if Child_Node.Child = null and then Empty_Notebook_Filler = null then Gtk_New (Dummy, ""); Gtk_New (Empty_Notebook_Filler, Dummy); Set_Title (Empty_Notebook_Filler, ""); Put (MDI, Empty_Notebook_Filler); Put_In_Notebook (MDI, Empty_Notebook_Filler, Notebook); end if; if Raised_Child /= null then Prepend (To_Raise, Gtk_Widget (Raised_Child)); -- Make sure the child appears first in the list for this -- notebook. That way, if the current focus child is closed by the -- user, we know the focus won't fallback to a child currently not -- visible in the notebook, which would result in a raise. Ref (Raised_Child); Remove (MDI.Items, Gtk_Widget (Raised_Child)); Prepend (MDI.Items, Gtk_Widget (Raised_Child)); Unref (Raised_Child); end if; Indent_Debug (-1); end Parse_Notebook_Node; ---------------------- -- Parse_Child_Node -- ---------------------- procedure Parse_Child_Node (MDI : access MDI_Window_Record'Class; Child_Node : Node_Ptr; User : User_Data; Focus_Child : in out MDI_Child; X : out Gint; Y : out Gint; Raised : out Boolean; State : out State_Type; Child : out MDI_Child; To_Hide : in out Gtk.Widget.Widget_List.Glist) is N : Node_Ptr; Register : Register_Node; W, H : Allocation_Int := -1; Visible : constant Boolean := Boolean'Value (Get_Attribute (Child_Node, "visible", "true")); Iter : Child_Iterator; Tmp : MDI_Child; begin Print_Debug ("Parse_Child_Node"); Indent_Debug (1); Child := null; Raised := False; State := Normal; X := 0; Y := 0; -- Check whether this child was already in a previous perspective. -- If that's the case, reuse it Iter := First_Child (MDI, Visible_Only => False); loop Tmp := Get (Iter); exit when Tmp = null; -- If not already used in the perspective if Tmp.State = Invisible and then Tmp.XML_Node_Name /= null and then Tmp.XML_Node_Name.all = Child_Node.Child.Tag.all then Print_Debug ("Reusing existing hidden view for " & Child_Node.Child.Tag.all); Child := Tmp; Put (MDI, Child); -- put it back in the MDI exit; end if; Next (Iter); end loop; -- Is there data associated with the node (in particular for widgets -- in the central area) if Child = null and then (Child_Node.Child.Child /= null or else (Child_Node.Child.Attributes /= null and then Child_Node.Child.Attributes.all /= "")) then Register := Registers; while Child = null and then Register /= null loop Child := Register.Load (MDI_Window (MDI), Child_Node.Child, User); Register := Register.Next; end loop; end if; -- Check whether we have a project-specific contents for this child. -- This always takes priority other any project-independent contents. -- When we have multiple children with the same XML node name, we -- should use the first project-dependent part, then the second,..., -- and not reuse multiple times the first one. To do this, we simply -- remove the nodes from the project-dependent part as we use them, -- which also saves memory. N := MDI.View_Contents; if Child = null and then N /= null then N := N.Child; while N /= null loop if N.Tag.all = Child_Node.Child.Tag.all then Register := Registers; while Child = null and then Register /= null loop Child := Register.Load (MDI_Window (MDI), N, User); Register := Register.Next; end loop; if Child /= null then Print_Debug ("Found project-specific contents for " & Child_Node.Child.Tag.all); Free (N); exit; end if; end if; N := N.Next; end loop; end if; -- Else search for project-specific contents Register := Registers; while Child = null and then Register /= null loop Child := Register.Load (MDI_Window (MDI), Child_Node.Child, User); if Child /= null then Print_Debug ("Found project-independent contents for " & Child_Node.Child.Tag.all); end if; Register := Register.Next; end loop; if Child = null then Print_Debug ("Parse_Child_Node: Could not create the child"); return; end if; Print_Debug ("Parse_Child_Node: created " & Get_Title (Child)); Child.Group := Child_Group'Value (Get_Attribute (Child_Node, "Group", Child_Group'Image (Child.Group))); State := State_Type'Value (Get_Attribute (Child_Node, "State", "NORMAL")); Raised := Boolean'Value (Get_Attribute (Child_Node, "Raised", "False")); if Boolean'Value (Get_Attribute (Child_Node, "Focus", "False")) then Focus_Child := Child; end if; N := Child_Node.Child.Next; while N /= null loop -- We ignore the and <short_title> fields. After all, -- the callback that created the child has or should have set -- a proper title already, and there is no reason to override -- this. if N.Tag.all = "X" then X := Gint'Value (N.Value.all); elsif N.Tag.all = "Y" then Y := Gint'Value (N.Value.all); elsif N.Tag.all = "width" then W := Gint'Value (N.Value.all); elsif N.Tag.all = "height" then H := Gint'Value (N.Value.all); else -- ??? Unknown node, just ignore for now null; end if; N := N.Next; end loop; if W /= -1 or else H /= -1 then Set_Size_Request (Child, W, H); end if; if not Visible then Print_Debug ("Parse_Child_Node: child will be hidden"); Prepend (To_Hide, Gtk_Widget (Child)); end if; Indent_Debug (-1); end Parse_Child_Node; --------------------- -- Get_XML_Content -- --------------------- function Get_XML_Content (MDI : access MDI_Window_Record'Class; Tag : String) return Glib.Xml_Int.Node_Ptr is function Internal_Get_XML_Content (N : Glib.Xml_Int.Node_Ptr) return Glib.Xml_Int.Node_Ptr; ------------------------------ -- Internal_Get_XML_Content -- ------------------------------ function Internal_Get_XML_Content (N : Glib.Xml_Int.Node_Ptr) return Glib.Xml_Int.Node_Ptr is Node : Glib.Xml_Int.Node_Ptr := N; Child : Glib.Xml_Int.Node_Ptr; begin while Node /= null loop if Node.Tag.all = Tag then return Node; end if; Child := Internal_Get_XML_Content (Node.Child); if Child /= null then return Child; end if; Node := Node.Next; end loop; return null; end Internal_Get_XML_Content; begin return Internal_Get_XML_Content (MDI.View_Contents); end Get_XML_Content; --------------------- -- Parse_Pane_Node -- --------------------- procedure Parse_Pane_Node (Paned : access Gtkada_Multi_Paned_Record'Class; MDI : access MDI_Window_Record'Class; Node : Node_Ptr; Focus_Child : in out MDI_Child; Parent_Width, Parent_Height : Gint; Parent_Orientation : Gtk_Orientation; User : User_Data; Initial_Ref_Child : Gtk_Notebook := null; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Empty_Notebook_Filler : in out MDI_Child) is Orientation : constant Gtk_Orientation := Gtk_Orientation'Value (Get_Attribute (Node, "Orientation")); N : Node_Ptr; Ref_Item : Gtk_Widget := Gtk_Widget (Initial_Ref_Child); Count : constant Natural := Children_Count (Node); Notebook_Node : Node_Ptr; Width, Height : Gint; Width_For_Children : Gint := Parent_Width; Height_For_Children : Gint := Parent_Height; begin Compute_Size_From_Attributes (Node, Parent_Width, Parent_Height, Parent_Orientation, Width_For_Children, Height_For_Children, Count); if Traces then New_Line; Print_Debug ("Parse_Pane_Node " & Gtk_Orientation'Image (Orientation) & " children=" & Integer'Image (Count) & " child_size=" & Gint'Image (Width_For_Children) & "x" & Gint'Image (Height_For_Children)); end if; Indent_Debug (1); declare Notebooks : array (1 .. Count) of Gtk_Notebook; W : Gtk_Widget; Tmp_Width, Tmp_Height : Gint; Tmp_Orientation : Gtk_Orientation; Index : Natural := Notebooks'First; begin -- First insert all direct children of the pane, splitting as -- needed. Only then process the Pane children. Otherwise, the -- children of Pane will have been split and reorganized so that -- we won't be able to get a reference item for further splitting. N := Node.Child; while N /= null loop Tmp_Width := Width_For_Children; Tmp_Height := Height_For_Children; Tmp_Orientation := Orientation; -- Find the first notebook node of N Notebook_Node := N; while Notebook_Node.Tag /= null and then Notebook_Node.Tag.all = "Pane" loop Compute_Size_From_Attributes (Notebook_Node, Parent_Width => Tmp_Width, Parent_Height => Tmp_Height, Parent_Orientation => Tmp_Orientation, Width => Tmp_Width, Height => Tmp_Height, Children_Count => Children_Count (Notebook_Node)); Tmp_Orientation := Gtk_Orientation'Value (Get_Attribute (Notebook_Node, "Orientation")); Print_Debug ("Descending into pane while looking for first notebook w=" & Gint'Image (Tmp_Width) & "x" & Gint'Image (Tmp_Height)); Notebook_Node := Notebook_Node.Child; end loop; if Index = Notebooks'First and then Initial_Ref_Child /= null then Notebooks (Index) := Initial_Ref_Child; W := Gtk_Widget (Initial_Ref_Child); else if Notebook_Node.Tag.all = "Notebook" then Parse_Notebook_Node (MDI => MDI, Child_Node => Notebook_Node, Parent_Width => Tmp_Width, Parent_Height => Tmp_Height, Parent_Orientation => Tmp_Orientation, User => User, Focus_Child => Focus_Child, Width => Width, Height => Height, Notebook => Notebooks (Index), To_Raise => To_Raise, To_Hide => To_Hide, Empty_Notebook_Filler => Empty_Notebook_Filler); W := Gtk_Widget (Notebooks (Index)); else W := Gtk_Widget (MDI.Central); Compute_Size_From_Attributes (Notebook_Node, Parent_Width => Tmp_Width, Parent_Height => Tmp_Height, Parent_Orientation => Tmp_Orientation, Width => Width, Height => Height, Children_Count => 1); Print_Debug ("Parse_Pane_Node: seen <central> size=" & Gint'Image (Width) & Gint'Image (Height)); end if; if Get_Parent (W) = null then if Ref_Item = null then Print_Debug ("Parse_Pane_Node, add notebook in MDI " & System.Address_Image (W.all'Address)); Add_Child (Win => Paned, New_Child => W, Orientation => Orientation, Width => Width, Height => Height); else Print_Debug ("Parse_Pane_Node Split notebook into MDI " & System.Address_Image (W.all'Address) & " ref=" & System.Address_Image (Ref_Item.all'Address) & " Orient=" & Gtk_Orientation'Image (Orientation)); Split (Paned, Ref_Widget => Ref_Item, New_Child => W, Width => Width, Height => Height, Orientation => Orientation); end if; else Print_Debug ("Parse_Pane_Node: notebook already in MDI"); Set_Size (Paned, W, Width => Width, Height => Height); end if; end if; Ref_Item := W; Index := Index + 1; N := N.Next; end loop; -- Now process the Pane children recursively, splitting as needed Print_Debug ("Parse_Pane_Node: now process pane children"); N := Node.Child; Index := Notebooks'First; while N /= null loop if N.Tag.all = "Pane" then Parse_Pane_Node (Paned => Paned, MDI => MDI, Node => N, Focus_Child => Focus_Child, User => User, Parent_Width => Width_For_Children, Parent_Height => Height_For_Children, Parent_Orientation => Orientation, Initial_Ref_Child => Notebooks (Index), To_Raise => To_Raise, To_Hide => To_Hide, Empty_Notebook_Filler => Empty_Notebook_Filler); end if; Index := Index + 1; N := N.Next; end loop; end; Indent_Debug (-1); end Parse_Pane_Node; ------------------------ -- Restore_Multi_Pane -- ------------------------ procedure Restore_Multi_Pane (Pane : access Gtkada_Multi_Paned_Record'Class; MDI : access MDI_Window_Record'Class; Focus_Child : in out MDI_Child; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Node : Node_Ptr; User : User_Data; Full_Width, Full_Height : Gint) is Child_Node : Node_Ptr := Node.Child; Raised : Boolean; X, Y : Gint; Child : MDI_Child; State : State_Type; Empty_Notebook_Filler : MDI_Child; -- Used to fill the empty notebook, and prevent it from being -- destroyed during a desktop load. begin Print_Debug ("Restore_Multi_Pane Full size=" & Gint'Image (Full_Width) & "x" & Gint'Image (Full_Height)); Indent_Debug (1); while Child_Node /= null loop if Traces then Print_Debug ("Restore_Multi_Pane, got child """ & Child_Node.Tag.all & """"); end if; if Child_Node.Tag.all = "Pane" then Parse_Pane_Node (Pane, MDI => MDI, Node => Child_Node, Focus_Child => Focus_Child, Parent_Width => Full_Width, Parent_Height => Full_Height, Parent_Orientation => Orientation_Horizontal, User => User, Initial_Ref_Child => null, To_Raise => To_Raise, To_Hide => To_Hide, Empty_Notebook_Filler => Empty_Notebook_Filler); elsif Child_Node.Tag.all = "Child" then -- Used for floating children, and children in the default -- desktop (see Add_To_Tree) Parse_Child_Node (MDI, Child_Node, User, Focus_Child, X, Y, Raised, State, Child, To_Hide => To_Hide); if Child /= null then case State is when Floating => Internal_Float_Child (Child, True, Position_At_Mouse => False, X => X, Y => Y); when Invisible => null; when Normal => Float_Child (Child, False); end case; end if; end if; Child_Node := Child_Node.Next; end loop; if Empty_Notebook_Filler /= null then -- The empty notebook has been created during the desktop load declare Note : constant Gtk_Notebook := Gtk_Notebook (Get_Parent (Empty_Notebook_Filler)); begin Print_Debug ("Restore desktop, removing empty_notebook_filler"); Remove_Page (Note, 0); end; end if; Indent_Debug (-1); end Restore_Multi_Pane; --------------------------------- -- Recompute_Perspective_Names -- --------------------------------- procedure Recompute_Perspective_Names (MDI : access MDI_Window_Record'Class) is N : Node_Ptr; Count : Natural := 0; begin Free (MDI.Perspective_Names); if MDI.Perspectives /= null then N := MDI.Perspectives.Child; while N /= null loop Count := Count + 1; N := N.Next; end loop; MDI.Perspective_Names := new GNAT.Strings.String_List (1 .. Count); Count := MDI.Perspective_Names'First; N := MDI.Perspectives.Child; while N /= null loop MDI.Perspective_Names (Count) := new String'(Get_Attribute (N, "name")); Count := Count + 1; N := N.Next; end loop; end if; end Recompute_Perspective_Names; --------------------- -- Restore_Desktop -- --------------------- 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 is To_Raise : Gtk.Widget.Widget_List.Glist; To_Hide : Gtk.Widget.Widget_List.Glist; Focus_Child : MDI_Child; Initial_All_Floating_Mode : constant Boolean := MDI.All_Floating_Mode; Do_Size_Allocate : Boolean := True; MDI_Width, MDI_Height : Gint; begin if Perspectives = null or else Perspectives.Child = null -- <perspective> node then -- No desktop to load, but we still have to setup a minimal -- environment to avoid critical errors later on. if MDI.Central /= null then Destroy (MDI.Central); end if; Gtk_New (MDI.Central); Add_Child (MDI, MDI.Central); Print_Debug ("No perspective to restore"); return False; end if; Free (MDI.Perspectives); MDI.Perspectives := Deep_Copy (Perspectives); MDI.Current_Perspective := null; Recompute_Perspective_Names (MDI); Free (MDI.View_Contents); MDI.View_Contents := Deep_Copy (From_Tree); -- ??? We could save some memory by freeing the <pane> nodes, but is -- there any point ? -- Temporarily disable the user of all floating mode, so that we can -- properly restore the desktop even if notebooks are referenced. MDI.All_Floating_Mode := False; if From_Tree /= null and then From_Tree.Tag.all /= "desktop" then return False; end if; Print_Debug ("Restore_Desktop"); Print_Debug ("Current MDI size is" & Gint'Image (Get_Allocation_Width (MDI)) & "x" & Gint'Image (Get_Allocation_Height (MDI))); Print_Debug ("Current window size is" & Gint'Image (Get_Allocation_Width (Get_Toplevel (MDI))) & "x" & Gint'Image (Get_Allocation_Height (Get_Toplevel (MDI)))); -- We must restore the size of the main window first, so that the -- rest of the desktop makes sense. declare State : Gdk_Window_State; begin State := Gdk_Window_State'Value (Get_Attribute (Perspectives, "state", "0")); if (State and Window_State_Maximized) /= 0 then -- Compute the width the window will have when maximized. -- We cannot simply do a Maximize and then read the allocation -- size, since that is asynchronous. -- On many systems, the following calls seem to fail, so we -- just simulate a size (this is irrelevant anyway, since the -- call to Maximize will find the correct size, but it helps -- debugging when we use the real sizes). if Traces then declare Rect : Gdk_Rectangle; begin Get_Monitor_Geometry (Screen => Gdk.Screen.Get_Default, Monitor_Num => Get_Monitor_At_Window (Gdk.Screen.Get_Default, Get_Window (MDI)), Dest => Rect); MDI_Width := Rect.Width; MDI_Height := Rect.Height; end; else MDI_Width := 1000; MDI_Height := 1000; end if; Maximize (Gtk_Window (Get_Toplevel (MDI))); Do_Size_Allocate := False; Print_Debug ("MDI must be maximized, to size " & Gint'Image (MDI_Width) & "x" & Gint'Image (MDI_Height)); else MDI_Width := Gint'Value (Get_Attribute (Perspectives, "width", "640")); MDI_Height := Gint'Value (Get_Attribute (Perspectives, "height", "480")); Print_Debug ("MDI size computed read from desktop " & Gint'Image (MDI_Width) & "x" & Gint'Image (MDI_Height)); Set_Default_Size (Gtk_Window (Get_Toplevel (MDI)), MDI_Width, MDI_Height); end if; exception when others => -- An invalid attribute in XML ? null; end; -- Close all existing windows (internal_load_perspective would try to -- preserve them, but they do not apply to the current desktop) declare Tmp : Widget_List.Glist := MDI.Items; Tmp2 : Widget_List.Glist; C : MDI_Child; Widget_Node : Node_Ptr; Widget_Is_Unique : Boolean; begin while Tmp /= Null_List loop Tmp2 := Next (Tmp); -- Do not force closure, we want to keep desktop-independent -- views Close (MDI, MDI_Child (Get_Data (Tmp))); Tmp := Tmp2; end loop; Tmp := MDI.Items; while Tmp /= Null_List loop C := MDI_Child (Get_Data (Tmp)); -- For those items still in the list, we must ensure we know -- their XML node name, otherwise they will never be reused and -- just waste memory (and result in memory leaks) if C.XML_Node_Name = null then Get_XML_For_Widget (Child => C, User => User, Data => Widget_Node, Widget_Is_Unique => Widget_Is_Unique); Free (Widget_Node); end if; Tmp := Next (Tmp); end loop; end; -- Prepare the contents of the central area. This will automatically -- replace the central area's contents in the perspective Print_Debug ("+++++++ Destroying central area ++++++"); if MDI.Central /= null then -- It could come from a previous desktop Destroy (MDI.Central); end if; Gtk_New (MDI.Central); -- The central area describes the floating children, so they are not -- part of MDI.Central. Print_Debug ("+++++++ Loading central area ++++++"); To_Raise := Widget_List.Null_List; To_Hide := Widget_List.Null_List; if not MDI.Independent_Perspectives and then From_Tree /= null then Restore_Multi_Pane (Pane => MDI.Central, MDI => MDI, Focus_Child => Focus_Child, To_Raise => To_Raise, To_Hide => To_Hide, User => User, Node => From_Tree, Full_Width => MDI_Width, Full_Height => MDI_Height); end if; Set_Child_Visible (MDI.Central, True); -- Now restore the appropriate perspective, which gives the global -- organization of the desktop apart from the default area (which is -- restored later on). Internal_Load_Perspective (MDI, Get_Attribute (From_Tree, "perspective", ""), User, Focus_Child => Focus_Child, To_Raise => To_Raise, To_Hide => To_Hide, Width => MDI_Width, Height => MDI_Height, Do_Size_Allocate => Do_Size_Allocate); Set_All_Floating_Mode (MDI, Initial_All_Floating_Mode); if Focus_Child /= null then Print_Debug ("Desktop set focus on " & Get_Title (Focus_Child)); Set_Focus_Child (Focus_Child); end if; Emit_By_Name (Get_Object (MDI), String (Signal_Perspective_Changed) & ASCII.NUL); Emit_By_Name (Get_Object (MDI), String (Signal_Children_Reorganized) & ASCII.NUL); return True; end Restore_Desktop; ------------------------ -- Get_XML_For_Widget -- ------------------------ procedure Get_XML_For_Widget (Child : MDI_Child; User : User_Data; Widget_Is_Unique : out Boolean; Data : out Node_Ptr) is Register : Register_Node := Registers; Widget_Node : Node_Ptr; begin while Widget_Node = null and then Register /= null loop Widget_Node := Register.Save (Child.Initial, User); Register := Register.Next; end loop; if Widget_Node /= null then -- Save the XML node name, which might be useful when switching -- perspectives Free (Child.XML_Node_Name); Child.XML_Node_Name := new String'(Widget_Node.Tag.all); end if; Data := Widget_Node; -- ??? Hard-coded for now. See comments in Save_Widget Widget_Is_Unique := True; end Get_XML_For_Widget; ------------------ -- Save_Desktop -- ------------------ 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) is MDI_Width : constant Gint := Gint (Get_Allocation_Width (Get_Toplevel (MDI))); MDI_Height : constant Gint := Gint (Get_Allocation_Height (Get_Toplevel (MDI))); Item : Widget_List.Glist; Child_Node : Node_Ptr; Child : MDI_Child; procedure Add (Parent : Node_Ptr; Name, Value : String); -- Add a new child to Child_Node procedure Save_Widget (Parent : Node_Ptr; Child : MDI_Child; Raised : Boolean; In_Central : Boolean); -- Save the Child. Raised is True if Child is the current page -- in a notebook. In_Central is True if we are saving a child part of -- the central area of the desktop procedure Save_Size (Iter : Gtkada.Multi_Paned.Child_Iterator; Node : Node_Ptr); -- Set the size of Widget (relative to the total size of the window) -- as attributes of Node procedure Save_Paned (Paned : access Gtkada_Multi_Paned_Record'Class; Parent : Node_Ptr; In_Central : Boolean); -- Look through all the notebooks, and save the widgets in the -- notebook order. function Save_Notebook (Current : Node_Ptr; Note : Gtk_Notebook; In_Central : Boolean) return Node_Ptr; -- save all pages of the notebook procedure Prune_Empty (N : in out Node_Ptr); -- Prunes empty panes below N --------- -- Add -- --------- procedure Add (Parent : Node_Ptr; Name, Value : String) is N : Node_Ptr; begin N := new Node; N.Tag := new String'(Name); N.Value := new String'(Value); Add_Child (Parent, N, Append => True); end Add; ----------------- -- Save_Widget -- ----------------- procedure Save_Widget (Parent : Node_Ptr; Child : MDI_Child; Raised : Boolean; In_Central : Boolean) is Widget_Node : Node_Ptr; Tmp_Node : Node_Ptr; Widget_Is_Unique : Boolean; begin if Child.State = Invisible then return; end if; Get_XML_For_Widget (Child, User, Widget_Is_Unique, Widget_Node); if Widget_Node /= null then -- When a window is in the perspective (and not in the central -- area), we used to save its location in the <perspective> -- node, and its contents in <central>, since it is project -- specific.. -- This works fine for the location window, for instance. -- But this doesn't work well for editors (for which there -- appears an empty area in the newly loaded desktop). Getting -- rid of the emplty areas requires restarting the MDI. -- Since the latter is blocking, the safest route for now is to -- always save the contents in the perspective, independently -- of the project. Most users always load the same project -- anyway. -- For windows that are in multiple perspectives, their -- contents is only saved in the current perspective. That -- works as expected because this is also the perspective that -- is reloaded. -- ??? One solution might be to let the widget itself decide -- where to save the contents when the window is not in the -- central area. Note that saving in <central> works best if -- there is a single window of a kind (so not for editors). Tmp_Node := Widget_Node; if Widget_Is_Unique and then not MDI.Independent_Perspectives and then not In_Central and then (Widget_Node.Child /= null or else Widget_Node.Attributes /= null) then Tmp_Node := new Node; Tmp_Node.Tag := new String'(Widget_Node.Tag.all); Add_Child (Central, Widget_Node, Append => True); end if; -- Note: We need to insert the children in the opposite order -- from Restore_Desktop, since the children are added at the -- beginning of the list. Child_Node := new Node; Child_Node.Tag := new String'("Child"); Add_Child (Child_Node, Tmp_Node, Append => True); Set_Attribute (Child_Node, "State", State_Type'Image (Child.State)); Set_Attribute (Child_Node, "Group", Child_Group'Image (Child.Group)); if Child.State = Floating then declare Win : constant Gtk_Widget := Get_Toplevel (Child.Initial); W, H : Gint; begin -- Note: This size doesn't include the size of the window -- decorations, doesn't seem to be a way to do this. W := Get_Allocation_Width (Win); H := Get_Allocation_Height (Win); Add (Child_Node, "height", Gint'Image (H)); Add (Child_Node, "width", Gint'Image (W)); end; end if; if Child = MDI.Focus_Child then Set_Attribute (Child_Node, "Focus", "True"); end if; if Raised then Set_Attribute (Child_Node, "Raised", "True"); end if; if not Visible_Is_Set (Child) then Set_Attribute (Child_Node, "visible", "False"); end if; Add_Child (Parent, Child_Node, Append => True); end if; end Save_Widget; --------------- -- Save_Size -- --------------- procedure Save_Size (Iter : Gtkada.Multi_Paned.Child_Iterator; Node : Node_Ptr) is Parent_Width, Parent_Height, Width, Height : Gint; Orientation : Gtk_Orientation; begin Get_Size (Iter, Width, Height, Parent_Width, Parent_Height, Orientation); case Orientation is when Orientation_Horizontal => Set_Attribute (Node, "width", Float'Image (Float (Width) * 100.0 / Float (Parent_Width)) & "%"); when Orientation_Vertical => Set_Attribute (Node, "height", Float'Image (Float (Height) * 100.0 / Float (Parent_Height)) & "%"); end case; end Save_Size; ------------------- -- Save_Notebook -- ------------------- function Save_Notebook (Current : Node_Ptr; Note : Gtk_Notebook; In_Central : Boolean) return Node_Ptr is Length : constant Gint := Get_N_Pages (Note); Current_Page : constant Gint := Get_Current_Page (Note); Parent : Node_Ptr; Has_Default_Group_Child : Boolean := False; Child : MDI_Child; begin Parent := new Node; Parent.Tag := new String'("Notebook"); Set_Attribute (Parent, "Tabs", Gtk_Position_Type'Image (Get_Tab_Pos (Note))); if Length > 0 then for Page_Index in 0 .. Length - 1 loop Child := MDI_Child (Get_Nth_Page (Note, Page_Index)); Has_Default_Group_Child := Has_Default_Group_Child or else Child.Group = Group_Default; Save_Widget (Parent, Child, Raised => Current_Page = Page_Index, In_Central => In_Central); end loop; end if; -- Do not append the Notebook node to the parent if no child in -- the notebook was found, unless the number of pages is 0, in -- which case this is a real empty space which should be saved -- in the desktop. Also add the default notebook always, since -- it plays a special role Print_Debug ("Saving notebook, Length=" & Gint'Image (Length) & " parent.child is null=" & Boolean'Image (Parent.Child = null)); if Length = 0 or else Parent.Child /= null then Add_Child (Current, Parent, Append => True); return Parent; else Free (Parent); return null; end if; end Save_Notebook; ----------------- -- Prune_Empty -- ----------------- procedure Prune_Empty (N : in out Node_Ptr) is C, Tmp : Node_Ptr; begin if N.Tag.all = "Pane" then C := N.Child; while C /= null loop Tmp := C.Next; Prune_Empty (C); C := Tmp; end loop; if N.Child = null then Free (N); end if; end if; end Prune_Empty; ---------------- -- Save_Paned -- ---------------- procedure Save_Paned (Paned : access Gtkada_Multi_Paned_Record'Class; Parent : Node_Ptr; In_Central : Boolean) is Current : Node_Ptr := Parent; N : Node_Ptr; Depth : Natural := 0; Iter : Gtkada.Multi_Paned.Child_Iterator := Start (Paned); Orientation : Gtk_Orientation; begin while not At_End (Iter) loop for N in Get_Depth (Iter) + 1 .. Depth loop Current := Current.Parent; end loop; Orientation := Get_Orientation (Iter); if Get_Widget (Iter) = Gtk_Widget (MDI.Central) then if MDI.Independent_Perspectives then Save_Paned (MDI.Central, Current, In_Central => True); else N := new Node; N.Tag := new String'("central"); Save_Size (Iter, N); Add_Child (Current, N, Append => True); end if; elsif Get_Widget (Iter) /= null then N := Save_Notebook (Current, Gtk_Notebook (Get_Widget (Iter)), In_Central => In_Central); if N /= null then Save_Size (Iter, N); end if; else N := new Node; N.Tag := new String'("Pane"); Set_Attribute (N, "Orientation", Gtk_Orientation'Image (Orientation)); Save_Size (Iter, N); Add_Child (Current, N, Append => True); Current := N; end if; Depth := Get_Depth (Iter); Next (Iter); end loop; if Parent.Child /= null then Prune_Empty (Parent.Child); end if; end Save_Paned; begin if MDI.Perspectives = null then MDI.Perspectives := new Node; MDI.Perspectives.Tag := new String'("perspectives"); end if; if MDI.Current_Perspective /= null then -- Replace (in place) the perspective. This is so that the -- order in the /Window/Perspectives menu is preserved as much -- as possible declare N : Node_Ptr := MDI.Current_Perspective.Child; N2 : Node_Ptr; begin while N /= null loop N2 := N.Next; Free (N); N := N2; end loop; end; else MDI.Current_Perspective := new Node; MDI.Current_Perspective.Tag := new String'("perspective"); Set_Attribute (MDI.Current_Perspective, "name", "default"); Add_Child (MDI.Perspectives, MDI.Current_Perspective, Append => False); end if; Central := new Node; Central.Tag := new String'("desktop"); -- Save the general configuration of the MDI declare Win : constant Gtk_Window := Gtk_Window (Get_Toplevel (MDI)); State : Gdk_Window_State; begin if Win /= null then State := Get_State (Get_Window (Win)); if (State and Window_State_Maximized) = 0 then Set_Attribute (MDI.Perspectives, "width", Gint'Image (MDI_Width)); Set_Attribute (MDI.Perspectives, "height", Gint'Image (MDI_Height)); end if; Set_Attribute (MDI.Perspectives, "state", Gdk_Window_State'Image (State)); Set_Attribute (Central, "perspective", Current_Perspective (MDI)); end if; end; Print_Debug ("Save_Desktop: window size reported as" & Gint'Image (MDI_Width) & "x" & Gint'Image (MDI_Height)); Print_Debug ("Save_Desktop: saving the perspective"); Save_Paned (MDI, MDI.Current_Perspective, In_Central => False); if not MDI.Independent_Perspectives then Print_Debug ("Save_Desktop: saving central area"); Save_Paned (MDI.Central, Central, In_Central => True); end if; -- Save the floating widgets (these are part of the perspective) Print_Debug ("Save_Desktop: saving floating widgets"); Item := MDI.Items; while Item /= Widget_List.Null_List loop Child := MDI_Child (Widget_List.Get_Data (Item)); case Child.State is when Normal | Invisible => null; when Floating => Save_Widget (Central, Child, False, In_Central => True); end case; Item := Widget_List.Next (Item); end loop; Perspectives := Deep_Copy (MDI.Perspectives); if Traces then Print_Debug ("After saving the desktop (current perspective is " & Current_Perspective (MDI) & "), desktop is"); Print (MDI.Perspectives); if not MDI.Independent_Perspectives then Print_Debug ("And the central area is"); Print (Central); end if; end if; end Save_Desktop; --------------------------------------- -- Free_Registered_Desktop_Functions -- --------------------------------------- procedure Free_Registered_Desktop_Functions is procedure Unchecked_Free is new Ada.Unchecked_Deallocation (Register_Node_Record, Register_Node); Next : Register_Node; begin while Registers /= null loop Next := Registers.Next; Unchecked_Free (Registers); Registers := Next; end loop; end Free_Registered_Desktop_Functions; ------------------------------- -- Internal_Load_Perspective -- ------------------------------- procedure Internal_Load_Perspective (MDI : access MDI_Window_Record'Class; Name : String; User : User_Data; Focus_Child : in out MDI_Child; To_Raise : in out Gtk.Widget.Widget_List.Glist; To_Hide : in out Gtk.Widget.Widget_List.Glist; Width, Height : Gint := 0; Do_Size_Allocate : Boolean) is Child : MDI_Child; procedure Remove_All_Items (Remove_All_Empty : Boolean); -- Remove all items from the MDI ---------------------- -- Remove_All_Items -- ---------------------- procedure Remove_All_Items (Remove_All_Empty : Boolean) is Children : Widget_List.Glist; L : Widget_List.Glist; Note : Gtk_Notebook; C : MDI_Child; Parent : Gtk_Widget; begin Print_Debug ("Remove_All_Items: remove_empty=" & Boolean'Image (Remove_All_Empty)); Indent_Debug (1); -- Remove all children from the MDI. However, we do not close them -- in case we switch back to the perspective (or the user opens -- them while in the perspective). They will just be marked as -- Invisible for now. L := MDI.Items; while L /= Null_List loop C := MDI_Child (Get_Data (L)); if C.State = Normal and then (MDI.Independent_Perspectives or else not In_Central_Area (MDI, C)) then Print_Debug ("Remove_All_Items, marking " & Get_Title (C) & " as invisible"); Ref (C); -- Unref called in Destroy_Child and Put Parent := Get_Parent (C); if Parent /= null then Remove (Gtk_Container (Get_Parent (C)), C); end if; Set_State (C, Invisible); if C.Menu_Item /= null then Destroy (C.Menu_Item); end if; end if; L := Next (L); end loop; -- We now force the closing of all empty notebooks -- The central area should no longer be part of the MDI at this -- stage, so all we get are notebooks Children := Get_Children (MDI); L := Children; while L /= Null_List loop Note := Gtk_Notebook (Get_Data (L)); if Get_Nth_Page (Note, 0) = null then Remove (MDI, Note); end if; L := Next (L); end loop; Free (Children); -- We used to close empty notebooks, but in fact such notebooks -- can now only be in the central area, whose contents has not -- changed anyway. Indent_Debug (-1); Print_Debug ("Remove_All_Items: done"); end Remove_All_Items; MDI_Width, MDI_Height : Gint; Tmp_Persp : Node_Ptr; begin -- Find the right perspective node Tmp_Persp := MDI.Perspectives.Child; while Tmp_Persp /= null and then Get_Attribute (Tmp_Persp, "name") /= Name loop Tmp_Persp := Tmp_Persp.Next; end loop; if Tmp_Persp = null then -- If not found, and we already have a perspective => do nothing if MDI.Current_Perspective /= null then return; end if; -- Else load the first one Print_Debug ("Perspective not found, loading default one"); Tmp_Persp := MDI.Perspectives.Child; end if; MDI.Current_Perspective := Tmp_Persp; Print_Debug ("+++++++ Loading perspective " & Name & " ++++++"); -- Get the size of the MDI if Width = 0 then MDI_Width := Gint (Get_Allocation_Width (MDI)); else MDI_Width := Width; end if; if Height = 0 then MDI_Height := Gint (Get_Allocation_Height (MDI)); else MDI_Height := Height; end if; Print_Debug ("MDI size reported as " & Gint'Image (MDI_Width) & Gint'Image (MDI_Height)); -- Remove central from the MDI, and it will be put in the new -- perspective Ref (MDI.Central); if Get_Parent (MDI.Central) /= null then Remove (Gtk_Container (Get_Parent (MDI.Central)), MDI.Central); end if; MDI.Loading_Desktop := True; Freeze (MDI); -- Clean up MDI if necessary Remove_All_Items (Remove_All_Empty => True); if Traces then Print_Debug ("Done removing all children, desktop is now:"); Dump (MDI); end if; Restore_Multi_Pane (Pane => MDI, MDI => MDI, Focus_Child => Focus_Child, To_Raise => To_Raise, To_Hide => To_Hide, User => User, Node => MDI.Current_Perspective, Full_Width => MDI_Width, Full_Height => MDI_Height); -- If the central area was not in the desktop, that's an error and -- the application will not be usable anyway, so better break the -- desktop but show the central area if not MDI.Independent_Perspectives and then Get_Parent (MDI.Central) = null then Add_Child (MDI, MDI.Central); end if; MDI.Desktop_Was_Loaded := True; Queue_Resize (MDI); -- Raise all appropriate items at the end, so that even if some items -- are added temporarily to notebooks, they have no long-lasting -- impact on the notebook itself. declare Item : Widget_List.Glist := To_Raise; begin while Item /= Widget_List.Null_List loop Child := MDI_Child (Widget_List.Get_Data (Item)); if Focus_Child = null then Focus_Child := Child; end if; Print_Debug ("Restore_Desktop, raising child with no focus " & Get_Title (Child)); Raise_Child (Child, Give_Focus => False); Item := Widget_List.Next (Item); end loop; Free (To_Raise); Print_Debug ("Restore_Desktop, hidding children"); Item := To_Hide; while Item /= Widget_List.Null_List loop Child := MDI_Child (Widget_List.Get_Data (Item)); Print_Debug ("Restore_Desktop, hidding " & Get_Title (Child)); Hide (Child); Item := Widget_List.Next (Item); end loop; Free (To_Hide); if Traces then Dump (MDI); end if; end; Reset_Title_Bars_And_Colors (MDI); Show_All (MDI); -- If Central was not part of the perspective (an error...), we let -- gtk+ display an error message. We cannot simply Add_Child the -- central area to the MDI, since that doesn't seem to work correctly if not MDI.Independent_Perspectives then Realize (MDI.Central); Show_All (MDI.Central); Unref (MDI.Central); end if; MDI.Loading_Desktop := False; Thaw (MDI); -- Update to show which menu is active Create_Perspective_Menu (MDI, User); -- Realize the window while frozen, so that windows that insist on -- setting their own size when realized (eg. the search window in -- GPS) will not break the desktop. -- However, don't do this when attempting to maximize the desktop, -- since otherwise we get a first Size_Allocate for whatever current -- size we have, and then a second one for the maximized size. The -- first one breaks the desktop partially. if Do_Size_Allocate then Print_Debug ("Internal_Load_Perspective, forcing a Size_Allocate"); Realize (MDI); Size_Allocate (MDI, Allocation => (X => Get_Allocation_X (MDI), Y => Get_Allocation_Y (MDI), Width => Get_Allocation_Width (MDI), Height => Get_Allocation_Height (MDI))); end if; end Internal_Load_Perspective; ---------------------- -- Load_Perspective -- ---------------------- procedure Load_Perspective (MDI : access MDI_Window_Record'Class; Name : String; User : User_Data) is To_Raise : Gtk.Widget.Widget_List.Glist; To_Hide : Gtk.Widget.Widget_List.Glist; Focus_Child : MDI_Child; Perspectives : Node_Ptr; begin -- Save modifications to current perspective, so that we can restore -- them when the user switches back to that perspective. A signal -- "perspective_changed" will be sent to the user, so that he has a -- chance to save the changes in an external file for persistency Print_Debug ("++++++ Load_Perspective " & Name); if MDI.Current_Perspective /= null then if Get_Attribute (MDI.Current_Perspective, "name") = Name then return; end if; -- We only rely on the side effect of changing MDI.Perspectives, -- since the central area does not change in any case Free (MDI.View_Contents); Save_Desktop (MDI, User, Perspectives, MDI.View_Contents); Free (Perspectives); end if; Print_Debug ("++++ Load_Perspective, desktop was saved, now loading"); Internal_Load_Perspective (MDI, Name, User, Focus_Child => Focus_Child, To_Raise => To_Raise, To_Hide => To_Hide, Do_Size_Allocate => True); end Load_Perspective; end Desktop; ----------------- -- First_Child -- ----------------- function First_Child (MDI : access MDI_Window_Record; Group_By_Notebook : Boolean := False; Visible_Only : Boolean := True) return Child_Iterator is Children : Widget_List.Glist; C : MDI_Child; begin if Group_By_Notebook then declare Iter : Child_Iterator := (Group_By_Notebook => True, Visible_Only => Visible_Only, Paned_Iter => Start (MDI), In_Central => False, Notebook => null, Notebook_Page => 0, Floating_Iter => MDI.Items, MDI => MDI_Window (MDI)); begin if MDI.Central /= null then Iter.Paned_Iter := Start (MDI.Central); Iter.In_Central := True; end if; Move_To_Next_Notebook (Iter); while Iter.Floating_Iter /= Null_List and then MDI_Child (Widget_List.Get_Data (Iter.Floating_Iter)).State /= Floating loop Iter.Floating_Iter := Widget_List.Next (Iter.Floating_Iter); end loop; return Iter; end; else Children := MDI.Items; if Children /= Widget_List.Null_List and then Visible_Only then C := MDI_Child (Widget_List.Get_Data (MDI.Items)); if C.State = Invisible then -- There are no visible child, since the first one should be -- the one with the focus Children := Widget_List.Null_List; end if; end if; return (Group_By_Notebook => False, Visible_Only => Visible_Only, Iter => Children); end if; end First_Child; ------------------ -- Get_Notebook -- ------------------ function Get_Notebook (Iterator : Child_Iterator) return Gtk.Notebook.Gtk_Notebook is C : constant MDI_Child := Get (Iterator); begin if C = null then return null; else return Get_Notebook (C); end if; end Get_Notebook; --------------------------- -- Move_To_Next_Notebook -- --------------------------- procedure Move_To_Next_Notebook (Iterator : in out Child_Iterator) is begin Iterator.Notebook := null; Iterator.Notebook_Page := 0; loop if At_End (Iterator.Paned_Iter) then if Iterator.In_Central then Iterator.In_Central := False; Iterator.Paned_Iter := Start (Iterator.MDI); exit when At_End (Iterator.Paned_Iter); else exit; end if; end if; -- Assert (not At_End (Iterator.Paned_Iter)) if Get_Widget (Iterator.Paned_Iter) /= null and then (not Iterator.Visible_Only or else Visible_Is_Set (Get_Widget (Iterator.Paned_Iter))) and then Get_Widget (Iterator.Paned_Iter).all in Gtk_Notebook_Record'Class then Iterator.Notebook := Gtk_Notebook (Get_Widget (Iterator.Paned_Iter)); exit; end if; Next (Iterator.Paned_Iter); end loop; end Move_To_Next_Notebook; ---------- -- Next -- ---------- procedure Next (Iterator : in out Child_Iterator) is C : MDI_Child; begin if Iterator.Group_By_Notebook then if Iterator.Notebook = null then -- Find the next floating child loop Iterator.Floating_Iter := Widget_List.Next (Iterator.Floating_Iter); exit when Iterator.Floating_Iter = Null_List or else MDI_Child (Widget_List.Get_Data (Iterator.Floating_Iter)).State = Floating; end loop; else Iterator.Notebook_Page := Iterator.Notebook_Page + 1; if Get_Nth_Page (Iterator.Notebook, Iterator.Notebook_Page) = null then Next (Iterator.Paned_Iter); Move_To_Next_Notebook (Iterator); end if; end if; else loop Iterator.Iter := Widget_List.Next (Iterator.Iter); if Iterator.Visible_Only then C := Get (Iterator); exit when C = null or else C.State /= Invisible; else exit; end if; end loop; end if; end Next; --------- -- Get -- --------- function Get (Iterator : Child_Iterator) return MDI_Child is begin if Iterator.Group_By_Notebook then if Iterator.Notebook = null then if Iterator.Floating_Iter = Widget_List.Null_List then return null; else return MDI_Child (Widget_List.Get_Data (Iterator.Floating_Iter)); end if; else return MDI_Child (Get_Nth_Page (Iterator.Notebook, Iterator.Notebook_Page)); end if; elsif Iterator.Iter /= Widget_List.Null_List then return MDI_Child (Widget_List.Get_Data (Iterator.Iter)); else return null; end if; end Get; --------------------- -- Highlight_Child -- --------------------- procedure Highlight_Child (Child : access MDI_Child_Record; Highlight : Boolean := True) is Note : constant Gtk_Notebook := Get_Notebook (Child); Style : Gtk_Style; begin if Highlight then Show (Child); -- Make sure the child is visible if Note /= null and then Get_Current_Page (Note) = Page_Num (Note, Child) then return; end if; Style := Child.MDI.Highlight_Style; else Style := null; end if; -- Might be null if we haven't created the MDI menu yet if Child.Menu_Item /= null then declare Children : Widget_List.Glist := Get_Children (Gtk_Box (Get_Child (Child.Menu_Item))); Tmp : Widget_List.Glist := Children; begin while Tmp /= Null_List loop if Get_Data (Tmp).all'Tag = Gtk_Accel_Label_Record'Tag then Set_Style (Get_Data (Tmp), Style); end if; Tmp := Next (Tmp); end loop; Free (Children); end; end if; if Child.Tab_Label /= null then Set_Style (Child.Tab_Label, Style); end if; end Highlight_Child; ------------------------ -- Desktop_Was_Loaded -- ------------------------ function Desktop_Was_Loaded (MDI : access MDI_Window_Record) return Boolean is begin return MDI.Desktop_Was_Loaded; end Desktop_Was_Loaded; --------------- -- Get_State -- --------------- function Get_State (Child : access MDI_Child_Record) return State_Type is begin return Child.State; end Get_State; -------------------- -- Set_Dnd_Source -- -------------------- procedure Set_Dnd_Source (Widget : access Gtk_Widget_Record'Class; Child : access Gtk_Widget_Record'Class) is begin Add_Events (Widget, Button_Press_Mask); Return_Callback.Object_Connect (Widget, Signal_Button_Press_Event, Return_Callback.To_Marshaller (Button_Pressed_Forced'Access), Child); end Set_Dnd_Source; ------------------------ -- Draw_Dnd_Rectangle -- ------------------------ procedure Draw_Dnd_Rectangle (MDI : access MDI_Window_Record'Class; Mode : Dnd_Rectangle_Mode; Ref_Window : Gdk.Gdk_Window := null) is Root_X, Root_Y : Gint; Success : Boolean; begin case Mode is when Destroy => if MDI.Dnd_Target_Window /= null then Destroy (MDI.Dnd_Target_Window); MDI.Dnd_Target_Window := null; end if; when Hide => if MDI.Dnd_Target_Window /= null then Hide (MDI.Dnd_Target_Window); end if; when Show => if MDI.Dnd_Target_Window = null then Gtk_New (MDI.Dnd_Target_Window, Window_Popup); Set_Transient_For (MDI.Dnd_Target_Window, Gtk_Window (Get_Toplevel (MDI))); Set_Events (MDI.Dnd_Target_Window, Exposure_Mask); Modify_Bg (MDI.Dnd_Target_Window, State_Normal, MDI.Focus_Title_Color); Set_Decorated (MDI.Dnd_Target_Window, False); Set_Accept_Focus (MDI.Dnd_Target_Window, False); Realize (MDI.Dnd_Target_Window); -- This will not work on all Unix platforms, though... Set_Opacity (Get_Window (MDI.Dnd_Target_Window), 0.5); end if; Resize (MDI.Dnd_Target_Window, MDI.Dnd_Rectangle.Width, MDI.Dnd_Rectangle.Height); Get_Origin (Ref_Window, Root_X, Root_Y, Success); Move (MDI.Dnd_Target_Window, Root_X + MDI.Dnd_Rectangle.X, Root_Y + MDI.Dnd_Rectangle.Y); -- Keep the text above, for readability, especially when the -- dnd window is not transparent Gdk_Raise (Get_Window (MDI.Dnd_Window)); Show (MDI.Dnd_Target_Window); end case; end Draw_Dnd_Rectangle; ---------------------- -- Child_Drag_Begin -- ---------------------- procedure Child_Drag_Begin (Child : access MDI_Child_Record'Class; Event : Gdk_Event) is Tmp : Gdk_Grab_Status; Win : Gdk.Window.Gdk_Window; pragma Unreferenced (Tmp); begin -- Focus and raise the child. Raise_Child must be called explicitly -- since Set_Focus_Child won't do it if the child already has the focus. -- We have to raise the child, since otherwise the Pointer_Grab below -- will fail Print_Debug ("Child_Drag_Begin, focus and raise " & Get_Title (Child)); Set_Focus_Child (Child); Raise_Child (Child, False); Win := Get_Window (Child); -- If Child is floating, Win may be null at this point. In this case, -- do nothing. if Win /= null then -- Grab the pointer, so that we can detect whether the mouse moved -- far enough from its initial position to start a drag. This also -- ensures we get all the button_motion events Tmp := Pointer_Grab (Win, False, Button_Press_Mask or Button_Motion_Mask or Button_Release_Mask, Cursor => null, Time => 0); Child.MDI.Drag_Start_X := Gint (Get_X_Root (Event)); Child.MDI.Drag_Start_Y := Gint (Get_Y_Root (Event)); Child.MDI.In_Drag := In_Pre_Drag; Child.MDI.Dnd_Rectangle := (0, 0, 0, 0); else Print_Debug ("Child is floating, did not initiate DnD"); end if; end Child_Drag_Begin; ----------------------- -- Cancel_Child_Drag -- ----------------------- procedure Cancel_Child_Drag (Child : access MDI_Child_Record'Class) is begin Print_Debug ("Cancel_Child_Drag"); Pointer_Ungrab; Child.MDI.In_Drag := No_Drag; end Cancel_Child_Drag; ------------------------- -- Child_Drag_Finished -- ------------------------- procedure Child_Drag_Finished (Child : access MDI_Child_Record) is pragma Unreferenced (Child); begin null; end Child_Drag_Finished; -------------------- -- Get_Dnd_Target -- -------------------- procedure Get_Dnd_Target (MDI : access MDI_Window_Record'Class; Parent : out Gtk_Widget; Position : out Child_Position; Rectangle : out Gdk_Rectangle) is Border_Width, Border_Height : Gint; Win : Gdk.Gdk_Window; Current : Gtk_Widget; X, Y : Gint; begin Window_At_Pointer (X, Y, Win); if (MDI.Dnd_Target_Window /= null and then Win = Get_Window (MDI.Dnd_Target_Window)) or else (MDI.Dnd_Window /= null and then Win = Get_Window (MDI.Dnd_Window)) then Win := MDI.Dnd_Target; end if; if Win = null then Position := Position_Automatic; Parent := null; else Current := Gtk_Widget (Get_User_Data (Win)); while Current /= null and then Current /= Gtk_Widget (MDI) and then Current.all not in Gtkada_Multi_Paned_Record'Class and then Get_Parent (Current) /= null and then (Current.all not in Gtk_Notebook_Record'Class or else Get_Parent (Current).all not in Gtkada_Multi_Paned_Record'Class) and then Get_Parent (Current) /= Gtk_Widget (MDI) loop Current := Get_Parent (Current); end loop; -- If the cursor was put in a floating window, we should make the -- new child floating as well. if Current = null or else Get_Parent (Current) = null then Parent := null; Position := Position_Automatic; return; end if; if Current = Gtk_Widget (MDI) and then MDI.Central /= null then Current := Gtk_Widget (MDI.Central); -- Central area not empty ? We have therefore passed the mouse on -- one of the handles, and should not allow a drop there if not At_End (Start (MDI.Central)) then Position := Position_Automatic; Parent := null; return; end if; end if; Parent := Current; -- Are we on the sides of the MDI itself ? Rectangle := (X => 0, Y => 0, Width => Get_Allocation_Width (MDI), Height => Get_Allocation_Height (MDI)); Get_Pointer (MDI, X, Y); if Y < Max_Drag_Border_Width / 2 then Position := Position_Top; Parent := Gtk_Widget (MDI); Rectangle := (X => 0, Y => 0, Width => Rectangle.Width, Height => Max_Drag_Border_Width / 2); elsif Y > Rectangle.Height - Max_Drag_Border_Width / 2 then Position := Position_Bottom; Parent := Gtk_Widget (MDI); Rectangle := (X => 0, Y => Rectangle.Height - Max_Drag_Border_Width / 2, Width => Rectangle.Width, Height => Max_Drag_Border_Width / 2); elsif X < Max_Drag_Border_Width / 2 then Position := Position_Left; Parent := Gtk_Widget (MDI); Rectangle := (X => 0, Y => 0, Width => Max_Drag_Border_Width / 2, Height => Rectangle.Height); elsif X > Rectangle.Width - Max_Drag_Border_Width / 2 then Position := Position_Right; Parent := Gtk_Widget (MDI); Rectangle := (X => Rectangle.Width - Max_Drag_Border_Width / 2, Y => 0, Width => Max_Drag_Border_Width / 2, Height => Rectangle.Height); else -- Are we on the sides of the current MDI child ? Rectangle := (X => Get_Allocation_X (Parent), Y => Get_Allocation_Y (Parent), Width => Get_Allocation_Width (Parent), Height => Get_Allocation_Height (Parent)); Get_Pointer (Parent, X, Y); Border_Height := Gint'Min (Max_Drag_Border_Width, Rectangle.Height / 3); Border_Width := Gint'Min (Max_Drag_Border_Width, Rectangle.Width / 3); if Y < Border_Height then Position := Position_Top; Rectangle := (X => 0, Y => 0, Width => Rectangle.Width, Height => Border_Height); elsif Y > Rectangle.Height - Border_Height then Position := Position_Bottom; Rectangle := (X => 0, Y => Rectangle.Height - Border_Height, Width => Rectangle.Width, Height => Border_Height); elsif X < Border_Width then Position := Position_Left; Rectangle := (X => 0, Y => 0, Width => Border_Width, Height => Rectangle.Height); elsif X > Rectangle.Width - Border_Width then Position := Position_Right; Rectangle := (X => Rectangle.Width - Border_Width, Y => 0, Width => Border_Width, Height => Rectangle.Height); else Position := Position_Automatic; Rectangle := (X => Border_Width, Y => Border_Height, Width => Rectangle.Width - 2 * Border_Width, Height => Rectangle.Height - 2 * Border_Height); end if; end if; if No_Window_Is_Set (Parent) then Rectangle.X := Rectangle.X + Get_Allocation_X (Parent); Rectangle.Y := Rectangle.Y + Get_Allocation_Y (Parent); end if; end if; end Get_Dnd_Target; -------------------------- -- List_Of_Perspectives -- -------------------------- function List_Of_Perspectives (MDI : access MDI_Window_Record) return GNAT.Strings.String_List_Access is begin return MDI.Perspective_Names; end List_Of_Perspectives; ------------------------- -- Current_Perspective -- ------------------------- function Current_Perspective (MDI : access MDI_Window_Record'Class) return String is begin if MDI.Current_Perspective = null then return ""; else return Get_Attribute (MDI.Current_Perspective, "name", ""); end if; end Current_Perspective; end Gtkada.MDI;