-- -- -- package Gtk.Main.Router Copyright (c) Dmitry A. Kazakov -- -- Implementation Luebeck -- -- Spring, 2006 -- -- -- -- Last revision : 13:14 14 Sep 2019 -- -- -- -- 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. -- -- -- -- As a special exception, if other files instantiate generics from -- -- this unit, or you link this unit with other files to produce an -- -- executable, this unit does not by itself cause the resulting -- -- executable to be covered by the GNU General Public License. This -- -- exception does not however invalidate any other reasons why the -- -- executable file might be covered by the GNU Public License. -- --____________________________________________________________________-- with Ada.Characters.Handling; use Ada.Characters.Handling; with Ada.Characters.Latin_1; use Ada.Characters.Latin_1; with Ada.Streams; use Ada.Streams; with Ada.Strings.Fixed; use Ada.Strings.Fixed; with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; with Ada.Task_Identification; use Ada.Task_Identification; with Gdk.Color.IHLS; use Gdk.Color.IHLS; with GLib.Main; use GLib.Main; with GLib.Messages; use GLib.Messages; with GLib.Values; use GLib.Values; with GNAT.Sockets; use GNAT.Sockets; with GNAT.Traceback; use GNAT.Traceback; with GNAT.Traceback.Symbolic; use GNAT.Traceback.Symbolic; with Gtk.Box; use Gtk.Box; with Gtk.Button; use Gtk.Button; with Gtk.Check_Button; use Gtk.Check_Button; with Gtk.Clipboard; use Gtk.Clipboard; with Gtk.Dialog; use Gtk.Dialog; with Gtk.Image; use Gtk.Image; with Gtk.Image_Menu_Item; use Gtk.Image_Menu_Item; with Gtk.Label; use Gtk.Label; with Gtk.Menu; use Gtk.Menu; with Gtk.Missed; use Gtk.Missed; with Gtk.Scrolled_Window; use Gtk.Scrolled_Window; with Gtk.Style_Context; use Gtk.Style_Context; with Gtk.Text_Buffer; use Gtk.Text_Buffer; with Gtk.Text_Iter; use Gtk.Text_Iter; with Gtk.Text_Tag; use Gtk.Text_Tag; with Gtk.Text_View; use Gtk.Text_View; with Gtk.Widget; use Gtk.Widget; with Pango.Font; use Pango.Font; with System; use System; with System.Storage_Elements; use System.Storage_Elements; with Ada.Unchecked_Deallocation; with Ada.Unchecked_Conversion; with Gtk.Handlers; package body Gtk.Main.Router is -- -- Gateway_State -- The state of the protected object controlling -- interaction between the main loop task and an Ada -- task requesting servicing. The following diagram illustrates state -- transitions upon a request. -- -- main loop task -- : | -- : |__ -- sleeping | Request_Service (data) -- : | -- timer -->| __| -- |__ : -- | Initiate_Service : -- | Service (data) waiting -- | Complete_Service --> : -- __| :__ -- | | Serviced -- : <--------- | -- sleeping .__| -- : | -- Queue_Size : constant := 100; Max_Recursion : constant := 100; GPS_Prompt : constant String := "GPS>> "; GPS_Port : Natural := 50_000; Connected : Boolean := Standard.False; Recursion : Natural := 0; Window_X : GInt := 0; Window_Y : GInt := 0; Parent : Gtk_Window := null; Main : Task_ID := Null_Task_ID; Trace_Dialog : Gtk_Dialog; Channel : Socket_Type; Command : Unbounded_String; function Where (Text : String) return String is begin return " in Gtk.Main.Router." & Text; end Where; function Get_Parent (Window : Gtk_Window := null) return Gtk_Window is begin if Window = null then return Parent; else return Window; end if; end Get_Parent; package Window_Callback is new Gtk.Handlers.Callback (Gtk_Window_Record); procedure On_Quit (Window : access Gtk_Window_Record'Class); type Request_State is ( Request_Unused, Request_Pending, Request_Serviced, Request_Failed, Request_Successful ); type Request_Item; type Request_Item_Ptr is access all Request_Item; type Request_Item is limited record Previous : Request_Item_Ptr := Request_Item'Unchecked_Access; Next : Request_Item_Ptr := Request_Item'Unchecked_Access; State : Request_State := Request_Unused; Fault : Exception_Occurrence; Synchronous : Boolean; Data : Request_Data_Ptr; end record; function Get (Head : Request_Item) return Request_Item_Ptr; pragma Inline (Get); procedure Insert (Head : Request_Item; Item : Request_Item_Ptr); pragma Inline (Insert); function Length (Head : Request_Item) return Natural; pragma Inline (Length); function Get (Head : Request_Item) return Request_Item_Ptr is Self : constant Request_Item_Ptr := Head.Next.Previous; begin if Head.Next = Self then -- Empty return null; else declare Item : constant Request_Item_Ptr := Head.Next; begin Self.Next := Item.Next; Item.Next.Previous := Self; Item.Next := Item; Item.Previous := Item; return Item; end; end if; end Get; procedure Insert (Head : Request_Item; Item : Request_Item_Ptr) is Self : constant Request_Item_Ptr := Head.Next.Previous; begin Item.Next.Previous := Item.Previous; Item.Previous.Next := Item.Next; Item.Previous := Self.Previous; Item.Next := Self; Item.Next.Previous := Item; Item.Previous.Next := Item; end Insert; function Length (Head : Request_Item) return Natural is This : Request_Item_Ptr := Head.Next; Self : constant Request_Item_Ptr := This.Previous; Count : Natural := 0; begin while This /= Self loop Count := Count + 1; This := This.Next; end loop; return Count; end Length; protected Gateway is procedure Abort_Service ( Item : in out Request_Item_Ptr; Error : Exception_Occurrence ); procedure Allocated_New; procedure Clean_Up (Item : out Request_Item_Ptr); procedure Complete_Service (Item : in out Request_Item_Ptr); procedure Get_Free_Item (Item : out Request_Item_Ptr); function Get_Max_Async return Positive; function Get_Request_Info return String; function Is_Quitted return Boolean; procedure Initiate_Service (Item : out Request_Item_Ptr); entry Request_Asynchronous (Item : Request_Item_Ptr); entry Request_Synchronous (Item : in out Request_Item_Ptr); procedure Set_Max_Async (Max : Positive); procedure Quit; private entry Serviced (Boolean) (Item : in out Request_Item_Ptr); Quitted : Boolean := Standard.False; Current : Boolean := Standard.False; Async : Natural := 0; -- Number of asynchronous requests Max_Async : Positive := 100; -- Maximum number of such requests Allocated : Natural := 0; -- Total allocated items Queued : aliased Request_Item; Ready : aliased Request_Item; Free : aliased Request_Item; end Gateway; -- -- Callback -- Called from the main loop on timer events -- function Callback return Boolean is Item : Request_Item_Ptr; begin loop Gateway.Initiate_Service (Item); exit when Item = null; begin if Recursion < Max_Recursion then Recursion := Recursion + 1; begin Service (Item.Data.all); Recursion := Recursion - 1; exception when others => Recursion := Recursion - 1; raise; end; end if; Gateway.Complete_Service (Item); exception when Error : others => Gateway.Abort_Service (Item, Error); end; end loop; return Standard.True; end Callback; protected body Gateway is procedure Abort_Service ( Item : in out Request_Item_Ptr; Error : Exception_Occurrence ) is begin if Item.Synchronous then Item.State := Request_Failed; Save_Occurrence (Item.Fault, Error); Insert (Ready, Item); Current := not Current; else Item.State := Request_Unused; Insert (Free, Item); Async := Async - 1; end if; end Abort_Service; procedure Allocated_New is begin Allocated := Allocated + 1; end Allocated_New; procedure Clean_Up (Item : out Request_Item_Ptr) is begin Item := Get (Ready); if Item = null then Item := Get (Free); end if; end Clean_Up; procedure Complete_Service (Item : in out Request_Item_Ptr) is begin if Item.Synchronous then Item.State := Request_Successful; Insert (Ready, Item); Current := not Current; else Item.State := Request_Unused; Insert (Free, Item); Async := Async - 1; end if; end Complete_Service; procedure Get_Free_Item (Item : out Request_Item_Ptr) is begin Item := Get (Free); end Get_Free_Item; function Get_Max_Async return Positive is begin return Max_Async; end Get_Max_Async; function Get_Request_Info return String is function Is_Quitted return String is begin if Quitted then return " [quitted]"; else return ""; end if; end Is_Quitted; begin return ( "requests: pending" & Integer'Image (Length (Queued)) & ", ready" & Integer'Image (Length (Ready )) & ", free" & Integer'Image (Length (Free )) & ", allocated" & Integer'Image (Allocated) & ", max async" & Integer'Image (Max_Async) & Is_Quitted ); end Get_Request_Info; procedure Initiate_Service (Item : out Request_Item_Ptr) is begin Item := Get (Queued); end Initiate_Service; function Is_Quitted return Boolean is begin return Quitted; end Is_Quitted; procedure Quit is begin Quitted := Standard.True; end Quit; entry Request_Asynchronous ( Item : Request_Item_Ptr ) when Async < Max_Async or else Quitted is begin if Quitted then Item.State := Request_Unused; Insert (Free, Item); raise Quit_Error; end if; Item.State := Request_Pending; Insert (Queued, Item); Item.Synchronous := Standard.False; Async := Async + 1; end Request_Asynchronous; entry Request_Synchronous ( Item : in out Request_Item_Ptr ) when Standard.True is begin if Quitted then Item.State := Request_Unused; Insert (Free, Item); raise Quit_Error; end if; Item.State := Request_Pending; Insert (Queued, Item); Item.Synchronous := Standard.True; requeue Serviced (not Current); end Request_Synchronous; entry Serviced (for Toggle in Boolean) ( Item : in out Request_Item_Ptr ) when Quitted or else Current = Toggle is begin if Quitted then Item.State := Request_Unused; Insert (Free, Item); raise Quit_Error; else case Item.State is when Request_Unused => null; when Request_Pending | Request_Serviced => requeue Serviced (not Current); when Request_Failed => Insert (Free, Item); Reraise_Occurrence (Item.Fault); when Request_Successful => Insert (Free, Item); end case; end if; end Serviced; procedure Set_Max_Async (Max : Positive) is begin Max_Async := Max; end Set_Max_Async; end Gateway; package body Generic_Message is procedure Free is new Ada.Unchecked_Deallocation ( Message_Data, Message_Data_Ptr ); package Sources is new Generic_Sources (Message_Data_Ptr); function Service (Message : Message_Data_Ptr) return Boolean is Ptr : Message_Data_Ptr := Message; begin Message.Handler (Message.Data); Free (Ptr); return Standard.False; exception when others => Free (Ptr); return Standard.False; end Service; procedure Send ( Handler : Handler_Procedure; Data : User_Data; Timeout : Duration := 0.5 ) is Message : Message_Data_Ptr := new Message_Data; Item : Request_Item_Ptr; begin Message.Handler := Handler; Message.Data := Data; if Main = Current_Task then declare ID : G_Source_ID; begin ID := Sources.Idle_Add (Service'Access, Message); end; elsif Main = Null_Task_ID then raise Program_Error; else Gateway.Get_Free_Item (Item); if Item = null then Item := new Request_Item; Gateway.Allocated_New; end if; Item.Synchronous := Standard.False; Item.Data := Message.all'Unchecked_Access; select Gateway.Request_Asynchronous (Item); or delay Timeout; raise Busy_Error with "Current state " & Gateway.Get_Request_Info; end select; end if; exception when others => Free (Message); raise; end Send; procedure Service (Data : in out Message_Data) is Message : Message_Data_Ptr := Data'Unchecked_Access; begin Message.Handler (Message.Data); Free (Message); exception when Error : others => Log ( GtkAda_Contributions_Domain, Log_Level_Critical, ( Exception_Information (Error) & Where ("Generic_Message.Service") ) ); Free (Message); end Service; end Generic_Message; -- -- Connect -- To the GPS -- -- The procedure shows a dialog box while connecting to the GPS server. -- The dialog box shows connection error message when connection fails. -- Waiting for connection can be canceled by closing the box. -- procedure Connect is Dialog : Gtk_Dialog; Message : Unbounded_String; Label : Gtk_Label; begin Gtk_New ( Dialog, ( "GPS at " & Host_Name & " port" & Integer'Image (GPS_Port) ), Get_Parent, Destroy_With_Parent + Modal ); Gtk_New (Label, "...connecting..."); Set_Line_Wrap (Label, Standard.True); Pack_Start ( Get_Content_Area (Dialog), Label, Standard.True, Standard.True ); declare -- -- An independent timer for a connection task to communicate -- with the dialog box. It must be independent because the -- default timer may be blocked at this point. -- package Timers is new Generic_Sources (Integer); -- -- Connect_To_GPS -- The task connecting to GPS -- task Connect_To_GPS; task body Connect_To_GPS is Address : Sock_Addr_Type; begin Connected := Standard.False; Create_Socket (Channel); Address.Addr := Addresses (Get_Host_By_Name ("localhost")); Address.Port := Port_Type (GPS_Port); begin Connect_Socket (Channel, Address); Connected := Standard.True; exception when Error : Socket_Error => Append ( Message, ( "Check if GPS is started with the server " & "option. For example" & Character'Val (10) & " > gps --server=" & Trim ( Integer'Image (GPS_Port), Ada.Strings.Both ) & Character'Val (10) & Exception_Message (Error) ) ); when Error : others => Append (Message, Exception_Information (Error)); end; end Connect_To_GPS; -- -- Connect_Callback -- Timer callback -- -- When the task is terminated, the callback either shows the -- error message in the dialog box, or else closes it. -- function Connect_Callback (Data : Integer) return Boolean is begin if Connect_To_GPS'Terminated then if Length (Message) = 0 then Response (Dialog, Gtk_Response_OK); elsif Label /= null then Label := null; Set_Text (Label, To_String (Message)); end if; end if; return Standard.True; end Connect_Callback; ID : G_Source_Id; Close : Gtk_Button; begin Close := Add_Button_From_Stock ( Dialog => Dialog, Response => Gtk_Response_Close, Icon => Stock_Close, Label => "_Close", Tip => "Stop connecting" ); Close.Set_Can_Default (Standard.True); Dialog.Show_All; ID := Timers.Timeout_Add ( Interval => 50, Func => Connect_Callback'Access, Data => 0 ); case Run (Dialog) is when Gtk_Response_OK => -- Closed on success from the task Remove (ID); when others => -- Closed on error or by user Remove (ID); -- Stop polling Close_Socket (Channel); -- Will aboort task if active Connected := Standard.False; end case; end; Destroy (Dialog); end Connect; function Get_Max_Asynchronous return Positive is begin return Gateway.Get_Max_Async; end Get_Max_Asynchronous; function Get_Request_Info return String is begin return Gateway.Get_Request_Info; end Get_Request_Info; -- -- GPS_Read -- Reading GPS connection socket until prompt it received -- procedure GPS_Read is Buffer : Stream_Element_Array (1..1); Byte : Character; From : Sock_Addr_Type; Last : Stream_Element_Offset; Index : Positive := GPS_Prompt'First; begin loop Receive_Socket (Channel, Buffer, Last, From); Byte := Character'Val (Stream_Element'Pos (Buffer (1))); if Byte = GPS_Prompt (Index) then exit when Index = GPS_Prompt'Last; Index := Index + 1; elsif Index > GPS_Prompt'First then Index := GPS_Prompt'First; if Byte = GPS_Prompt (Index) then Index := GPS_Prompt'First + 1; end if; end if; end loop; end GPS_Read; -- -- GPS_Send -- Writing into GPS socket -- -- Command - The GPS command to send -- procedure GPS_Send (Command : String) is Buffer : Stream_Element_Array (1..Command'Length); for Buffer'Address use Command (Command'First)'Address; pragma Import (Ada, Buffer); Last : Stream_Element_Offset; begin Send_Socket (Channel, Buffer, Last); end GPS_Send; procedure Init ( Window : not null access Gtk_Window_Record'Class; Period : Duration := 0.2; GPS_Port : Natural := 50_000 ) is begin Gtk.Main.Router.GPS_Port := GPS_Port; if Main = Null_Task_ID then Main := Current_Task; declare ID : G_Source_Id; begin ID := Timeout_Add ( Guint (Float (Period) * 1000.0), Callback'Access ); end; Parent := Window.all'Unchecked_Access; Window_Callback.Connect (Window, "destroy", On_Quit'Access); end if; end Init; function Is_Active return Boolean is begin return not Gateway.Is_Quitted; end Is_Active; procedure Quit is begin Gateway.Quit; end Quit; procedure Request (Data : in out Request_Data'Class) is begin if Main = Current_Task then if Gateway.Is_Quitted then raise Quit_Error; end if; if Recursion < Max_Recursion then Recursion := Recursion + 1; begin Service (Data); Recursion := Recursion - 1; exception when others => Recursion := Recursion - 1; raise; end; end if; elsif Main = Null_Task_ID then Log ( GtkAda_Contributions_Domain, Log_Level_Critical, "No main task active" & Where ("Request") ); raise Program_Error; else declare Item : Request_Item_Ptr; begin Gateway.Get_Free_Item (Item); if Item = null then Item := new Request_Item; Gateway.Allocated_New; end if; Item.Data := Data'Unchecked_Access; Gateway.Request_Synchronous (Item); end; end if; end Request; package body Generic_Callback_Request is procedure Request ( Callback : Callback_Procedure; Data : not null access User_Data ) is Marshaler : Callback_Data (Data); begin Marshaler.Callback := Callback; Request (Marshaler); end Request; procedure Service (Data : in out Callback_Data) is begin Data.Callback (Data.Parameters); end Service; end Generic_Callback_Request; type Callback_Request_Data is new Request_Data with record Callback : Gtk_Callback; end record; procedure Service (Data : in out Callback_Request_Data); procedure Service (Data : in out Callback_Request_Data) is begin Data.Callback.all; end Service; procedure Request (Service : Gtk_Callback) is Marshaler : Callback_Request_Data; begin Marshaler.Callback := Service; Request (Marshaler); end Request; type UTF8_String_Ptr is access all UTF8_String; type Say_Data is record Message : UTF8_String_Ptr; Title : UTF8_String_Ptr; Mode : UTF8_String_Ptr; Justification : Gtk_Justification; Parent : Gtk_Widget; end record; package Say_Callback is new Generic_Callback_Request (Say_Data); procedure Say (Data : not null access Say_Data) is Main : Gtk_Window := Parent; begin if Data.Parent /= null then declare Top : Gtk_Widget; begin Top := Data.Parent.Get_Toplevel; if ( Top /= null and then Top.all in Gtk_Window_Record'Class ) then Main := Gtk_Window_Record'Class (Top.all)'Unchecked_Access; end if; end; end if; Message_Dialog ( Message => Data.Message.all, Mode => Data.Mode.all, Title => Data.Title.all, Justification => Data.Justification, Parent => Main ); end Say; procedure Say ( Message : UTF8_String; Title : UTF8_String := ""; Mode : UTF8_String := Stock_Dialog_Info; Justification : Gtk_Justification := Justify_Left; Parent : access Gtk_Widget_Record'Class := null ) is Message_Text : aliased UTF8_String := Message; Title_Text : aliased UTF8_String := Title; Mode_Text : aliased UTF8_String := Mode; Data : aliased Say_Data; begin Data.Message := Message_Text'Unchecked_Access; Data.Title := Title_Text'Unchecked_Access; Data.Mode := Mode_Text'Unchecked_Access; Data.Justification := Justification; if Parent /= null then Data.Parent := Parent.all'Unchecked_Access; end if; Say_Callback.Request (Say'Access, Data'Access); end Say; procedure Set_Max_Asynchronous (Max : Positive) is begin Gateway.Set_Max_Async (Max); end Set_Max_Asynchronous; type Trace_Request (Length : Natural; Break : Boolean) is record Text : String (1..Length); end record; package Trace_Callback is new Generic_Callback_Request (Trace_Request); package Dialog_Handlers is new Gtk.Handlers.Callback (Gtk_Dialog_Record); package View_Handlers is new Gtk.Handlers.User_Callback (Gtk_Text_View_Record, Gtk_Dialog); package View_Event_Handlers is new Gtk.Handlers.User_Return_Callback ( Gtk_Text_View_Record, Boolean, Gtk_Dialog ); package Gtk_Image_Menu_Item_Handlers is new Gtk.Handlers.Callback (Gtk_Image_Menu_Item_Record); Messages_List : Gtk_Text_Buffer; Step_Button : Gtk_Button; Run_Button : Gtk_Button; View : Gtk_Text_View; Break_Button : Gtk_Check_Button; type Tag_Index is mod 5; Tags : array (Tag_Index) of Gtk_Text_Tag; Current_Tag : Tag_Index := 0; -- -- On_Button_Press -- Event handler -- function On_Button_Press ( Dialog : access Gtk_Text_View_Record'Class; Event : Gdk_Event; Parent : Gtk_Dialog ) return Boolean is begin if Get_Event_Type (Event) = Button_Press then Window_X := GInt (Event.Button.X); Window_Y := GInt (Event.Button.Y); end if; return Standard.False; end On_Button_Press; -- -- On_Go_To_Location -- Event handler -- procedure On_Go_To_Location ( Item : access Gtk_Image_Menu_Item_Record'Class ) is begin if not Connected then Connect; end if; if Connected then GPS_Send (To_String (Command)); GPS_Read; end if; end On_Go_To_Location; -- -- On_Paste_Trace -- Event handler -- procedure On_Paste_Trace ( Item : access Gtk_Image_Menu_Item_Record'Class ) is Text : constant String := Wait_For_Text (Get); Pointer : Integer := Text'First; procedure Skip is begin while Pointer < Text'Last loop exit when Text (Pointer) /= ' '; Pointer := Pointer + 1; end loop; end Skip; function Get_Item return Address is Value : Integer_Address := 0; begin if Pointer + 1 > Text'Last then raise Constraint_Error; end if; if Text (Pointer..Pointer + 1) /= "0x" then raise Constraint_Error; end if; Pointer := Pointer + 2; while Pointer < Text'Last loop case Text (Pointer) is when '0'..'9' => Value := ( Value * 16 + Character'Pos (Text (Pointer)) - Character'Pos ('0') ); when 'a'..'f' => Value := ( Value * 16 + Character'Pos (Text (Pointer)) - Character'Pos ('a') + 10 ); when 'A'..'F' => Value := ( Value * 16 + Character'Pos (Text (Pointer)) - Character'Pos ('A') + 10 ); when others => exit; end case; Pointer := Pointer + 1; end loop; if Text (Pointer - 1) = 'x' then raise Constraint_Error; end if; return To_Address (Value); end Get_Item; Count : Natural := 0; begin declare Location : Address; begin while Pointer <= Text'Last loop Skip; Location := Get_Item; Count := Count + 1; end loop; exception when others => null; end; if Count > 0 then declare TB : Tracebacks_Array (1..Count); begin Pointer := Text'First; for Index in TB'Range loop Skip; TB (Index) := Get_Item; end loop; Trace ( "Symbolic stack traceback from the clipboard:" & LF & Symbolic_Traceback (TB) & LF ); end; end if; end On_Paste_Trace; -- -- On_Populate_Popup -- Event handler -- procedure On_Populate_Popup ( Dialog : access Gtk_Text_View_Record'Class; Params : GValues; Parent : Gtk_Dialog ) is Buffer : constant Gtk_Text_Buffer := Get_Buffer (Dialog); Widget : Gtk_Widget; Menu : Gtk_Menu; Item : Gtk_Image_Menu_Item; Icon : Gtk_Image; Start : Gtk_Text_Iter; Stop : Gtk_Text_Iter; Can_Go : Boolean := Standard.False; begin Widget := Convert (Get_Address (Nth (Params, 1))); if Widget /= null and then Widget.all in Gtk_Menu_Record'Class then Menu := Gtk_Menu_Record'Class (Widget.all)'Unchecked_Access; Menu.Modify_Font ( Get_Font ( Get_Style_Context (Parent), Gtk_State_Flag_Normal ) ); if Get.Wait_Is_Text_Available then -- Paste traceback item Gtk_New (Item, "Paste stack traceback"); Gtk_New (Icon, Stock_Find, Icon_Size_Menu); Item.Set_Image (Icon); Item.Show_All; Gtk_Image_Menu_Item_Handlers.Connect ( Item, "activate", On_Paste_Trace'Access ); Menu.Add (Item); end if; if Main /= Null_Task_ID then -- Go to the location item declare use type Gdk.Gdk_Window; Buffer_X : GInt; Buffer_Y : GInt; Moved : Boolean; begin Dialog.Window_To_Buffer_Coords ( Text_Window_Widget, Window_X, Window_Y, Buffer_X, Buffer_Y ); Dialog.Get_Iter_At_Location (Start, Buffer_X, Buffer_Y); Copy (Start, Stop); Moved := Standard.True; while Moved and then not Starts_Line (Start) loop Backward_Cursor_Position (Start, Moved); end loop; Moved := Standard.True; while Moved and then not Ends_Line (Stop) loop Forward_Cursor_Position (Stop, Moved); end loop; end; declare Line : String := Buffer.Get_Text (Start, Stop); Pointer : Integer := Line'Last + 1; begin if ( Line'Length < 3 or else Line (Line'First) /= '0' or else Line (Line'First + 1) /= 'x' ) then goto Break; end if; loop if Pointer = Line'First then goto Break; end if; Pointer := Pointer - 1; exit when Line (Pointer) not in '0'..'9'; end loop; if ( Pointer - 4 < Line'First or else Line (Pointer - 4) /= '.' or else To_Lower (Line (Pointer - 3)) /= 'a' or else To_Lower (Line (Pointer - 2)) /= 'd' or else Line (Pointer) /= ':' ) then goto Break; end if; Line (Pointer) := ' '; case To_Lower (Line (Pointer - 1)) is when 's' | 'b' => Pointer := Pointer - 4; when others => goto Break; end case; loop if Pointer - 4 < Line'First then goto Break; end if; exit when Line (Pointer - 4..Pointer - 1) = " at "; Pointer := Pointer - 1; end loop; Can_Go := Standard.True; Command := To_Unbounded_String ("Editor.edit "); Append (Command, Line (Pointer..Line'Last)); Append (Command, Character'Val (10)); end; <> Gtk_New (Item, "Go to the source location"); Item.Set_Sensitive (Can_Go); Gtk_New (Icon, Stock_Jump_To, Icon_Size_Menu); Item.Set_Image (Icon); Item.Show_All; Gtk_Image_Menu_Item_Handlers.Connect ( Item, "activate", On_Go_To_Location'Access ); Menu.Add (Item); end if; end if; end On_Populate_Popup; procedure On_Quit (Window : access Gtk_Window_Record'Class) is procedure Free is new Ada.Unchecked_Deallocation ( Request_Item, Request_Item_Ptr ); procedure Free is new Ada.Unchecked_Deallocation ( Request_Data'Class, Request_Data_Ptr ); Item : Request_Item_Ptr; begin Parent := null; Gateway.Quit; loop Gateway.Initiate_Service (Item); exit when Item = null; if not Item.Synchronous then Free (Item.Data); end if; Free (Item); end loop; loop Gateway.Clean_Up (Item); exit when Item = null; Free (Item); end loop; end On_Quit; procedure On_Response ( Dialog : access Gtk_Dialog_Record'Class; Response : Glib.Values.GValues ) is begin case Gtk_Response_Type (Get_Int (Nth (Response, 1))) is when Gtk_Response_None => Trace_Dialog := null; when Gtk_Response_Cancel => Set_Active (Break_Button, Standard.False); when Gtk_Response_OK => Set_Active (Break_Button, Standard.True); when others => Destroy (Trace_Dialog); Trace_Dialog := null; end case; end On_Response; procedure Trace (Data : not null access Trace_Request) is Start : Gtk_Text_Iter; Stop : Gtk_Text_Iter; begin if Trace_Dialog = null then declare Scroll : Gtk_Scrolled_Window; Button : Gtk_Button; Font : Pango_Font_Description := From_String ("monospace 10"); begin Gtk_New ( Trace_Dialog, "Trace", Get_Parent, Destroy_With_Parent ); Tags := (others => null); Gtk_New (Messages_List); Gtk_New (View, Messages_List); Messages_List.Unref; View.Modify_Font (Font); Free (Font); -- Get menu View_Handlers.Connect ( View, "populate-popup", On_Populate_Popup'Access, Trace_Dialog ); View_Event_Handlers.Connect ( View, "button_press_event", View_Event_Handlers.To_Marshaller ( On_Button_Press'Access ), Trace_Dialog ); -- Scrolled window Gtk_New (Scroll); Scroll.Set_Policy (Policy_Automatic, Policy_Automatic); Scroll.Add (View); Get_Content_Area (Trace_Dialog).Pack_Start (Scroll); -- Break button Gtk_New (Break_Button, "Break"); Break_Button.Set_Active (Standard.True); Trace_Dialog.Get_Action_Area.Pack_Start ( Break_Button, Standard.False, Standard.False ); -- Continue button Step_Button := Add_Button_From_Stock ( Dialog => Trace_Dialog, Response => Gtk_Response_OK, Icon => Stock_Media_Next, Label => "_Next", Tip => "Run to the next message" ); Step_Button.Set_Can_Default (Standard.True); Run_Button := Add_Button_From_Stock ( Dialog => Trace_Dialog, Response => Gtk_Response_Cancel, Icon => Stock_Media_Record, Label => "_Continue", Tip => "Continue without breaking" ); Button := Add_Button_From_Stock ( Dialog => Trace_Dialog, Response => Gtk_Response_Close, Icon => Stock_Quit, Label => "_Close", Tip => "Close trace window and continue" ); Dialog_Handlers.Connect ( Trace_Dialog, "response", On_Response'Access ); Trace_Dialog.Set_Default_Size (200, 400); Trace_Dialog.Show_All; Trace_Dialog.Set_Transient_For (Get_Parent); end; end if; declare -- Max_Buffer_Size : constant := 10_000; Offset : constant GInt := Messages_List.Get_Char_Count; begin -- while Offset > Max_Buffer_Size loop -- Messages_List.Get_Iter_At_Line (Start, 0); -- Messages_List.Get_End_Iter (Stop); -- Messages_List.Get_Iter_At_Line (Stop, 1); -- Messages_List.Delete (Start, Stop); -- Offset := Messages_List.Get_Char_Count; -- end loop; Messages_List.Get_End_Iter (Start); -- Messages_List.Insert (Start, Data.Text); Insert_Alt (Messages_List, Start, Data.Text); Messages_List.Get_Iter_At_Offset (Start, Offset); end; if View.Scroll_To_Iter ( Start, 0.25, Standard.False, 0.0, 0.0 ) then null; end if; -- Colorize new text if Tags (Current_Tag) = null then Tags (Current_Tag) := Create_Tag (Messages_List); Gdk.Color.Set_Property ( Tags (Current_Tag), Background_Gdk_Property, To_RGB ( Val ( ( Hue => 0, Luminance => 240 * 256, Saturation => 29 * 256 ), Natural (Current_Tag), Tags'Length ) ) ); end if; declare Offset : constant GInt := Messages_List.Get_Char_Count; begin Messages_List.Get_Iter_At_Offset (Stop, Offset); end; Messages_List.Apply_Tag (Tags (Current_Tag), Start, Stop); Current_Tag := Current_Tag + 1; if not (Data.Break or else Break_Button.Get_Active) then while Events_Pending loop -- Pump messages queue exit when not Main_Iteration_Do (Standard.False); end loop; return; end if; -- Setting buttons Step_Button.Set_Sensitive (Standard.True); Run_Button.Set_Sensitive (Standard.True); Trace_Dialog.Set_Default (Step_Button); Trace_Dialog.Set_Modal (Standard.True); case Trace_Dialog.Run is when Gtk_Response_Cancel | Gtk_Response_OK => Trace_Dialog.Set_Modal (Standard.False); Step_Button.Set_Sensitive (Standard.False); Run_Button.Set_Sensitive (Standard.False); when others => null; end case; end Trace; procedure Trace ( Message : UTF8_String; Break : Boolean := Standard.False ) is Data : aliased Trace_Request (Message'Length + 1, Break); begin Data.Text := Message & LF; Trace_Callback.Request (Trace'Access, Data'Access); end Trace; procedure Trace ( Error : Exception_Occurrence; Break : Boolean := Standard.True ) is begin Trace (Exception_Information (Error), Break); end Trace; end Gtk.Main.Router;