This package provides an interactive canvas, on which the user can put items, move them with the mouse, etc. The items can be connected together, and the connections remain active while the items are moved.
It also supports scrolling if put in a Gtk_Scrolled_Window. The canvas will be scrolled (and the selected items moved) if an item is selected and the mouse is dragged on a small area on the side of the canvas or even directly outside of the canvas. Scrolling will continue until the mouse is either released or moved back inside the canvas.
The scrolling speed will slightly increase over time if the mouse is kept outside of the canvas. This makes the canvas much more comfortable to use for the user.
All items put in this canvas must inherit from the type Canvas_Item_Record. However, it is your responsability, as a programmer, to provide drawing routines. In fact, all these items should draw in a pixmap, which is then copied automatically to the screen whenever the canvas needs to redraw itself.
The items can also react to mouse events: mouse clicks are transmitted to the item if the mouse did not move more than a given amount of pixels. To decide what their reaction should be, you should override the On_Button_Click subprogram.
This canvas is not intended for cases where you want to put hundreds of items on the screen. For instance, it does not provide any smart double-buffering other than the one provided by gtk+ itself, and thus you would get some flicker if there are too many items.
There are three coordinate systems used by widget. All the subprograms expect a specific coordinate system as input or output. Here are the three systems:
Items are selected automatically when they are clicked. If Control is pressed at the same time, multiple items can be selected. If the background is clicked (and control is not pressed), then all items are unselected. Pressing and dragging the mouse in the backgroudn draws a virtual box on the screen. All the items fully included in this box when it is released will be selected (this will replace the current selection if Control was not pressed).
Signals |
---|
Types |
---|
type Arrow_Type is (No_Arrow, -- the link does not have an arrow Start_Arrow, -- the link has an arrow at its beginning End_Arrow, -- the link has an arrow at the end Both_Arrow -- the link has an arrow on both sides ); | |
Indicate whether the links have an arrow or not.
| |
type Item_Iterator is private; | |
| |
type Item_Processor is access function (Canvas : access Interactive_Canvas_Record'Class; | |
| |
type Item_Side is (East, West, North, South); | |
Each side of an item, along its rectangle bounding box
| |
type Layout_Algorithm is access procedure | |
| |
type Link_Processor is access function (Canvas : access Interactive_Canvas_Record'Class; | |
| |
type Selection_Iterator is private; | |
|
Subprograms |
---|
Creating a canvas | ||
procedure Gtk_New (Canvas : out Interactive_Canvas; Auto_Layout : Boolean := True); | ||
Create a new empty Canvas. | ||
procedure Configure (Canvas : access Interactive_Canvas_Record; Grid_Size : Glib.Guint := Default_Grid_Size; Annotation_Font : Pango.Font.Pango_Font_Description := Pango.Font.From_String (Default_Annotation_Font); | ||
| ||
procedure Draw_Area (Canvas : access Interactive_Canvas_Record'Class; Rect : Gdk.Rectangle.Gdk_Rectangle); | ||
Draw in Canvas the specified area.
| ||
procedure Draw_Background (Canvas : access Interactive_Canvas_Record; Screen_Rect : Gdk.Rectangle.Gdk_Rectangle); | ||
Draw the background of the canvas. This procedure should be overriden if Screen_Rect is the rectangle on the screen that needs to be refreshed. These are canvas coordinates, therefore you must take into account the current zoom level while drawing. The default implementation draws a grid.
An example implementation that draws a background image is shown at the
end of this file.
| ||
procedure Draw_Grid (Canvas : access Interactive_Canvas_Record; GC : Gdk.GC.Gdk_GC; Screen_Rect : Gdk.Rectangle.Gdk_Rectangle); | ||
Helper function that can be called from Draw_Background. It cannot be | ||
procedure Set_Orthogonal_Links (Canvas : access Interactive_Canvas_Record; Orthogonal : Boolean); | ||
If Orthogonal is True, then all the links will be drawn only with | ||
function Get_Orthogonal_Links (Canvas : access Interactive_Canvas_Record) return Boolean; | ||
Return True if the links are only drawn horizontally and vertically.
| ||
procedure Align_On_Grid (Canvas : access Interactive_Canvas_Record; Align : Boolean := True); | ||
Choose whether the items should be aligned on the grid when moved. | ||
function Get_Align_On_Grid (Canvas : access Interactive_Canvas_Record) return Boolean; | ||
Return True if items are currently aligned on grid.
| ||
procedure Move_To (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class; X, Y : Glib.Gint := Glib.Gint'First); | ||
Move the item in the canvas, to world coordinates (X, Y). | ||
procedure Set_Items (Canvas : access Interactive_Canvas_Record; Items : Glib.Graphs.Graph); | ||
Set the items and links to display in the canvas from Items.
You mustn't destroy items yourself, this is done automatically when the
canvas is destroyed.
| ||
procedure Put (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class; X, Y : Glib.Gint := Glib.Gint'First); | ||
Add a new item to the canvas, at world coordinates (X, Y). | ||
function Item_At_Coordinates (Canvas : access Interactive_Canvas_Record; X, Y : Glib.Gint) return Canvas_Item; | ||
Return the item at world coordinates (X, Y) which is on top of all | ||
function Item_At_Coordinates (Canvas : access Interactive_Canvas_Record; Event : Gdk.Event.Gdk_Event) return Canvas_Item; | ||
Same as above, but using the canvas coordinates of the event, taking | ||
procedure Remove (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class); | ||
Remove an item and all the links to and from it from the canvas. | ||
procedure Item_Updated (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class); | ||
This should be called when Item has changed the contents of its | ||
procedure Refresh_Canvas (Canvas : access Interactive_Canvas_Record); | ||
Redraw the whole canvas (both in the double buffer and on the screen).
| ||
procedure Raise_Item (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class); | ||
Raise the item so that it is displayed on top of all the others | ||
procedure Lower_Item (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class); | ||
Lower the item so that it is displayed below all the others. | ||
function Is_On_Top (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class) return Boolean; | ||
Return True if Item is displayed on top of all the others in the canvas.
| ||
procedure Show_Item (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class); | ||
Scroll the canvas so that Item is visible.
| ||
Iterating over items | ||
procedure For_Each_Item (Canvas : access Interactive_Canvas_Record; Execute : Item_Processor; Linked_From_Or_To : Canvas_Item := null); | ||
Execute an action on each of the items contained in the canvas.
If Linked_From_Or_To is not null, then only the items linked to this one
will be processed. It is possible that a given item will be returned
twice, if it is both linked to and from the item.
| ||
function Start (Canvas : access Interactive_Canvas_Record; Linked_From_Or_To : Canvas_Item := null) return Item_Iterator; | ||
Return the first item in the canvas. | ||
procedure Next (Iter : in out Item_Iterator); | ||
Move the iterator to the next item. | ||
function Get (Iter : Item_Iterator) return Canvas_Item; | ||
Return the item pointed to by the iterator. | ||
function Is_Linked_From (Iter : Item_Iterator) return Boolean; | ||
Return True if there is a link from: | ||
Zooming | ||
procedure Zoom (Canvas : access Interactive_Canvas_Record; Percent : Glib.Guint := 100; Steps : Glib.Guint := 1); | ||
Zoom in or out in the canvas. Steps is the number of successive zooms that will be done to provide smooth scrolling.
Note that one possible use for this function is to refresh the canvas
and emit the "zoomed" signal, which might redraw all the items. This can
be accomplished by keeping the default 100 value for Percent.
| ||
function Get_Zoom (Canvas : access Interactive_Canvas_Record) return Glib.Guint; | ||
Return the current zoom level
| ||
function To_Canvas_Coordinates (Canvas : access Interactive_Canvas_Record'Class; X : Glib.Gint) return Glib.Gint; | ||
Scale the scalar X depending by the zoom level (map from world | ||
function Top_World_Coordinates (Canvas : access Interactive_Canvas_Record'Class) return Glib.Gint; | ||
Return the world coordinates for the y=0 canvas coordinates (ie for the | ||
function Left_World_Coordinates (Canvas : access Interactive_Canvas_Record'Class) return Glib.Gint; | ||
Return the world coordinates for the x=0 canvas coordinates (ie for the | ||
function To_World_Coordinates (Canvas : access Interactive_Canvas_Record'Class; X : Glib.Gint) return Glib.Gint; | ||
Scale the scalar X depending by the zoom level (map from canvas | ||
procedure Get_World_Coordinates (Canvas : access Interactive_Canvas_Record'Class; X, Y : out Glib.Gint; Width : out Glib.Gint; Height : out Glib.Gint); | ||
Return the world coordinates of Canvas.
| ||
Layout of items | ||
procedure Set_Layout_Algorithm (Canvas : access Interactive_Canvas_Record; Algorithm : Layout_Algorithm); | ||
Set the layout algorithm to use to compute the position of the items. | ||
procedure Default_Layout_Algorithm (Canvas : access Interactive_Canvas_Record'Class; Graph : Glib.Graphs.Graph; Force : Boolean; Vertical_Layout : Boolean); | ||
The default algorithm used in the canvas. | ||
procedure Set_Auto_Layout (Canvas : access Interactive_Canvas_Record; Auto_Layout : Boolean); | ||
If Auto_Layout is true, then the items will be automatically layed out | ||
procedure Layout (Canvas : access Interactive_Canvas_Record; Force : Boolean := False; Vertical_Layout : Boolean := False); | ||
Recompute the layout of the canvas. | ||
Links | ||
procedure Configure (Link : access Canvas_Link_Record; Arrow : in Arrow_Type := End_Arrow; Descr : in Glib.UTF8_String := ""); | ||
Configure a link. | ||
function Get_Descr (Link : access Canvas_Link_Record) return Glib.UTF8_String; | ||
Return the description for the link, or "" if there is none
| ||
function Get_Arrow_Type (Link : access Canvas_Link_Record) return Arrow_Type; | ||
Return the location of the arrows on Link
| ||
procedure Set_Src_Pos (Link : access Canvas_Link_Record; X_Pos, Y_Pos : Glib.Gfloat := 0.5); | ||
Set the position of the link's attachment in its source item. | ||
procedure Set_Dest_Pos (Link : access Canvas_Link_Record; X_Pos, Y_Pos : Glib.Gfloat := 0.5); | ||
Same as Set_Src_Pos for the destination item
| ||
procedure Get_Src_Pos (Link : access Canvas_Link_Record; X, Y : out Glib.Gfloat); | ||
Return the attachment position of the link along its source item
| ||
procedure Get_Dest_Pos (Link : access Canvas_Link_Record; X, Y : out Glib.Gfloat); | ||
Return the attachment position of the link along its destination item
| ||
function Has_Link (Canvas : access Interactive_Canvas_Record; From, To : access Canvas_Item_Record'Class; Name : Glib.UTF8_String := "") return Boolean; | ||
Test whether there is a link from From to To, with the same name. | ||
procedure Add_Link (Canvas : access Interactive_Canvas_Record; Link : access Canvas_Link_Record'Class; Src : access Canvas_Item_Record'Class; Dest : access Canvas_Item_Record'Class; Arrow : in Arrow_Type := End_Arrow; Descr : in Glib.UTF8_String := ""); | ||
Add Link in the canvas. This connects the two items Src and Dest. | ||
procedure Remove_Link (Canvas : access Interactive_Canvas_Record; Link : access Canvas_Link_Record'Class); | ||
Remove a link from the canvas. | ||
procedure For_Each_Link (Canvas : access Interactive_Canvas_Record; Execute : Link_Processor; From, To : Canvas_Item := null); | ||
Execute an action on each of the links contained in the canvas. (From, To) can be used to limit what links are looked for.
??? Would be nicer to give direct access to the Graph iterators
| ||
procedure Destroy (Link : in out Canvas_Link_Record); | ||
Method called every time a link is destroyed. You should override this | ||
Drawing links | ||
Drawing of links can be controlled at several levels:
| ||
procedure Update_Links (Canvas : access Interactive_Canvas_Record; GC : Gdk.GC.Gdk_GC; Invert_Mode : Boolean; From_Selection : Boolean); | ||
Redraw all the links in the canvas, after the items have been laid out. | ||
procedure Draw_Link (Canvas : access Interactive_Canvas_Record'Class; Link : access Canvas_Link_Record; Invert_Mode : Boolean; GC : Gdk.GC.Gdk_GC; Edge_Number : Glib.Gint); | ||
Redraw the link on the canvas. The link should be drawn directly in Get_Window (Canvas). GC is a possible graphic context that could be used to draw the link. You shouldn't destroy it or modify its attributes. However, you can use any other graphic context specific to your application, for instance if you want to draw the link in various colors or shapes. The graphic context you use must be in Invert mode (see Gdk.GC.Set_Function) if and only if Invert_Mode is true, so that when items are moved on the canvas, the links properly follow the items they are attached to. This graphic context is only used to draw links, so you don't need to restore it on exit if your Draw_Link function always sets it at the beginning.
Edge_Number indicates the index of link in the list of links that join
the same source to the same destination. It should be used so that two
links do not overlap (for instance, the default is to draw the first
link straight, and the others as arcs).
| ||
procedure Clip_Line (Src : access Canvas_Item_Record; To_X : Glib.Gint; To_Y : Glib.Gint; X_Pos : Glib.Gfloat; Y_Pos : Glib.Gfloat; Side : out Item_Side; X_Out : out Glib.Gint; Y_Out : out Glib.Gint); | ||
Clip the line that goes from Src at pos (X_Pos, Y_Pos) to (To_X, To_Y) | ||
procedure Draw_Straight_Line (Link : access Canvas_Link_Record; Window : Gdk.Window.Gdk_Window; GC : Gdk.GC.Gdk_GC; Src_Side : Item_Side; X1, Y1 : Glib.Gint; Dest_Side : Item_Side; X2, Y2 : Glib.Gint); | ||
Draw a straight link between two points. This could be overriden if you | ||
Selection | ||
procedure Clear_Selection (Canvas : access Interactive_Canvas_Record); | ||
Clear the list of currently selected items.
| ||
procedure Add_To_Selection (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class); | ||
Add Item to the selection. This is only meaningful during a drag | ||
procedure Remove_From_Selection (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class); | ||
Remove Item from the selection. | ||
function Is_Selected (Canvas : access Interactive_Canvas_Record; Item : access Canvas_Item_Record'Class) return Boolean; | ||
Return True if the item is currently selected
| ||
function Start (Canvas : access Interactive_Canvas_Record) return Selection_Iterator; | ||
Return the first selected item
| ||
function Next (Iterator : Selection_Iterator) return Selection_Iterator; | ||
Move to the next selected item
| ||
function Get (Iterator : Selection_Iterator) return Canvas_Item; | ||
Return the current item, or null if there is no more selected item.
| ||
Items manipulation | ||
procedure Selected (Item : access Canvas_Item_Record; Canvas : access Interactive_Canvas_Record'Class; Is_Selected : Boolean); | ||
Called when the item is selected or unselected. | ||
function Point_In_Item (Item : access Canvas_Item_Record; X, Y : Glib.Gint) return Boolean; | ||
This function should return True if (X, Y) is inside the item. X and Y | ||
procedure Set_Screen_Size (Item : access Canvas_Item_Record; Width : Glib.Gint; Height : Glib.Gint); | ||
Set the size of bounding box for the item in world coordinates. | ||
procedure Draw (Item : access Canvas_Item_Record; Canvas : access Interactive_Canvas_Record'Class; GC : Gdk.GC.Gdk_GC; Xdest, Ydest : Glib.Gint); | ||
This subprogram, that must be overridden, should draw the item on | ||
procedure Destroy (Item : in out Canvas_Item_Record); | ||
Free the memory occupied by the item (not the item itself). You should | ||
procedure On_Button_Click (Item : access Canvas_Item_Record; Event : Gdk.Event.Gdk_Event_Button); | ||
Function called whenever the item was clicked on. | ||
function Get_Coord (Item : access Canvas_Item_Record) return Gdk.Rectangle.Gdk_Rectangle; | ||
Return the coordinates and size of the bounding box for item, in world | ||
procedure Set_Visibility (Item : access Canvas_Item_Record; Visible : Boolean); | ||
Set the visibility status of the item. An invisible item will not be | ||
function Is_Visible (Item : access Canvas_Item_Record) return Boolean; | ||
Return True if the item is currently visible.
| ||
function Is_From_Auto_Layout (Item : access Canvas_Item_Record) return Boolean; | ||
Return True if the current location of the item is the result from the | ||
Buffered items | ||
function Pixmap (Item : access Buffered_Item_Record) return Gdk.Pixmap.Gdk_Pixmap; | ||
Return the double-buffer. | ||
Signals | ||
procedure Set_Screen_Size (Item : access Buffered_Item_Record; Width, Height : Glib.Gint); | ||
See documentation from inherited subprogram
| ||
procedure Draw (Item : access Buffered_Item_Record; Canvas : access Interactive_Canvas_Record'Class; GC : Gdk.GC.Gdk_GC; Xdest, Ydest : Glib.Gint); | ||
Draw the item's double-buffer onto Dest.
| ||
procedure Destroy (Item : in out Buffered_Item_Record); | ||
Free the double-buffer allocated for the item
|
Example |
---|
-- The following example shows a possible Draw_Background procedure, -- that draws a background image on the canvas's background. It fully -- handles zooming and tiling of the image. Note that drawing a large -- image will dramatically slow down the performances. Background : Gdk.Pixbuf.Gdk_Pixbuf := ...; procedure Draw_Background (Canvas : access Image_Canvas_Record; Screen_Rect : Gdk.Rectangle.Gdk_Rectangle) is X_Left : constant Glib.Gint := Left_World_Coordinates (Canvas); Y_Top : constant Glib.Gint := Top_World_Coordinates (Canvas); X, Y, W, H, Ys : Gint; Xs : Gint := Screen_Rect.X; Bw : constant Gint := Get_Width (Background) * Gint (Get_Zoom (Canvas)) / 100; Bh : constant Gint := Get_Height (Background) * Gint (Get_Zoom (Canvas)) / 100; Scaled : Gdk_Pixbuf := Background; begin if Get_Zoom (Canvas) /= 100 then Scaled := Scale_Simple (Background, Bw, Bh); end if; while Xs < Screen_Rect.X + Screen_Rect.Width loop Ys := Screen_Rect.Y; X := (X_Left + Xs) mod Bw; W := Gint'Min (Screen_Rect.Width + Screen_Rect.X- Xs, Bw - X); while Ys < Screen_Rect.Y + Screen_Rect.Height loop Y := (Y_Top + Ys) mod Bh; H := Gint'Min (Screen_Rect.Height + Screen_Rect.Y - Ys, Bh - Y); Render_To_Drawable (Pixbuf => Scaled, Drawable => Get_Window (Canvas), Gc => Get_Black_GC (Get_Style (Canvas)), Src_X => X, Src_Y => Y, Dest_X => Xs, Dest_Y => Ys, Width => W, Height => H); Ys := Ys + H; end loop; Xs := Xs + W; end loop; if Get_Zoom (Canvas) /= 100 then Unref (Scaled); end if; end Draw_Background;