John B. Matthews, M.D.

Return home.


Download the Ada source for the demo.
Visit the GtkAda home page.
Visit the MacPorts home page.
Visit Apple's Open Source support page.
Visit the GNAT for Mac OS X home page.
Show me how to use shared libraries.
Show me some code!

A GtkAda animation demo.

Here's a simple demonstration of buffered animation using GtkAda. It builds on either Linux or Mac OS X using the included Makefile.

The original GNAT port to Mac OS featured a demo of Lady Ada, Countess of Lovelace, bouncing around in a window. I still remember sitting at a trade show in front of a screen full of bouncing ladies, while passing developers stared enviously. I couldn't resist resurrecting it. Here is a static picture of the demo:

lady

To build the demo on Mac OS X, you need several components. If you already have X11, Gtk+, GNAT and GtkAda installed, skip to the Makefile, below:

1. Apple's X11. This program lets you run X11 applications on the Mac desktop. It's included with Xcode on Mac OS X 10.5 "Leopard". It's located in the "Optional Installs.mpkg" package on the Mac OS X 10.4 "Tiger" DVD. It's on disk 3 of the Mac OS X 10.3 "Jaguar" CD set. Don't forget to install the X11 SDK, too. It is not included by default on earlier systems, and it is required by everything else.

2. Gtk+ 2.x. Under Mac OS X 10.5, there are several approaches: [*] Note: the prebuilt binary includes an earlier version of GtkAda, but you can checkout the newest version and build it against the prebuilt Gtk+.

Don't change the default MacPorts installation prefix "/opt/local" to something else. The glade port, a graphical Gtk interface development tool, is also worthwhile.

3. GNAT. This is the Mac OS X port of GNAT, the GNU Ada compiler. If you have installed GNAT prior to installing Gtk, you may need to revert to the stock Apple gcc compiler to build certain dependencies, e.g. gettext. With previous versions, you had to change the softlink /usr/bin/gcc to point to Apple's gcc, and put it back when you were done. More recent versions allow you to switch compilers by altering the PATH environment variable. A simple script to manage the PATH is shown here.

4. GtkAda 2.x. GtkAda is a full-featured, object-oriented binding to Gtk. See the Installation notes below.

Installation notes:

Under Mac OS X 10.5, use MacPorts:
[switch to Apple gcc]
$ port install gtk2
[switch to GNAT 4.3]
$ svn checkout http://svn.eu.adacore.com/anonsvn/Dev/trunk/GtkAda
$ cd GtkAda/
$ ./configure --prefix=/opt/local --with-GL=GL --with-GL-prefix=/usr/X11R6
$ make
$ make install
When building manually, you have to assemble the required source archives; a typical set of dependencies are shown; see gtkada/docs/gtk-build.txt for additional details: Lady: Makefile.

Once you've installed GtkAda, it's time to build the application. A typical Makfile is shown. By default, it shows the build targets available. Note in particular that the shared library path defaults to ada-4.3, if not set. You'll have to alter the relevant variables for a prior version of GNAT. This script also shows how to use install_name_tool to change the path to a shared library after linking, as suggested by Jim Hopper. The static binary is nearly twice the size of the shared.
# Lady Makefile
# 
# Make shared, static or debug targets.
# 
# Author: John B. Matthews
# Lady Makefile
# John B. Matthews
# Created:  15-Oct-2007
# Modified: 18-Oct-2007 Isolate Darwin specific commands.
# Modified: 10-Apr-2009 New shared library path.
# Modified: 23-Sep-2010 Use gtkada-config options; obj directory.
# Modified: 25-Jul-2011 Upddate debug target.
# Distribution: GPL 

OS := $(shell uname)
OBJ = obj
TARGET = main
RENAME = install_name_tool
GNATMAKE = gnatmake -D $(OBJ)
CARGS = -cargs -O3 -gnatp -gnatwu
BARGS = -bargs
LARGS = -largs
SHARED = libgnat-4.3.dylib
ADA_LIB ?= /usr/local/ada-4.3/lib
.PHONEY: clean cleaner dist-clean tar

all:
	@echo ""
	@echo "Lady build targets:"
	@echo ""
	@echo "    shared     Use the shared Ada libraries."
	@echo "    static     Link the Ada libraries statically."
	@echo "    debug      Enable debugging."
	@echo ""
	@echo "Support targets:"
	@echo ""
	@echo "    clean      Remove *.ali *.o b~.*"
	@echo "    cleaner    Remove target, too."
	@echo "    dist-clean Remove Xcode build directory, too."
	@echo "    tar        Build a clean distribution tarball."
	@echo ""

shared: $(OBJ)
shared: INCLUDE = $(shell gtkada-config --cflags)
shared: BARGS += -shared
shared: LARGS += $(shell gtkada-config --libs)
shared: LARGS += -dead_strip
shared: *.ad[sb]
	@echo "building with shared libraries:"
	$(GNATMAKE) $(TARGET) $(INCLUDE) $(CARGS) $(BARGS) $(LARGS)
ifeq ($(OS), Darwin)
#	$(RENAME) -change $(SHARED) $(ADA_LIB)/$(SHARED) $(TARGET)
endif

static: $(OBJ)
static: INCLUDE = $(shell gtkada-config --static --cflags)
static: BARGS += -static
static: LARGS += $(shell gtkada-config --static --libs)
static: LARGS += -dead_strip
static: *.ad[sb]
	$(GNATMAKE) $(TARGET) $(INCLUDE) $(CARGS) $(BARGS) $(LARGS)

debug: $(OBJ)
debug: INCLUDE = $(shell gtkada-config --static --cflags)
debug: BARGS += -static
debug: LARGS += $(shell gtkada-config --static --libs)
debug: *.ad[sb]
	$(GNATMAKE) -g $(TARGET) $(INCLUDE) $(LARGS)

$(OBJ):
	mkdir $(OBJ)

clean:
	${RM} $(OBJ)/* b~*

cleaner: clean
	${RM} $(TARGET)

dist-clean: cleaner
	${RM} -r $(OBJ) 

tar: dist-clean
ifeq ($(OS), Darwin)
	(export COPYFILE_DISABLE=true; \
	tar --exclude '.svn' -zcvf ~/Sites/HomePage/gtk/lady.tgz ../lady)
else
	tar --exclude '.svn' -zcvf ~/archive/lady.tgz ../lady
endif
Lady: Core source code.

------------------------------------------------------------------
--|
--| Lady: A Gtk Animation Demo
--|
--| Author: John B. Matthews, Wright State University 
--| Last Modified: 17-Jul-2005
--|
------------------------------------------------------------------
with Ada.Text_IO;
with Double_Buffer;
with Gdk.Color;
with Gdk.Drawable;
with Gdk.GC;
with Gdk.Pixbuf;
with Gdk.Rectangle;
with Glib;
   use Glib; use type Glib.Gint;
with Glib.Error;
   use type Glib.Error.GError;
with Gtk.Box;
with Gtk.Button;
with Gtk.Drawing_Area;
with Gtk.Enums;
with Gtk.Handlers;
   pragma Elaborate_All (Gtk.Handlers);
with Gtk.Hbutton_Box;
with Gtk.Label;
with Gtk.Main;
   pragma Elaborate_All (Gtk.Main);
with Gtk.Style;
with Gtk.Widget;
with Gtk.Window;
with Pango.Font;

package body Lady is

   White_Gc : Gdk.GC.Gdk_GC;
   Black_Gc : Gdk.GC.Gdk_GC;

   Image_Width  : Gint;
   Image_Height : Gint;
   Image : Gdk.Pixbuf.Gdk_Pixbuf;
   X_Pos : Gint := 13; -- current position
   Y_Pos : Gint := 17;
   Vmin  : Gint := 2;  -- min. velocity
   Vmax  : Gint := 20; -- max. velocity
   Dh    : Gint := 4;  -- horizontal change
   Dv    : Gint := 4;  -- vertical change

   package Void_Cb is new
      Gtk.Handlers.Callback (Gtk.Window.Gtk_Window_Record);
   package Button_Cb is new
      Gtk.Handlers.Callback (Gtk.Button.Gtk_Button_Record);
   package Timeout is new
      Gtk.Main.Timeout (Gtk.Drawing_Area.Gtk_Drawing_Area);
   
   procedure Inset_Rect (R : in out Gdk.Rectangle.Gdk_Rectangle;
      Dh, Dv : Gint) is
   begin
      R.X := Gint'Min(R.X - (Dh / 2), R.X + R.Width / 2);
      R.Y := Gint'Min(R.Y - (Dv / 2), R.Y + R.Height / 2);
      R.Width := Gint'Max(R.Width + Dh, 0);
      R.Height := Gint'Max(R.Height + Dv, 0);
   end Inset_Rect;

   ------------------
   -- Draw_Content --
   ------------------

   procedure Draw_Content (Pixmap : Gdk.Gdk_Drawable) is
      Width, Height : Gint;
      Rect : Gdk.Rectangle.Gdk_Rectangle;
   begin
      -- Erase the old
      Gdk.Drawable.Get_Size(Pixmap, Width, Height);
      Gdk.Drawable.Draw_Rectangle(Pixmap, White_Gc, True, 0, 0, Width, Height);

      -- Draw the new
      Rect := (0, 0, Width, Height);
      for i in Gint'(2) .. 10 loop
         Inset_Rect(Rect, -(i * 10), -(i * 10));
         Gdk.Drawable.Draw_Rectangle(Pixmap, Black_Gc, False,
            Rect.X, Rect.Y, Rect.Width, Rect.Height);
      end loop;

      Gdk.Pixbuf.Render_To_Drawable (
         Pixbuf   => Image,
         Drawable => Pixmap,
         Gc       => Black_Gc,
         Src_X    => 0,
         Src_Y    => 0,
         Dest_X   => X_Pos,
         Dest_Y   => Y_Pos,
         Width    => Image_Width,
         Height   => Image_Height);

      Gdk.Drawable.Draw_Rectangle (Pixmap, Black_Gc, False,
         X_Pos, Y_Pos, Image_Width, Image_Height);
      if X_Pos < 0 then Dh := -Dh; end if;
      if Y_Pos < 0 then Dv := -Dv; end if;
      if X_Pos > Width  - Image_Width  then
         X_Pos := Width - Image_Width;
         Dh := -Dh;
      end if;
      if Y_Pos > Height - Image_Height then
         Y_Pos := Height - Image_Height;
         Dv := -Dv;
      end if;
      X_Pos := X_Pos + Dh;
      Y_Pos := Y_Pos + Dv;

   end Draw_Content;

   -----------------
   -- Draw_Buffer --
   -----------------

   function Draw_Buffer (
      Area : Gtk.Drawing_Area.Gtk_Drawing_Area) return Boolean is
      Buffer : Double_Buffer.Gtk_Double_Buffer :=
         Double_Buffer.Gtk_Double_Buffer (Area);
   begin
      Draw_Content (Double_Buffer.Get_Pixmap (Buffer));
      Double_Buffer.Draw (Buffer);
      return True;
   end Draw_Buffer;
   
   ----------
   -- Sign --
   ----------
   
   function Sign (n :  Gint) return Gint is
   begin
      if n < 0 then return -1;
      else return 1;
      end if;
   end Sign;
   
   ------------
   -- Slower --
   ------------
   
   procedure Slower (
      Button : access Gtk.Button.Gtk_Button_Record'Class) is
      pragma Warnings (Off, Button);
   begin
      Dh := Sign(Dh) * Gint'Max(Abs(Dh) - 1, Vmin);
      Dv := Sign(Dv) * Gint'Max(Abs(Dv) - 1, Vmin);
   end Slower;

   ------------
   -- Faster --
   ------------
   
   procedure Faster (
      Button : access Gtk.Button.Gtk_Button_Record'Class) is
      pragma Warnings (Off, Button);
   begin
      Dh := Sign(Dh) * Gint'Min(Abs(Dh) + 1, Vmax);
      Dv := Sign(Dv) * Gint'Min(Abs(Dv) + 1, Vmax);
   end Faster;

   ----------
   -- Quit --
   ----------

   procedure Quit (Win : access Gtk.Window.Gtk_Window_Record'Class) is
      pragma Warnings (Off, Win);
   begin
      Gtk.Main.Gtk_Exit (0);
   end Quit;

   ----------
   -- Init --
   ----------

   procedure Init is
      Win    : Gtk.Window.Gtk_Window;
      Buffer : Double_Buffer.Gtk_Double_Buffer;
      Hbox   : Gtk.Hbutton_Box.Gtk_HButton_Box;
      Vbox   : Gtk.Box.Gtk_Box;
      Label  : Gtk.Label.Gtk_Label;
      Style  : Gtk.Style.Gtk_Style;
      Button : Gtk.Button.Gtk_Button;
      Id     : Gtk.Main.Timeout_Handler_Id;
      Error  : Glib.Error.GError;


   begin

      -- Double buffer demo
      Gtk.Window.Gtk_New (Win, Gtk.Enums.Window_Toplevel);
      Gtk.Window.Set_Title (Win, "GtkAda Animation");
      Void_Cb.Connect (Win, "destroy", Void_Cb.To_Marshaller (Quit'Access));

      -- A vertical box with three rows
      Gtk.Box.Gtk_New_Vbox (Vbox, Homogeneous => False, Spacing => 5);
      
      -- Row 1: a label with style
      Gtk.Label.Gtk_New (Label, "Lady Ada Lovelace");
      Style := Gtk.Style.Copy (Gtk.Window.Get_Style (Win));
      Gtk.Style.Set_Font_Description (Style,
         Pango.Font.From_String ("Helvetica Bold 14"));
      Gtk.Label.Set_Style (Label, Style);
      
      -- Row 2: a double buffered drawing area
      Double_Buffer.Gtk_New (Buffer);
      Double_Buffer.Set_USize (Buffer, 450, 325);
      Double_Buffer.Set_Back_Store (Buffer, True);

      -- Row 3: A horizontal row of buttons
      Gtk.HButton_Box.Gtk_New (Hbox);
      Gtk.HButton_Box.Set_Border_Width (Hbox, 10);
      Gtk.HButton_Box.Set_Spacing (Hbox, 10);
      Gtk.HButton_Box.Set_Layout (Hbox, Gtk.Enums.Buttonbox_Spread);
      Gtk.Button.Gtk_New (Button, "Slower");
      Gtk.HButton_Box.Pack_Start (HBox, Button);
      Button_Cb.Object_Connect (Button, "clicked",
         Button_Cb.To_Marshaller (Slower'Access), Button);
      Gtk.Button.Gtk_New (Button, "Faster");
      Gtk.HButton_Box.Pack_Start (HBox, Button);
      Button_Cb.Object_Connect (Button, "clicked",
         Button_Cb.To_Marshaller (Faster'Access), Button);

      -- Pack them in
      Gtk.Box.Pack_Start (Vbox, Label, Expand => False);
      Gtk.Box.Pack_Start (Vbox, Buffer);
      Gtk.Box.Pack_Start (Vbox, HBox, Expand => False);
      Gtk.Window.Add (Win, Vbox);
      Gtk.Window.Show_All (Win);

      -- The window needs to be created before creating the GCs
      Gdk.GC.Gdk_New (White_Gc, Double_Buffer.Get_Window (Buffer));
      Gdk.GC.Set_Foreground
         (White_Gc, Gdk.Color.White (Gtk.Widget.Get_Default_Colormap));
      Gdk.GC.Gdk_New (Black_Gc, Double_Buffer.Get_Window (Buffer));
      Gdk.GC.Set_Foreground
         (Black_Gc, Gdk.Color.Black (Gtk.Widget.Get_Default_Colormap));

      -- Load the image
      Gdk.Pixbuf.Gdk_New_From_File (Image, "lady.png", Error);
      if Error = null then
         Image_Width  := Gdk.Pixbuf.Get_Width (Image);
         Image_Height := Gdk.Pixbuf.Get_Height (Image);
      else
         Ada.Text_IO.Put_Line("Error: " & Glib.Error.Get_Message(Error));
         Glib.Error.Error_Free(Error);
         Image_Width  := 45; Image_Height := 60;
         Image := Gdk.Pixbuf.Gdk_New(Bits_Per_Sample => 24,
            Width => Image_Width, Height => Image_Height);
         Gdk.Pixbuf.Fill(Image, 16#0000FF00#);
      end if;

      -- Draw a frame every 40 ms (25 frames/sec)
      Id := Timeout.Add (40, Draw_Buffer'Access,
         Gtk.Drawing_Area.Gtk_Drawing_Area (Buffer));
        
   end Init;

end Lady;

ada: a script to manage the PATH.

#!/bin/sh
#
# Manage Ada in $PATH
#

ADA_BIN=${ADA_BIN:-/usr/local/ada-4.3/bin}

function checkPath() {
  echo ${PATH} | grep -q -s $1
}

function removePath() {
  newPath=`echo $PATH | sed -e "s;$1;;" \
    -e "s/^://" -e "s/::/:/" -e "s/:$//"`
}

function status() {
  echo `gcc --version | head -n 1`
  if checkPath ${ADA_BIN} ; then
    echo "${ADA_BIN} \033[1min\033[0m \$PATH"
    removePath ${ADA_BIN}
    echo "export PATH=${newPath} ; $0"
  else
    echo "${ADA_BIN} \033[1mnot in\033[0m \$PATH"
    echo "export PATH=${ADA_BIN}:${PATH} ; $0"
  fi
}

status
exit $?
Copyright 1984, 2004, 2009 John B. Matthews
Distribution permitted under the terms of the GPL: http://www.gnu.org/copyleft/gpl.html.
Last updated 23-Sep-2010
Return home.