Difference between revisions of "Tutorial:Window"

From Freepascal Amiga wiki
Jump to navigation Jump to search
m (dumb typo removed that messed up layout)
(→‎Tags, Taglists: added first program with close event)
Line 28: Line 28:
 
|}
 
|}
  
As can been seen from the table above, there are different types (integer, string and boolean) that can be assigned as a Tag-value although the Tag's property name itself dictates which type that should actually be. But, in order to keep things simple we should be able to transfer all these different types using the same interface. There is a very simple but rather clever way that can solve this problem for us and that is by declaring tag values to be of type "PtrUInt" (or "NativeUInt", which is the same). This type is an unsigned integer value which is guaranteed to have enough room in order to even store a pointer. This way we are able to supply all our Tag-Values by simply casting them to PtrUInt. For example strings can be converted this way as a PChar (zero terminated C like strings) and would then be treated as any other common Pointer. Instead of casting (which is a rather tedious thing to do) there is also another solution available and that is offered by unit Utility, that offers a special set of wrapper functions named AsTag(). AsTag() will automatically convert all possible types into a PtrUint. Another notable feature of such tag-lists is that the list itself always needs to end with a special closing tag named TAG_END. That let every other function that work with tag-lists know where the actual end of the list is located. The TAG_END-Tag does not really require an actual tag-value. Since its common for a picture to be able to tell more than a thousand words, adding a list of tag-value pairs to a list looks something like:
+
As can been seen from the table above, there are different types (integer, string and boolean) that can be assigned as a Tag-value although the Tag's property name itself dictates which type that should actually be. But, in order to keep things simple we should be able to transfer all these different types using the same interface. There is a very simple but rather clever way that can solve this problem for us and that is by declaring tag values to be of type "PtrUInt" (or "NativeUInt", which is the same). This type is an unsigned integer value which is guaranteed to have enough room in order to even store a pointer. This way we are able to supply all our Tag-Values by simply casting them to PtrUInt. For example strings can be converted this way as a PChar (zero terminated C like strings) and would then be treated as any other common Pointer. Instead of casting (which is a rather tedious thing to do) there is also another solution available and that is offered by unit Utility, that offers a special set of wrapper functions named AsTag(). AsTag() will automatically convert all possible types into a PtrUint. You can find them in the utility unit. Another notable feature of such tag-lists is that the list itself always needs to end with a special closing tag named TAG_END. That let every other function that work with tag-lists know where the actual end of the list is located. The TAG_END-Tag does not really require an actual tag-value. Since its common for a picture to be able to tell more than a thousand words, adding a list of tag-value pairs to a list looks something like:
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 50: Line 50:
  
 
Now, with all this gained knowledge let's try to open a window om our workbench. The Amiga Api offers quite some different functions that open a window:<br/>
 
Now, with all this gained knowledge let's try to open a window om our workbench. The Amiga Api offers quite some different functions that open a window:<br/>
* OpenWindow() [http://www.alb42.de/fpc-docu/intuition/openwindow.html] - This is an older declaration/interface that was used before taglists where invented but is still available for compatibility reasons.
+
* [http://www.alb42.de/fpc-docu/intuition/openwindow.html OpenWindow()] - This is an older declaration/interface that was used before taglists where invented but is still available for compatibility reasons.
* OpenWindowTagList() [http://www.alb42.de/fpc-docu/intuition/openwindowtaglist.html] - This function requires a link to a tag-list for the second parameter. You create the tag-list before making the actual call to the function. This is very convenient in case you wish to manipulate the tag-list beforehand or programmaticaly.
+
* [http://www.alb42.de/fpc-docu/intuition/openwindowtaglist.html OpenWindowTagList()] - This function requires a link to a tag-list for the second parameter. You create the tag-list before making the actual call to the function. This is very convenient in case you wish to manipulate the tag-list beforehand or programmaticaly.
* OpenWindowTags() [http://www.alb42.de/fpc-docu/intuition/openwindowtags.html] - This function has a <code>array of PtrUInt</code> as second parameter which can be used to directly supply a list of tags and corresponding tag-values to the function.
+
* [http://www.alb42.de/fpc-docu/intuition/openwindowtags.html OpenWindowTags()] - This function has a <code>array of PtrUInt</code> as second parameter which can be used to directly supply a list of tags and corresponding tag-values to the function.
 +
 
 +
For example a simple way to create a window (with the given parameter) will be:
 +
 
 +
<source lang="Pascal">
 +
  OpenWindowTags(nil,
 +
  [
 +
    WA_Width, AsTag(300),
 +
    WA_Height, AsTag(200),
 +
    WA_Title, AsTag('My Window'),
 +
    WA_DepthGadget, AsTag(True),
 +
    WA_SizeGadget, AsTag(True),
 +
    TAG_END 
 +
  ])
 +
</source>
 +
 
 +
Of course we need to close the Window again, with [http://www.alb42.de/fpc-docu/intuition/closewindow.html CloseWindow()] which needs the pointer to the window we created. This is returned by the OpenWindow call and has the type [http://www.alb42.de/fpc-docu/intuition/pwindow.html PWindow] so we need to put that into a variable.
 +
 
 +
 
 +
<source lang="Pascal">
 +
program window;
 +
uses
 +
  utility, intuition;
 +
var
 +
  win: PWindow;
 +
begin
 +
  win := OpenWindowTags(nil,
 +
  [
 +
    WA_Width, AsTag(300),
 +
    WA_Height, AsTag(200),
 +
    WA_Title, AsTag('My Window'),
 +
    WA_DepthGadget, AsTag(True),
 +
    WA_SizeGadget, AsTag(True),
 +
    TAG_END 
 +
  ]);
 +
  CloseWindow(win);
 +
end.
 +
</source>
 +
 
 +
This program will open a window and close it directly again. We can introduce a <code>Sleep(1000)</code> to at least see the window. Next step is to keep the window open until the user pressed the close gadget, as you are used to it. For that we have to react to the event of clicking the gadget.
 +
 
 +
=== Events ===
 +
currently the window does not have a close gadget, we need to activate that on the openwindow call with this flag: <code>WA_Flags, WFLG_CloseGadget</code>. Flags are parameter for the window. If you need several flags at once, just or them together.
 +
To have the close gadget is not enough we also have to activate the event, we supply an additional tag <code>OpenWindow()</code> call: <code>WA_IDCMP, IDCMP_CLOSEWINDOW</code> which tells the system that we want to be notified when the window gets closed. To get this event message we have to check the message port of the window. The message port is something like a post box where events about that window arrive. You can use the function [http://www.alb42.de/fpc-docu/exec/getmsg.html GetMsg()] from the exec unit. Now we need the message port of the window which is a field of the window we created called <code>win^.UserPort</code>. First we have to wait until a message appears using [http://www.alb42.de/fpc-docu/exec/waitport.html WaitPort()], it blocks the execution until a message appears in the message port and gives other tasks a chance to do something. Which is much better than asking the port as fast as possible until a message appears. After we get the message we can inspect the contents of it, if we finished with this message we reply to the sender that we processed the message an we do not need it anymore. The call to do that is [http://www.alb42.de/fpc-docu/exec/replymsg.html ReplyMsg()]. Do not access the message after this call usually the sender of the message will free the memory of it. The usual flow will be:
 +
<source lang="Pascal">
 +
repeat
 +
  WaitPort(Win^.UserPort);
 +
  Msg := GetMsg(Win^.UserPort);
 +
  // Process Msg
 +
  ReplyMsg(Msg);
 +
until done;
 +
</source>
 +
Because we only called for a single message if a message arrive it will be our message, but because we later want to react on more messages we have to process the message first. Because that message is send to window by intuition it's type is not just [http://www.alb42.de/fpc-docu/exec/pmessage.html PMessage] but [http://www.alb42.de/fpc-docu/intuition/pintuimessage.html PIntuiMessage] which has the field <code>Msg^.IClass</code> which defines the type of message received. If that field is <code>IDCMP_CLOSEWINDOW</code> that message is a close window event. putting all together it looks like that:
 +
 
 +
<source lang="Pascal">
 +
program window;
 +
uses
 +
  exec, sysutils, utility, intuition;
 +
var
 +
  win: PWindow;
 +
  done: Boolean = False;
 +
  Msg: PMessage;
 +
begin
 +
  win := OpenWindowTags(nil,
 +
  [
 +
    WA_Width, AsTag(300),
 +
    WA_Height, AsTag(200),
 +
    WA_Title, AsTag('My Window'),
 +
    WA_DepthGadget, AsTag(True),
 +
    WA_SizeGadget, AsTag(True),
 +
    WA_Flags, WFLG_CloseGadget,
 +
    WA_IDCMP, IDCMP_CLOSEWINDOW,
 +
    TAG_END 
 +
  ]);
 +
  repeat
 +
    WaitPort(Win^.UserPort);
 +
    Msg := GetMsg(Win^.UserPort);
 +
    case PIntuiMessage(Msg)^.IClass of
 +
      IDCMP_CloseWindow: Done := True;
 +
    end;
 +
    ReplyMsg(Msg);
 +
  until done;
 +
  CloseWindow(win);
 +
end.
 +
</source>

Revision as of 21:19, 30 December 2018

About Windows

When Amiga was released in 1985 the availability of two interfaces to interact with the computer was quite a novel feature. Amiga's can be operated via command line interface and graphical user interface. For the latter free movable windows are very important. In this tutorial we will open a window using AmigaOS routines and perform some simple drawing. Along the way we have to deal with Tags, variable parameter, messaging, drawing principles and intuition structures.

The part of AmigaOS that handles Windows, Mouse and/or Keyboard input is named Intuition and is accessible by the developer through intuition.library and can be combined with use of graphics.library for drawing routines. There are more things involved but these two libraries are the most important. Both libraries with their functions can be accessed in Free Pascal with the units named intuition and agraphics. The latter had to be named AGraphics because Free Pascal itself already has a (generic) unit named graphics and which is available in the Lazarus component library. To prevent issues due to name collision we decided to change the name for Amiga platforms to agraphics. Unfortunately we have to deal with such name collisions for some of the units/libraries and also applies to some of the declared structures. A list with names that had to be changed can be found at our Specifics wiki Page.

Tags, Taglists

Some really cool innovations that the Amiga offers are hidden from the average user. The Taglist feature is such an innovation. Imagine you want to design a function that opens a window and which requires a lot of parameters in order to accomplish this. F.e. x and y coordinates, width, height, the type of window and even more if wanted. If you want to put them all into the parameter list of a declaration of a function or procedure then the list of parameters will become significant in length quickly and because of that such a lengthy list becomes very tedious to use. Usually such long lists of parameters can be avoided by using a record instead so that you would only have to supply a pointer to this record and which could then contain all parameters and other relevant information. But, after years of developing you'll notice that you need to add more and more parameters simply because more and more (powerful) features are added to their parameter list in order to be able to make use of this newly added window functionality.

One way to solve that without destroying compatibility to old programs is, to create a new function or record which includes these new fields as well. MS-Windows for example did that very often. You will find many functions/records with an additional suffix "Ex" which is the extended version that is able to support new properties.
Amiga took a different path: Tags. You supply a list of Tags and Value pairs to the function. Every supplied Tag is just a simple integer number and corresponds to a single property of the window. Just as an example (a fictional example though, let's assume we have the following TAG-value-pairs: Left = 1, Top = 2, Width = 3, Height = 4). If we also assume that you ignore all Tags that you don't know about and by default fill all other (unknown) properties which are not provided with some meaningful default values then this offers a rather ingenious and clever way of being able to add new tag properties to such a tag-list without actually breaking either backward or forward compatibility.
A taglist could look like this (this is actually part of a real taglist that is used to open a window):

Tag Value
WA_Width 300
WA_Height 200
WA_Title 'My Window'
WA_DepthGadget True
WA_SizeGadget True

As can been seen from the table above, there are different types (integer, string and boolean) that can be assigned as a Tag-value although the Tag's property name itself dictates which type that should actually be. But, in order to keep things simple we should be able to transfer all these different types using the same interface. There is a very simple but rather clever way that can solve this problem for us and that is by declaring tag values to be of type "PtrUInt" (or "NativeUInt", which is the same). This type is an unsigned integer value which is guaranteed to have enough room in order to even store a pointer. This way we are able to supply all our Tag-Values by simply casting them to PtrUInt. For example strings can be converted this way as a PChar (zero terminated C like strings) and would then be treated as any other common Pointer. Instead of casting (which is a rather tedious thing to do) there is also another solution available and that is offered by unit Utility, that offers a special set of wrapper functions named AsTag(). AsTag() will automatically convert all possible types into a PtrUint. You can find them in the utility unit. Another notable feature of such tag-lists is that the list itself always needs to end with a special closing tag named TAG_END. That let every other function that work with tag-lists know where the actual end of the list is located. The TAG_END-Tag does not really require an actual tag-value. Since its common for a picture to be able to tell more than a thousand words, adding a list of tag-value pairs to a list looks something like:

Tag Value
WA_Width AsTag(300)
WA_Height AsTag(200)
WA_Title AsTag('My Window')
WA_DepthGadget AsTag(True)
WA_SizeGadget AsTag(True)
TAG_END

Free Pascal is smart enough to create the string for WA_Title as type PChar. Be aware that this is not possible if the string is calculated at this point. Besides that, we also have to take into account whether or not the PChar that is going to be supplied to the function is copied or not (but more on that later).

Now, with all this gained knowledge let's try to open a window om our workbench. The Amiga Api offers quite some different functions that open a window:

  • OpenWindow() - This is an older declaration/interface that was used before taglists where invented but is still available for compatibility reasons.
  • OpenWindowTagList() - This function requires a link to a tag-list for the second parameter. You create the tag-list before making the actual call to the function. This is very convenient in case you wish to manipulate the tag-list beforehand or programmaticaly.
  • OpenWindowTags() - This function has a array of PtrUInt as second parameter which can be used to directly supply a list of tags and corresponding tag-values to the function.

For example a simple way to create a window (with the given parameter) will be:

  OpenWindowTags(nil, 
   [
    WA_Width, AsTag(300),
    WA_Height, AsTag(200),
    WA_Title, AsTag('My Window'),
    WA_DepthGadget, AsTag(True),
    WA_SizeGadget, AsTag(True),
    TAG_END  
   ])

Of course we need to close the Window again, with CloseWindow() which needs the pointer to the window we created. This is returned by the OpenWindow call and has the type PWindow so we need to put that into a variable.


program window;
uses
  utility, intuition;
var
  win: PWindow;
begin 
  win := OpenWindowTags(nil, 
   [
    WA_Width, AsTag(300),
    WA_Height, AsTag(200),
    WA_Title, AsTag('My Window'),
    WA_DepthGadget, AsTag(True),
    WA_SizeGadget, AsTag(True),
    TAG_END  
   ]);
  CloseWindow(win);
end.

This program will open a window and close it directly again. We can introduce a Sleep(1000) to at least see the window. Next step is to keep the window open until the user pressed the close gadget, as you are used to it. For that we have to react to the event of clicking the gadget.

Events

currently the window does not have a close gadget, we need to activate that on the openwindow call with this flag: WA_Flags, WFLG_CloseGadget. Flags are parameter for the window. If you need several flags at once, just or them together. To have the close gadget is not enough we also have to activate the event, we supply an additional tag OpenWindow() call: WA_IDCMP, IDCMP_CLOSEWINDOW which tells the system that we want to be notified when the window gets closed. To get this event message we have to check the message port of the window. The message port is something like a post box where events about that window arrive. You can use the function GetMsg() from the exec unit. Now we need the message port of the window which is a field of the window we created called win^.UserPort. First we have to wait until a message appears using WaitPort(), it blocks the execution until a message appears in the message port and gives other tasks a chance to do something. Which is much better than asking the port as fast as possible until a message appears. After we get the message we can inspect the contents of it, if we finished with this message we reply to the sender that we processed the message an we do not need it anymore. The call to do that is ReplyMsg(). Do not access the message after this call usually the sender of the message will free the memory of it. The usual flow will be:

repeat
  WaitPort(Win^.UserPort);
  Msg := GetMsg(Win^.UserPort);
  // Process Msg
  ReplyMsg(Msg);
until done;

Because we only called for a single message if a message arrive it will be our message, but because we later want to react on more messages we have to process the message first. Because that message is send to window by intuition it's type is not just PMessage but PIntuiMessage which has the field Msg^.IClass which defines the type of message received. If that field is IDCMP_CLOSEWINDOW that message is a close window event. putting all together it looks like that:

program window;
uses
  exec, sysutils, utility, intuition;
var
  win: PWindow;
  done: Boolean = False;
  Msg: PMessage;
begin 
  win := OpenWindowTags(nil, 
   [
    WA_Width, AsTag(300),
    WA_Height, AsTag(200),
    WA_Title, AsTag('My Window'),
    WA_DepthGadget, AsTag(True),
    WA_SizeGadget, AsTag(True),
    WA_Flags, WFLG_CloseGadget,
    WA_IDCMP, IDCMP_CLOSEWINDOW,
    TAG_END  
   ]);
  repeat
    WaitPort(Win^.UserPort);
    Msg := GetMsg(Win^.UserPort);
    case PIntuiMessage(Msg)^.IClass of
      IDCMP_CloseWindow: Done := True;
    end;
    ReplyMsg(Msg);
  until done;
  CloseWindow(win);
end.