/***************************************************************************** This source code is made available to you under the terms of the Open Development License. This Package should be accompanied by a file that contains the associated licensing terms. If you are unable to find this file, please refer to its location on the Internet at www.rocklyte.com/license.html. ****************************************************************************** ###CLASS### Name: Dialog Version: 1.0 ID: ID_DIALOG Status: Beta Category: GUI Date: December 2003 Author: Rocklyte Systems Copyright: Rocklyte Systems, 2003. All rights reserved. Keywords: dialog, box, question, query, user, request, requester Short: The Dialog class is used to pose a question and retrieve an answer from the user. ###DESCRIPTION### <p>The Dialog class provides the means for the creation of simple dialog windows, typically for the purpose of posing a question to the user and then waiting for a response before continuing. You will need to specify the text to be printed inside the dialog box and the buttons for the user to click on. Optionally you may also specify an image to accompany the text for the purposes of enhancing the message.</p> <p>The following example illustrates how you can use the dialog control in a DML script, for the purposes of requesting a Yes/No response from the user.</p> <pre> &lt;dialog image="icons:items/question(48)" buttons="yes;no" title="Confirmation Required" flags="wait" responseobject="[variable]" responsefield="result"/&gt; </pre> <p>A simple input box can be created inside the dialog window if you need the user to type in a one-line string as part of the dialog response. To do this, set the INPUT flag and write a string to the InputText field if you wish to set a pre-defined response. On successful completion, the InputText field will be updated to reflect the user's string entry.</p> <p>If a dialog box needs to be used multiple times, create it as static and then use the Show action to display the dialog window as required. This is a good way of 'caching' the window so that it does not need to be recreated from scratch each time that the dialog window needs to be displayed.</p> <p>Any child objects that are initialised to a dialog will be activated in the event that a successful response is given by the user. Failure to respond, or a response of 'cancel', 'quit' or 'none' will prevent the activation of the child objects.</p> ###END### *****************************************************************************/ //#define DEBUG #include <pandora/system/all.h> #include <pandora/graphics/all.h> #include <pandora/files/all.h> #include <pandora/misc/all.h> #include <pandora/modules/strings.h> #include <pandora/modules/render.h> #include <pandora/tools/dialog.h> #include <pandora/functions/set.c> static ERROR CMDInit(OBJECTPTR, struct KernelBase *); static ERROR CMDExpunge(void); #define VER_DIALOG 1.0 MODULE_HEADER = { MODULE_HEADER_V1, CMDInit, NULL, NULL, CMDExpunge, JMP_DEFAULT, 0, CPU_PC, VER_DIALOG, VER_KERNEL, "Rocklyte Systems", "Rocklyte Systems (c) 2003. All rights reserved.", "December 2003", "Dialog" }; static struct KernelBase *KernelBase = 0; static struct StringsBase *StringsBase; static OBJECTPTR DialogClass = 0; static OBJECTPTR StringsMod = 0; static LONG glBreakMessageID = 0; #define DLG_WIDTH 290 #define DLG_HEIGHT 102 #define BUTTONGAP 6 /***************************************************************************** ** Class definition. */ static struct FieldDef DialogFlags[] = { { "WAIT", DF_WAIT }, { "INPUT", DF_INPUT }, { "INPUTREQUIRED", DF_INPUTREQUIRED }, { NULL, NULL } }; static struct FieldDef ButtonFlags[] = { { "CANCEL", RSF_CANCEL }, { "YES", RSF_YES }, { "NO", RSF_NO }, { "NOALL", RSF_NOALL }, { "YESALL", RSF_YESALL }, { "OKAY", RSF_OKAY }, { "QUIT", RSF_QUIT }, { "NONE", RSF_NONE }, { NULL, NULL } }; static ERROR GET_String(struct Dialog *, STRING *); static ERROR GET_Title(struct Dialog *, STRING *); static ERROR GET_Icon(struct Dialog *, STRING *); static ERROR GET_Image(struct Dialog *, STRING *); static ERROR GET_InputText(struct Dialog *, STRING *); static ERROR SET_Buttons(struct Dialog *, STRING); static ERROR SET_Icon(struct Dialog *, STRING); static ERROR SET_Image(struct Dialog *, STRING); static ERROR SET_InputText(struct Dialog *, STRING); static ERROR SET_Response(struct Dialog *, LONG *); static ERROR SET_ResponseField(struct Dialog *, STRING); static ERROR SET_String(struct Dialog *, STRING); static ERROR SET_Title(struct Dialog *, STRING); static struct FieldArray DialogFields[] = { { "Window", 0, FDF_OBJECTID|FDF_RW, 0, NULL, NULL }, { "Target", 0, FDF_OBJECTID|FDF_RI, 0, NULL, NULL }, { "Flags", 0, FDF_LONGFLAGS|FDF_RW, (LONG)&DialogFlags, NULL, NULL }, { "Response", 0, FDF_LONGFLAGS|FDF_RW, (LONG)&ButtonFlags, NULL, SET_Response }, { "ResponseObject", 0, FDF_OBJECTID|FDF_RW, 0, NULL, NULL }, { "Static", 0, FDF_LONG|FDF_RW, 0, NULL, NULL }, /** Virtual fields ***/ { "Buttons", 0, FDF_STRING|FDF_W, 0, NULL, SET_Buttons }, { "Icon", 0, FDF_STRING|FDF_RW, 0, GET_Icon, SET_Icon }, { "Image", 0, FDF_STRING|FDF_RW, 0, GET_Image, SET_Image }, { "InputText", 0, FDF_STRING|FDF_RW, 0, GET_InputText, SET_InputText }, { "ResponseField", 0, FDF_STRING|FDF_RW, 0, NULL, SET_ResponseField }, { "String", 0, FDF_STRING|FDF_RW, 0, GET_String, SET_String }, { "Title", 0, FDF_STRING|FDF_RW, 0, GET_Title, SET_Title }, END_FIELD }; /***************************************************************************** ** Actions */ static ERROR DIALOG_ActionNotify(struct Dialog *, struct acActionNotify *); static ERROR DIALOG_Activate(struct Dialog *, APTR); static ERROR DIALOG_ClosingTag(struct Dialog *, APTR); static ERROR DIALOG_Free(struct Dialog *, APTR); static ERROR DIALOG_Init(struct Dialog *, APTR); static ERROR DIALOG_NewObject(struct Dialog *, APTR); static ERROR DIALOG_Show(struct Dialog *, APTR); static struct ActionArray DialogActions[] = { { AC_ActionNotify, DIALOG_ActionNotify }, { AC_Activate, DIALOG_Activate }, { AC_ClosingTag, DIALOG_ClosingTag }, { AC_Free, DIALOG_Free }, { AC_Init, DIALOG_Init }, { AC_NewObject, DIALOG_NewObject }, { AC_Show, DIALOG_Show }, { NULL, NULL } }; /***************************************************************************** ** Prototypes */ static ERROR CreateWindow(struct Dialog *Self); /***************************************************************************** ** Command: Init() */ static ERROR CMDInit(OBJECTPTR argModule, struct KernelBase *argKernelBase) { KernelBase = argKernelBase; /*** Open required modules ***/ if (CreateObject(ID_MODULE, NULL, &StringsMod, NULL, FID_Name|TSTRING, "strings", FID_Version|TFLOAT, MODVERSION_STRINGS, TAGEND) IS ERR_Okay) { if (GetField(StringsMod, FID_ModBase, FT_POINTER, &StringsBase) != ERR_Okay) { return(ObjectError(argModule, ERH_InitModule, ERR_GetField)); } } else return(ObjectError(argModule, ERH_InitModule, ERR_CreateObject)); glBreakMessageID = AllocateID(IDTYPE_MESSAGE); return(CreateObject(ID_CLASS, NULL, (OBJECTPTR *)&DialogClass, NULL, FID_BaseClassID|TLONG, ID_DIALOG, FID_Version|TFLOAT, VER_DIALOG, FID_Name|TSTRING, "Dialog", FID_Category|TLONG, CCF_GUI, FID_Flags|TLONG, CLF_PROMOTECHILDREN|CLF_PRIVATEONLY, FID_Actions|TPTR, DialogActions, FID_Fields|TPTR, DialogFields, FID_Size|TLONG, sizeof(struct Dialog), TAGEND)); } /***************************************************************************** ** Command: Expunge() */ static ERROR CMDExpunge(void) { if (DialogClass) { Action(AC_Free, DialogClass, NULL); DialogClass = NULL; } if (StringsMod) { Action(AC_Free, StringsMod, NULL); StringsMod = NULL; } return(ERR_Okay); } /***************************************************************************** ** Dialog: Activate */ static ERROR DIALOG_Activate(struct Dialog *Self, APTR Void) { /*** Do nothing on activation (the Response field manages the Activate action) ***/ return(ERR_Okay|ERF_Notified); } /***************************************************************************** ** Dialog: ActionNotify */ static ERROR DIALOG_ActionNotify(struct Dialog *Self, struct acActionNotify *Args) { if ((Args->ActionID IS AC_Free) AND (Args->ObjectID IS Self->WindowID)) { Self->Response = RSF_CANCEL; if (Self->Active) SendMessage(NULL, glBreakMessageID, NULL, NULL, NULL); if (Self->Static IS FALSE) { ActionMsg(AC_Free, Self->WindowID, NULL); Self->WindowID = NULL; } } return(ERR_Okay); } /***************************************************************************** ** Dialog: ClosingTag */ static ERROR DIALOG_ClosingTag(struct Dialog *Self, APTR Void) { if (Self->Static IS FALSE) { DPrintF("~ClosingTag()","[Dialog]"); Action(AC_Show, Self, NULL); /* Note: The dialog window will be destroyed automatically ** when a response is received from the buttons. */ StepBack(); } return(ERR_Okay); } /***************************************************************************** ** Dialog: Free */ static ERROR DIALOG_Free(struct Dialog *Self, APTR Void) { OBJECTPTR window; if ((Self->WindowID) AND (CheckObjectExists(Self->WindowID, NULL) IS ERR_Okay)) { if (AccessObject(Self->WindowID, 5000, &window) IS ERR_Okay) { UnsubscribeAction(window, NULL, Self->Head.UniqueID); ReleaseObject(window); } ActionMsg(AC_Free, Self->WindowID, NULL); Self->WindowID = NULL; } if (Self->String) { FreeMemory(Self->String); Self->String = NULL; } return(ERR_Okay); } /***************************************************************************** ** Dialog: Init() */ static ERROR DIALOG_Init(struct Dialog *Self, APTR Void) { ERROR error; if (Self->TotalButtons < 1) { DPrintF("@Init:","[Dialog] The buttons have not been set."); return(ERR_FieldNotSet); } /* Create the dialog window now if we are not static, otherwise ** we can wait until the Show action is used. */ if (Self->Static IS FALSE) { #ifndef DEBUG DebugState(FALSE, -1); #endif error = CreateWindow(Self); #ifndef DEBUG DebugState(TRUE, -1); #endif if (error != ERR_Okay) return(ObjectError(Self, ERH_Init, error)); } return(ERR_Okay); } /***************************************************************************** ** Dialog: NewObject() */ static ERROR DIALOG_NewObject(struct Dialog *Self, APTR Void) { StrCopy("Confirmation Required", Self->Title, sizeof(Self->Title)); StrCopy("icons:items/question", Self->Icon, sizeof(Self->Icon)); return(ERR_Okay); } /***************************************************************************** ###ACTION### Name: Show Short: Puts the dialog window on display. ###DESCRIPTION### <p>Call the Show action to display the dialog window. If you have set the WAIT option in the Flags field, the process will be put to sleep in a message processing loop while it waits for the user to respond to the dialog box. After the Show action returns, you will be able to use the Response field to read the user's response to the dialog box.</p> <p>The WAIT option is recommended for script usage only. When programming, a more reliable and controlled way to wait on the dialog box is to use a loop such as the following:</p> <pre> while (!dialog->Response) { WaitTime(0, 10000); ProcessMessages(NULL, NULL); } </pre> <p>The dialog object will automatically terminate itself after the Show action is used and the user responds. This behaviour may be averted if you set the Static field to TRUE.</p> ###END### *****************************************************************************/ static ERROR DIALOG_Show(struct Dialog *Self, APTR Void) { struct Message message; struct NextMsg nextmsg; ERROR error; DPrintF("~Show()","[Dialog:%d]", Self->Head.UniqueID); /*** If we are active, do not continue ***/ if (Self->Active IS TRUE) return(ERR_Okay); Self->Response = NULL; /* If our dialog window has disappeared (e.g. the user killed it on ** a previous activation), we'll need to recreate it. */ if ((!Self->WindowID) OR (CheckObjectExists(Self->WindowID, NULL) != ERR_Okay)) { Self->WindowID = NULL; #ifndef DEBUG DebugState(FALSE, -1); #endif error = CreateWindow(Self); #ifndef DEBUG DebugState(TRUE, -1); #endif if (error != ERR_Okay) return(ERR_Failed); } /*** Show the dialog window ***/ ActionMsg(AC_MoveToFront, Self->WindowID, NULL); ActionMsg(AC_Show, Self->WindowID, NULL); if (Self->Flags & DF_WAIT) { Self->Active = TRUE; /* Wait for a user response. We will awaken if the Response field ** is updated, or if the dialog window is killed. See the code for ** the Response field for further details. */ DPrintF("Show:","[Dialog] Entering sleep mode..."); while (ProcessMessages(&nextmsg, MF_WAIT) IS ERR_Okay) { if (nextmsg.Count > 0) { if (GetMessage(NULL, NULL, NULL, &message, sizeof(struct Message)) IS ERR_Okay) { if ((message.Type IS MSGID_QUIT) OR (message.Type IS glBreakMessageID)) break; else DPrintF("@Dialog:","Received unknown message type #%d.", message.Type); } } } DPrintF("Show:","[Dialog] Break received - I no longer slumber!"); /* Terminate our dialog object if we are non-static and called ** from a script (we don't want to auto-terminate when the WAIT ** option is used in a normal program). */ if (Self->Static IS FALSE) Action(AC_Free, Self, NULL); Self->Active = FALSE; } StepBack(); return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: Buttons Short: Buttons for the dialog box are defined through this field. Type: STRING Status: Set ###DESCRIPTION### <p>Use the Buttons field to define a series of buttons that will appear in the dialog box. Setting this field is compulsory in order for a dialog object to initialise. This field is set using the following field format:</p> <pre> "response:text; response:text; ..." </pre> <p>Each button definition is separated by a semi-colon and the order that you use reflects the button creation, scanning from left to right in the dialog window. You must define a response type for each button, which may be one of Cancel, Yes, YesAll, No, NoAll, Quit and Okay. A special response type of None is also allowed if you want to create a dummy button that only closes the dialog window. The response definition may be followed with a colon and then a text description to be displayed inside the button area. If you do not wish to declare a text description, you can follow-up with a semi-colon and then the next button's description.</p> <p>When a button is pressed, the matching response value will be written to the Response field and then the dialog window will be closed.</p> ###END### *****************************************************************************/ static ERROR SET_Buttons(struct Dialog *Self, STRING Value) { WORD index, i, j; UBYTE response[30]; index = 0; while ((*Value) AND (index < ARRAYSIZE(Self->Buttons))) { /*** Extract the response type ***/ for (i=0; (*Value) AND (*Value != ';') AND (*Value != ':'); i++) { response[i] = *Value++; } response[i] = 0; /*** Convert the response to a value ***/ Self->Buttons[index].Response = RSF_NONE; /* No response by default */ for (j=0; ButtonFlags[j].Name; j++) { if (StrCompare(ButtonFlags[j].Name, response, 0, STR_MATCHLENGTH) IS ERR_Okay) { Self->Buttons[index].Response = ButtonFlags[j].Value; } } /*** Extract text ***/ if (*Value IS ':') { Value++; while ((*Value) AND (*Value <= 0x20)) Value++; for (j=0; (*Value) AND (*Value != ';'); j++) Self->Buttons[index].Text[j] = *Value++; Self->Buttons[index].Text[j] = 0; } else Self->Buttons[index].Text[0] = 0; if (!Self->Buttons[index].Text[0]) { switch (Self->Buttons[index].Response) { case RSF_CANCEL: StrCopy("Cancel", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; case RSF_QUIT: StrCopy("Quit", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; case RSF_NO: StrCopy("No", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; case RSF_NOALL: StrCopy("No to All", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; case RSF_YES: StrCopy("Yes", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; case RSF_YESALL: StrCopy("Yes to All", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; case RSF_OKAY: StrCopy("Okay", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; default: StrCopy("-", Self->Buttons[index].Text, sizeof(Self->Buttons[0].Text)); break; } } /*** Go to the next button entry ***/ while ((*Value) AND (*Value != ';')) Value++; if (*Value IS ';') Value++; while ((*Value) AND (*Value <= 0x20)) Value++; index++; } Self->TotalButtons = index; return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: Flags Short: Optional flags may be set here. Type: LONG Status: Read/Init ###DESCRIPTION### <p>Optional flags that may be set against a dialog object are as follows:</p> <table border="1" bordercolordark="#000000" cellspacing="0" cellpadding="2" bordercolorlight="#000000" width="80%" align="center"> <tr><td class="tableheader" bgcolor="#eeeeee">Flag</td><td class="tableheader" bgcolor="#eeeeee">Description</td></tr> <tr><td width="10%">WAIT</td><td>Normally when a dialog window is displayed, process control will return immediately to the program. This can be problematic if you require a response to the dialog box before continuing with your processing. To solve this issue, use the WAIT flag to curb the program flow until the dialog box receives a response from the user.</td></tr> <tr><td width="10%">INPUT</td><td>An input box can be created inside the dialog window by setting this flag. In this mode, the dialog box will return a user input string in the InputText field after the user has responded to the dialog window.</td></tr> <tr><td width="10%">INPUTREQUIRED&nbsp;&nbsp;</td><td>This flag can be used in conjunction with the INPUT option. It forces the user to input at least one character in the input box, otherwise the dialog window will automatically cancel itself.</td></tr> </table> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: Icon Short: The icon that appears in the window title bar may be set here. Type: STRING Status: Get/Set ###DESCRIPTION### <p>A question-mark icon is set in the dialog window by default, however you may change to a different icon image if you wish. If you are going to refer to a stock icon, use the file format, "icons:category/name".</p> ###END### *****************************************************************************/ static ERROR GET_Icon(struct Dialog *Self, STRING *Value) { if (Self->Icon[0]) { *Value = Self->Icon; return(ERR_Okay); } else return(ERR_FieldNotSet); } static ERROR SET_Icon(struct Dialog *Self, STRING Value) { WORD i; if (Value) { for (i=0; (i < sizeof(Self->Icon)-1) AND (Value[i]); i++) Self->Icon[i] = Value[i]; Self->Icon[i] = 0; } else Self->Icon[0] = 0; return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: Image Short: An icon file may be specified here in order to visually enhance the dialog message. Type: STRING Status: Get/Set ###DESCRIPTION### <p>Images may be used inside a dialog window to enhance the message that is presented to the user. A number of icons are available in Athene's icon library that are suitable for display in dialog boxes (the icons:items/ directory contains most of these). The image should be no larger than 48x48 pixels and no less than 32x32 pixels in size.</p> ###END### *****************************************************************************/ static ERROR GET_Image(struct Dialog *Self, STRING *Value) { if (Self->Image[0]) { *Value = Self->Image; return(ERR_Okay); } else return(ERR_FieldNotSet); } static ERROR SET_Image(struct Dialog *Self, STRING Value) { WORD i; if (Value) { for (i=0; (i < sizeof(Self->Image)-1) AND (Value[i]); i++) Self->Image[i] = Value[i]; Self->Image[i] = 0; } else Self->Image[0] = 0; /* Destroy the existing image and replace it ** with the new image. */ if (Self->ImageID) { } return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: InputText Short: Text to be placed inside the dialog input box may be set here. Type: STRING Status: Get/Set ###DESCRIPTION### <p>If you are creating a dialog box with a user input area, you may optionally specify an input string to be displayed inside the input box. The user will be able to edit the string as he sees fit. Once the user has responded to the dialog window, you can read this field to discover what the user has entered.</p> <p>Note: When the user responds to an input entry field by pressing the enter key, the dialog object will produce a response of "Okay". To aid the predictability of the possible dialog responses, you should create an okay button to accompany the input box.</p> ###END### *****************************************************************************/ static ERROR GET_InputText(struct Dialog *Self, STRING *Value) { if (Self->InputText[0]) { *Value = Self->InputText; return(ERR_Okay); } else return(ERR_FieldNotSet); } static ERROR SET_InputText(struct Dialog *Self, STRING Value) { WORD i; if (Value) { for (i=0; (i < sizeof(Self->InputText)-1) AND (Value[i]); i++) Self->InputText[i] = Value[i]; Self->InputText[i] = 0; } else Self->InputText[0] = 0; return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: Response Short: Holds the response value when a button is pressed. Type: LONG Status: Read ###DESCRIPTION### <p>This field holds the response value when a button is pressed in the dialog box window. Valid response values are:</p> <table border="1" bordercolordark="#000000" cellspacing="0" cellpadding="2" bordercolorlight="#000000" width="80%" align="center"> <tr><td class="tableheader" bgcolor="#eeeeee">Value</td></tr> <tr><td width="10%">RSF_CANCEL</td></tr> <tr><td width="10%">RSF_QUIT</td></tr> <tr><td width="10%">RSF_NO</td></tr> <tr><td width="10%">RSF_NOALL</td></tr> <tr><td width="10%">RSF_YES</td></tr> <tr><td width="10%">RSF_YESALL</td></tr> <tr><td width="10%">RSF_OKAY</td></tr> </table> <p>If no response was returned (for example, the user killed the dialog window rather than clicking a button) then the Response value will be NULL.</p> ###SEE ALSO### Field: Buttons ###END### *****************************************************************************/ static ERROR SET_Response(struct Dialog *Self, LONG *Value) { OBJECTPTR object; STRING str; LONG count; WORD i; if (*Value IS RSF_NONE) Self->Response = NULL; else Self->Response = *Value; /* If we are sleeping, send a break message because the user has ** clicked one of our buttons. */ if (Self->Active) SendMessage(NULL, glBreakMessageID, NULL, NULL, NULL); if (Self->Head.ObjectFlags & NF_INITIALISED) { /*** Hide the window, now that the user has responded ***/ ActionMsg(AC_Hide, Self->WindowID, NULL); if (Self->InputID) { /* When using input boxes in the dialog window, write the user's ** input response to the TextInput field. */ if (AccessObject(Self->InputID, 5000, &object) IS ERR_Okay) { if ((GetField(object, FID_String, FT_STRING, &str) IS ERR_Okay) AND (str)) { StrCopy(str, Self->InputText, sizeof(Self->InputText)); DPrintF("Dialog:","User input: %s", str); } else Self->InputText[0] = 0; ReleaseObject(object); } if ((Self->Flags & DF_INPUTREQUIRED) AND (!Self->InputText[0])) { DPrintF("Dialog:","Response cancelled as no input was given."); Self->Response = NULL; /* Cancel response if no input was given */ } } if (Self->Response) { /*** If we need to store the response in an object, do it now ***/ DPrintF("Dialog:","Received response #%d.", Self->Response); if (Self->ResponseObject) { if (AccessObject(Self->ResponseObject, 5000, &object) IS ERR_Okay) { for (i=0; ButtonFlags[i].Name; i++) { if (ButtonFlags[i].Value IS Self->Response) { SetField(object, Self->ResponseField, FT_STRING, ButtonFlags[i].Name); break; } } ReleaseObject(object); } } /*** Activate child objects ***/ if (Self->Response & RSF_POSITIVE) { if ((count = TotalChildren(Self->Head.UniqueID))) { struct ChildEntry list[count]; if (ListChildren(GetUniqueID(Self), list, &count) IS ERR_Okay) { for (i=0; i < count; i++) ActionMsg(AC_Activate, list[i].ObjectID, NULL); } } NotifySubscribers(Self, AC_Activate, NULL, NULL); } } else DPrintF("Dialog:","No response code was given."); /* If we are not static, destroy our object and window immediately ** after receiving a response from the buttons. */ if (Self->Static IS FALSE) { Action(AC_Free, Self, NULL); } } return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: ResponseField Short: References a field in relation to the ResponseObject. Type: STRING Status: Set ###DESCRIPTION### <p>This field is used when the ResponseObject field has been set. By setting a field name here, the dialog object will automatically write the result of any button-press to the referenced field name. The field must accept strings.</p> ###END### *****************************************************************************/ static ERROR SET_ResponseField(struct Dialog *Self, STRING Value) { ResolveFields(Value, &Self->ResponseField, TAGEND); return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: ResponseObject Short: Refers to an object that will receive the results of button presses. Type: OBJECTID Status: Read/Write ###DESCRIPTION### <p>When the user clicks on the button of a dialog box, you may need to record the result in another location, especially in the event that the dialog object is non-static and closes after usage. To write the response to another object, refer to the unique ID of that object in this field. You will also need to set the ResponseField to the name of the field that you would like to update.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: Static Short: Set to TRUE to make the object static. Status: Read/Write Type: OBJECTID ###DESCRIPTION### <p>By default, a Dialog object will execute itself and then self-destruct when a closing tag is received. If you would rather that the object stays in the system, set this field to TRUE. If you do this, the only way to get a Dialog object to perform is to call the Show action.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: String Short: The string that is to be printed inside the dialog box is declared here. Type: STRING Status: Get/Set ###DESCRIPTION### <p>The string that you would like to be displayed in the dialog box is specified in this field. The string must be in UTF-8 format and may contain line feeds if you need to separate the text. Each line of text will be automatically word-wrapped to fit the dialog window if any of them are too wide.</p> ###END### *****************************************************************************/ static ERROR GET_String(struct Dialog *Self, STRING *Value) { if (Self->String) { *Value = Self->String; return(ERR_Okay); } else return(ERR_FieldNotSet); } static ERROR SET_String(struct Dialog *Self, STRING Value) { OBJECTPTR text; WORD i; if (Self->String) { FreeMemory(Self->String); Self->String = NULL; } if ((Value) AND (*Value)) { for (i=0; Value[i]; i++); if (AllocMemory(i+1, MEM_STRING|MEM_NOCLEAR, (void **)&Self->String, NULL) IS ERR_Okay) { for (i=0; Value[i]; i++) Self->String[i] = Value[i]; Self->String[i] = 0; } else return(ObjectError(Self, ERH_SetField, ERR_AllocMemory)); } /*** Update the text in the dialog box ***/ if (Self->TextID) { if (AccessObject(Self->TextID, 5000, &text) IS ERR_Okay) { SetField(text, FID_String, FT_STRING, Value); ReleaseObject(text); } } return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: Target Short: The target for the dialog box window is specified here. Type: OBJECTID Status: Init ###DESCRIPTION### <p>The window for a dialog box will normally be created on the desktop. On occasion it may be useful to have the window appear in a different area, such as inside another window or screen. To do this, point the Target field to the unique ID of the drawable that you want to open the window on.</p> <p>The target may not be changed after initialisation.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: Title Short: Defines the window title for the dialog box. Type: STRING Status: Get/Set ###DESCRIPTION### <p>The window title for the dialog box is specified here as a standard UTF-8 text string.</p> ###END### *****************************************************************************/ static ERROR GET_Title(struct Dialog *Self, STRING *Value) { if (Self->Title[0]) { *Value = Self->Title; return(ERR_Okay); } else return(ERR_FieldNotSet); } static ERROR SET_Title(struct Dialog *Self, STRING Value) { WORD i; if (Value) { for (i=0; (i < sizeof(Self->Title)-1) AND (Value[i] >= 0x20); i++) Self->Title[i] = Value[i]; Self->Title[i] = 0; } else Self->Title[0] = 0; /*** Update the window title ***/ if (Self->WindowID) { } return(ERR_Okay); } /***************************************************************************** ###FIELD### Name: Window Short: Refers to the ID of the window created by the dialog object. Type: OBJECTID Status: Read ###DESCRIPTION### <p>This readable field references the ID of the dialog box's window. It is only usable on successful initialisation of a dialog box. It is recommended that you avoid tampering with the generated window, but direct access may be useful for actions such as altering the window position.</p> ###END### *****************************************************************************/ /***************************************************************************** ** Internal: CreateWindow() */ static ERROR CreateWindow(struct Dialog *Self) { struct acRedimension redimension; OBJECTPTR window, image, object, text; LONG leftmargin, rightmargin, bottommargin, topmargin; LONG imagewidth, buttonheight, win_x, win_y, scr_height, textheight; WORD buttonwidth, i, total, focus; OBJECTID buttonid, tabfocusid, win_parent; STRING response; DPrintF("CreateWindow()","[Dialog:%d]", Self->Head.UniqueID); if (Self->TotalButtons < 1) { DPrintF("@CreateWindow()","[Dialog] The buttons have not been set."); return(ERR_FieldNotSet); } /*** Run the window DML script ***/ if (NewObject(ID_WINDOW, NF_CHILD, &window, &Self->WindowID) IS ERR_Okay) { SetFields(window, FID_Title|TSTRING, Self->Title, FID_InsideWidth|TLONG, DLG_WIDTH, FID_InsideHeight|TLONG, DLG_HEIGHT, FID_MinWidth|TLONG, DLG_WIDTH, FID_MinHeight|TLONG, DLG_HEIGHT, FID_MaxWidth|TLONG, DLG_WIDTH, FID_MaxHeight|TLONG, DLG_HEIGHT, FID_StickToFront|TLONG, TRUE, FID_Quit|TLONG, FALSE, FID_Center|TLONG, TRUE, FID_Icon|TSTRING, Self->Icon, TAGEND); if (Self->TargetID) SetField(window, FID_Parent, FT_LONG, &Self->TargetID); if (Action(AC_Init, window, NULL) IS ERR_Okay) { SubscribeActionTags(window, Self, AC_Free, TAGEND); GetFields(window, FID_LeftMargin|TLONG, &leftmargin, FID_TopMargin|TLONG, &topmargin, FID_BottomMargin|TLONG, &bottommargin, FID_RightMargin|TLONG, &rightmargin, FID_XCoord|TLONG, &win_x, FID_YCoord|TLONG, &win_y, FID_Parent|TLONG, &win_parent, TAGEND); } else { Action(AC_Free, window, NULL); ReleaseObject(window); Self->WindowID = NULL; return(ERR_Init); } ReleaseObject(window); } else return(ERR_NewObject); /*** Create tab-focus manager ***/ CreateObject(ID_TABFOCUS, NULL, NULL, &tabfocusid, FID_Owner|TLONG, Self->WindowID, TAGEND); /*** Create buttons inside the window ***/ buttonwidth = (DLG_WIDTH - ((Self->TotalButtons - 1) * BUTTONGAP)) / Self->TotalButtons; if (buttonwidth > 100) buttonwidth = 100; buttonheight = 0; buttonid = 0; /*** Calculate the starting button's offset ***/ for (total=0, i=0; i < ARRAYSIZE(Self->Buttons); i++) { if (Self->Buttons[i].Text[0]) total++; } Self->ButtonOffset = rightmargin + ((total-1) * buttonwidth) + ((total-1) * 6); focus = FALSE; for (i=0; i < ARRAYSIZE(Self->Buttons); i++) { if (Self->Buttons[i].Text[0]) { DPrintF("Dialog:","Creating button: %s", Self->Buttons[i].Text); switch (Self->Buttons[i].Response) { case RSF_CANCEL: response = "Cancel"; break; case RSF_NO: response = "No"; break; case RSF_NOALL: response = "NoAll"; break; case RSF_YES: response = "Yes"; break; case RSF_YESALL: response = "YesAll"; break; case RSF_OKAY: response = "Okay"; break; case RSF_QUIT: response = "Quit"; break; case RSF_NONE: response = "None"; break; case NULL: response = "None"; break; default: DPrintF("@CreateWindow:","[Dialog] Failed to identify response #%d for button %d", Self->Buttons[i].Response, i); return(ERR_Failed); } if (CreateObject(ID_BUTTON, NULL, NULL, &buttonid, FID_Owner|TLONG, Self->WindowID, FID_Text|TSTRING, Self->Buttons[i].Text, FID_YOffset|TLONG, bottommargin, FID_XOffset|TLONG, Self->ButtonOffset, FID_Width|TLONG, buttonwidth, FID_TabFocus|TLONG, tabfocusid, TAGEND) IS ERR_Okay) { if (CreateObject(ID_SET, NULL, NULL, NULL, FID_Owner|TLONG, buttonid, FID_Object|TLONG, Self->Head.UniqueID, FID_Static|TLONG, TRUE, FID_Response|TUNLISTED, response, TAGEND) != ERR_Okay) { DPrintF("@Dialog:","Failed to create Set object."); return(ERR_CreateObject); } Self->ButtonOffset -= buttonwidth + 6; if (focus IS FALSE) { ActionMsg(AC_Focus, buttonid, NULL); focus = TRUE; } ActionMsg(AC_Show, buttonid, NULL); } else { DPrintF("@Dialog:","Failed to create Button object."); return(ERR_CreateObject); } } } /*** Retrieve the default button height ***/ if (AccessObject(buttonid, 5000, &object) IS ERR_Okay) { GetField(object, FID_Height, FT_LONG, &buttonheight); ReleaseObject(object); } else buttonheight = 30; /*** Create image inside the dialog window ***/ imagewidth = 0; if (Self->Image[0]) { if (CreateObject(ID_IMAGE, NULL, &image, &Self->ImageID, FID_Owner|TLONG, Self->WindowID, FID_Location|TSTRING, Self->Image, FID_Align|TLONG, 0, FID_Name|TSTRING, "imgDialog", FID_XCoord|TLONG, leftmargin, FID_YCoord|TLONG, topmargin, TAGEND) IS ERR_Okay) { GetField(image, FID_ImageWidth, FT_LONG, &imagewidth); leftmargin += imagewidth + 10; ReleaseObject(image); } } if (Self->Flags & DF_INPUT) { if (CreateObject(ID_INPUT, NULL, NULL, &Self->InputID, FID_Owner|TLONG, Self->WindowID, FID_Text|TSTRING, Self->InputText, FID_YOffset|TLONG, bottommargin + buttonheight + 16, FID_XOffset|TLONG, rightmargin, FID_X|TLONG, leftmargin, FID_TabFocus|TLONG, tabfocusid, FID_Flags|TLONG, INF_SELECTTEXT, TAGEND) IS ERR_Okay) { if (CreateObject(ID_SET, NULL, NULL, NULL, FID_Owner|TLONG, Self->InputID, FID_Object|TLONG, Self->Head.UniqueID, FID_Static|TLONG, TRUE, FID_Response|TUNLISTED, "Okay", TAGEND) != ERR_Okay) return(ERR_CreateObject); ActionMsg(AC_Focus, Self->InputID, NULL); ActionMsg(AC_Show, Self->InputID, NULL); } else return(ObjectError(Self, ERH_Init, ERR_CreateObject)); } /*** Create text inside the dialog window ***/ if (CreateObject(ID_TEXT, NULL, &text, &Self->TextID, FID_Owner|TLONG, Self->WindowID, FID_Face|TSTRING|TTRANS, "[gl_fonts.face]", FID_Colour|TSTRING|TTRANS, "[gl_fonts.window_colour]", FID_XCoord|TLONG, leftmargin, FID_YCoord|TLONG, topmargin, FID_XOffset|TLONG, rightmargin, FID_Flags|TLONG, TXF_WORDWRAP, FID_String|TSTRING, Self->String, TAGEND) IS ERR_Okay) { /* Recalculate the height of the dialog window according ** to the height of the text. The height of the window ** is calculated as follows: ** ** topmargin + textheight + 10 + buttonheight + bottommargin */ if (GetField(text, FID_TextHeight, FT_LONG, &textheight) IS ERR_Okay) { if (textheight < 20) textheight = 20; scr_height = 0; if ((win_parent) AND (AccessObject(win_parent, 5000, &object) IS ERR_Okay)) { GetField(object, FID_Height, FT_LONG, &scr_height); ReleaseObject(object); } if (AccessObject(Self->WindowID, 5000, &window) IS ERR_Okay) { redimension.Width = 0; redimension.Height = topmargin + textheight + 30 + buttonheight + bottommargin; if (Self->Flags & DF_INPUT) redimension.Height += 24; if (scr_height > 0) { redimension.YCoord = (scr_height - redimension.Height) / 2; if (redimension.Height > (scr_height * 0.75)) redimension.Height = scr_height * 0.75; } else redimension.YCoord = win_y; redimension.Depth = 0; redimension.XCoord = win_x; redimension.ZCoord = 0; SetFields(window, FID_MinHeight|TLONG, (LONG)(redimension.Height - bottommargin - topmargin), FID_MaxHeight|TLONG, (LONG)(redimension.Height - bottommargin - topmargin), TAGEND); Action(AC_Redimension, window, &redimension); ReleaseObject(window); } } else DPrintF("@CreateWindow:","[Dialog] Failed to get the height of the text."); ReleaseObject(text); } else return(ERR_CreateObject); return(ERR_Okay); }