1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
|
.. _Object-oriented_features:
************************
Object-oriented features
************************
GtkAda has been designed from the beginning to provide a full object
oriented layer over gtk+. This means that features such as type
extension and dynamic dispatching are made available through the
standard Ada language.
This section will describe how things work, how you can extend existing
widgets, and even how to create your own widgets.
.. _General_description_of_the_tagged_types:
General description of the tagged types
=======================================
Why should I use object-oriented programming ?
----------------------------------------------
Every widget in the `Gtk.*` packages in GtkAda is a tagged type with a number
of primitive subprograms that are inherited by all of its children. Tagged
types in Ada make it possible to perform safe, automatic type conversions
without using explicit casts (such as is necessary when coding in C). It is
also possible for the compiler to verify whether or not these type conversions
are valid. Most errors are found at compile time, which leads to a safer and
more robust application.
As a further example, imagine a table that has been populated by some widgets.
It is possible to query for this table's children and operate on these widgets
without knowing details about their type, their creator, and so on--the tagged
objects that are returned contain all the information necessary. It becomes
possible to use dynamic dispatching without ever having to cast to a known
type.
Modifying a standard widget to draw itself differently or display different
data is easy using tagged types. Simply create a new type that extends the
current one (see the section :ref:`Using_tagged_types_to_extend_Gtk_widgets`
below.
Creating a new reusable widget from scratch is also possible. Create a new
tagged type and specify properties of the widget--such as how it is to draw
itself and how it should react to events. See the section
:ref:`Creating_new_widgets_in_Ada` below.
Object oriented programming through the use of Ada tagged types makes GtkAda a
very powerful, flexible, and safe tool for designing graphical interfaces.
Type conversions from C to Ada widgets
--------------------------------------
There are three kinds of widgets that you can use with GtkAda:
* *Ada widgets*:
These are widgets that are written directly in Ada, using the object
oriented features of GtkAda
* *Standard widgets*:
These are the widgets that are part of the standard gtk+ and GtkAda
distributions. This include all the basic widgets you need to build
advanced interfaces.
* *third party C widgets*
These are widgets that were created in C, and for which you (or someone else)
created an Ada binding. This is most probably the kind of widgets you will
have if you want to use third party widgets.
GtkAda will always be able to find and/or create a valid tagged type in the
first two cases, no matter if you explicitly created the widget or if it was
created automatically by gtk+. For instance, if you created a widget in Ada,
put it in a table, and later on extracted it from the table, then you will
still have the same widget.
In the third case (third party C widgets), GtkAda is not, by default, able to
create the corresponding Ada type.
The case of third party C widgets is a little bit trickier. Since GtkAda does
not know anything about them when it is built, it can't magically convert the C
widgets to Ada widgets. This is your job to teach GtkAda how to do the
conversion.
We thus provide a 'hook' function which you need to modify. This function is
defined in the package **Glib.Type_Conversion**. This function takes a string
with the name of the C widget (ex/ "GtkButton"), and should return a newly
allocated pointer. If you don't know this type either, simply return **null**.
.. _Using_tagged_types_to_extend_Gtk_widgets:
Using tagged types to extend Gtk widgets
========================================
.. highlight:: ada
With this toolkit, it's possible to associate your own data with existing
widgets simply by creating new types. This section will show you a simple
example, but you should rather read the source code in the :file:`testgtk/`
directory where we used this feature instead of using `user_data` as is used in
the C version:::
type My_Button_Record is new Gtk_Button_Record with record
-- whatever data you want to associate with your button
end record;
type My_Button is access all My_Button_Record'Class;
With the above statements, your new type is defined. Every function
available for `Gtk_Button` is also available for `My_Button`.
Of course, as with every tagged type in Ada, you can create your own
primitive functions with the following prototype::
procedure My_Primitive_Func (Myb : access My_Button_Record);
To instanciate an object of type `My_Button` in your application, do
the following::
declare
Myb : My_Button;
begin
Myb := new My_Button_Record;
Initialize (Myb); -- from Gtk.Button
end;
The first line creates the Ada type, whereas the `Initialize` call
actually creates the C widget and associates it with the Ada type.
.. _Creating_new_widgets_in_Ada:
Creating new widgets in Ada
===========================
With GtkAda, you can create widgets directly in Ada. These new
widgets can be used directly, as if they were part of gtk itself.
Creating new widgets is a way to create reuseable components. You can apply to
them the same functions as you would for any other widget, such as `Show`,
`Hide`, and so on.
This section will explain how to create two types of widgets: composite widgets
and widgets created from scratch. Two examples are provided with GtkAda, in the
directories :file:`examples/composite_widget` and :file:`examples/base_widget`.
Please also refer to the gtk+ tutorial, which describes the basic mechanisms
that you need to know to create a widget.
.. _Creating_composite_widgets:
Creating composite widgets
--------------------------
A composite widget is a widget that does not do much by itself. Rather, this is
a collection of subwidgets grouped into a more general entity. For instance,
among the standard widgets, `Gtk_File_Selection` and `Gtk_Font_Selection`
belong to this category.
The good news is that there is nothing special to know. Just create a new
tagged type, extending one of the standard widgets (or even another of your own
widgets), provide a `Gtk_New` function that allocates memory for this widget,
and call the `Initialize` function that does the actual creation of the widget
and the subwidgets. There is only one thing to do: `Initialize` should call
the parent class's `Initialize` function, to create the underlying C widget.
The example directory :file:`examples/composite_widget` reimplements the
`Gtk_Dialog` widget as written in C by the creators of gtk+.
.. _Creating_widgets_from_scratch:
Creating widgets from scratch
-----------------------------
Creating a working widget from scratch requires a certain level of familiary
with the GtkAda signal mechanism and entails much work with low level signals.
This is therefore not an activity recommended for novice GtkAda programmers.
Creating a widget from scratch is what you want to do if your widget should be
drawn in a special way, should create and emit new signals, or otherwise
perform differently than pre-existing widgets. The example we give in
:file:`examples/base_widget` is a small target on which the user can click, and
that sends one of two signals: "bullseye" or "missed", depending on where the
user has clicked.
See also the example in :file:`examples/tutorial/gtkdial` for a more complex
widget, that implements a gauge where the user can move the arrow to select
a new value.
Since we are creating a totally new widget from scratch, with potentially
its own signals, we need to do slightly more work. In particular, we need to
provide a function ``Get_Type`` similar to what all the predefined widgets
provide::
with Glib.Properties.Creation; use Glib.Properties.Creation;
with Glib.Objects; use Glib.Objects;
with Gtk.Scrollable;
with System;
package body My_Widgets is
type My_Widget_Record is new Gtk_Button_Record with record
...
end record;
type My_Widget is access all My_Widget_Record'Class;
Klass : aliased Ada_GObject_Class := Uninitialized_Class;
PROP_H_ADJ : constant Property_Id := 1;
PROP_V_ADJ : constant Property_Id := 2;
-- internal identifier for our widget properties
procedure Class_Init (Self : GObject_Class);
pragma Convention (C, Class_Init);
procedure Class_Init (Self : GObject_Class) is
begin
-- Set properties handler
Set_Properties_Handlers (Self, Prop_Set'Access, Prop_Get'Access);
-- Override inherited properties
Override_Property (Self, PROP_H_ADJ, "hadjustment");
Override_Property (Self, PROP_V_ADJ, "vadjustment");
-- Install some custom style properties
Install_Style_Property (Self, Gnew_Int (...));
-- Override some the inherited methods (how to draw the widget)
Set_Default_Draw_Handler (Self, On_Draw'Access);
-- Override the primitives to compute the widget size
Set_Default_Get_Preferred_Width (Self, ...);
end Class_Init;
function Get_Type return GType is
Info : access GInterface_Info;
begin
if Initialize_Class_Record
(Ancestor => Gtk.Button.Get_Type,
Class_Record => Klass'Access,
Type_Name => "My_Widget",
Class_Init => Class_Init)
begin
-- Add interfaces if needed
Info := new GInterface_Info'(null, null, System.Address);
Add_Interface (Klass, Gtk.Scrollable.Get_Type, Info);
end if;
return Klass.The_Type;
end Get_Type;
end My_Widgets;
You should also create the usual functions ``Gtk_New`` and
``Initialize``::
procedure Gtk_New (Self : out My_Widget) is
begin
Self := new My_Widget_Record; -- create the Ada wrapper
Initialize (Self);
end Gtk_New;
procedure Initialize (Self : not null access My_Widget_Record'Class) is
begin
G_New (Self, Get_Type); -- allocate the C widget, unless done
-- Initialize parent fields.
Gtk.Button.Initialize (Self);
-- Initialization of the Ada types
Self.Field1 := ...;
end Initialize;
In the above example, the new part is the ``Get_Type`` subprogram. It takes
three or four arguments:
* `Ancestor`
This is the GType for the ancestor that is being extended.
* `Signals`
This is an array of string access containing the name of the signals
you want to create. For instance, you could create Signals with::
Signals : Gtkada.Types.Chars_Ptr_Array := "bullseye" + "missed";
This will create two signals, named "bullseye" and "missed", whose callbacks'
arguments can be specified with the fourth parameter.
* `Class_Record`
Every widget in C is associated with three records:
- An instance of `GType`, which is a unique identifier (integer) for all
the class of widgets defined in the framework. This description also
contains the name of the class, its parent type, the list of
interfaces it inherits, and all the signals is defines.
These `GType` are often created early on when an application is launched,
and provide the basic introspection capabilities in a gtk+ application.
In Ada, this type is created by the function `Get_Type` in the example
above (which is why we need to add the interface in that function).
- An instance of `GObject_Class`, which contains implementation details
for the class, defines the default signal handlers (how to draw a
widget of the class, how to handle size negociation,...), and defines
any number of properties that can be configured on the widget
(properties are a generic interface to access the components of a
composite widget, as well as some of its behavior -- they can be
modified through introspection for instance in a GUI builder).
Such a type is created automatically by gtk+ just before it creates
the first instance of that widget type. It will then immediately call
the `Class_Init` function that might have been passed to
`Glib.Object.Initialize_Class_Record`. At that point, you can add your
own new properties, or override the default signal handlers to redirect
them to your own implementation.
- A class instance record; there is one such record for each widget of
that type.
In GtkAda, the 'instance record' is simply your tagged type and its
fields. It is created when you call any of the `Gtk_New` functions.
* `Parameters`
This fourth argument is in fact optional, and is used to specify which
kind of parameters each new signal is expecting.
By default (ie if you don't give any value for this parameter), all the
signals won't expect any argument, except of course a possible user_data.
However, you can decide for instance that the first signal ("bullseye") should
in fact take a second argument (say a Gint), and that "missed" will take
two parameters (two Gints).
`Parameters` should thus contain a value of::
(1 => (1 => Gtk_Type_Int, 2 => Gtk_Type_None),
2 => (1 => Gtk_Type_Int, 2 => Gtk_Type_Int));
Due to the way arrays are handled in Ada, each component must have the same
number of signals. However, if you specify a type of `Gtk_Type_None`, this
will in fact be considered as no argument. Thus, the first signal above has
only one parameter.
Note also that to be able to emit a signal such a the second one, ie with
multiple arguments, you will have to extend the packages defined in
Gtk.Handlers. By default, the provided packages can only emit up to one
argument (and only for a few specific types). Creating your own
`Emit_By_Name` subprograms should not be hard if you look at what is done
in :file:`gtk-marshallers.adb`. Basically, something like::
procedure Emit_With_Two_Ints
(Object : access Widget_Type'Class;
Name : String;
Arg1 : Gint;
Arg2 : Gint);
pragma Import (C, Emit_With_Two_Ints,
"gtk_signal_emit_by_name");
Emit_With_Two_Ints (Gtk.Get_Object (Your_Widget),
"missed" & ASCII.NUL, 1, 2);
will emit the "missed" signal with the two parameters 1 and 2.
|