July 5, 2018
by Tibor Lorántfy
modified at July 19, 2019

Hello, world! Part 2: Dialog with text, SVG icon and button

Ákos Somorjai has already written a post about “Hello, world!”. It helps to create a simple text element with “Hello, API world!” content.
Now, in this post, I will show you how to create a simple dialog with a “Hello World” text label on it. And this will be also a perfect example for placing an icon image into a dialog using SVG image resource.

At first, let’s find out the design of our simple dialog. It will contain a text, with “Hello World” content, an SVG image representing the Earth and a button with “Close” content.
The dialog will be a modal dialog and not resizable. Only one simple user interaction will be implemented: when the user clicks the “Close” button, the dialog closes.

Preparations

Building up the structure of the Add-On is the first thing we should do. I created the HelloWorld_Example folder (that will be the name of the example Add-On) inside the installed DevKit’s Examples folder, and built up the common Add-On folder structure.

RFIX folder

RFIX folder contains the non-localizable platform independent resources (for example images). So I placed my SVG image here into the Images folder and created the HelloWorld_ExampleFix.grc for the non-localizable resources. Note that the SVG image file’s name must follow the following naming convention: <name>_<width>x<height>.svg, where <width>x<height> is the normal not scaled size of the image.

  • 'MDID': The Fix.grc must contain the 'MDID' resource of the Add-On. I generated a new identifier using AddOnAdmin tool and copied the generated ids.
  • 'GICN': There must be a 'GICN' resource for each image resource file with an unique identifier. The parameter of the resource is the name of the image file (excluding the sizes from the SVG filename).
// -----------------------------------------------------------------------------
// HelloWorld_ExampleFix.grc
// 		Non-localizable resource description
// -----------------------------------------------------------------------------

'MDID' 32500 "Add-On Identifier" {
	628121456
	2723489675
}

'GICN' 32500  "Earth SVG icon" {
	"Earth"
}

RINT folder

RINT folder contains the localizable resources (for example strings and dialogs). I created the HelloWorld_Example.grc into this folder with the following content:

  • 'STR#' 32000: The string resource with 32000 identifier contains the main informations of the Add-On: name and short description. It will be used by CheckEnvironment function and inside ARCHICAD the Add-On Manager dialog will show these informations about the Add-On.
  • 'STR#' 32500: The string resource with 32500 id defines a new menu system. The Add-On registers a new main menu named “Test” with a submenu named “Hello World Example” containing a single menu item with “Hello World Dialog” text.
// -----------------------------------------------------------------------------
// HelloWorld_Example.grc
// 		Localizable resource description
// -----------------------------------------------------------------------------

/* Add-On Information */

'STR#' 32000 "Information" {
/* [  1] */	"Hello World Example"
/* [  2] */	"This Add-On shows an example for making a simple dialog with a label, a button and an icon."
}

/* Text appearing in the menu */

'STR#' 32500 "Menu strings" {
/* [   ] */	"Test"
/* [   ] */	"Hello World Example"
/* [  1] */		"Hello World Dialog"
}

'STR#' 32510 "Prompt strings" {
/* [   ] */	"Test"
/* [   ] */	"Hello World Example"
/* [  1] */		"Invoke Hello World Dialog"
}

/* Localizable dialog resource(s) with the accompanying help resource(s) */
#include "DGDefs.h"

'GDLG'  32500  Modal	 40   40  300  130  "Hello World Example Dialog" {
/* [  1] */ Button	145  102  150   21	LargePlain  "Close"
/* [  2] */ LeftText	145   55  150   40	LargePlain  "Hello World"
/* [  3] */ Icon	  5    5  120  120	32500
}

'DLGH'  32500  DLG_32500 {
1	"Close Button"		Button_0
2	"Hello World Text"	LeftText_0
3	"Earth Icon"		IconItem_0
}

'GDLG' dialog descriptor

The Modal keyword after the identifier of the 'GDLG' resource defines that it will be a modal dialog. I added the 3 items into it, the button, the text (which will be aligned to left) and the icon. The four numbers in each lines are the coordinates and the sizes: the first two numbers are the X and Y coordinates of the top-left corner, the next two numbers are the width and height of the item (or the whole dialog).
Special parameters of the items follow the number values. The icon item needs just one 'GICN' resource identifier as a parameter, which defines an image file. Both the button and the text items need two parameters, the font size (can be SmallPlain or LargePlain) and the text content.

'DLGH' is the help resource for the dialog. It contains prompt messages for the items of the dialog.

RFIX.win folder

RFIX.win folder is for the Windows platform specific resources. This folder contains only the HelloWorld_Example.rc2 file. This file joins the separeted grc resource files by including them:

// -----------------------------------------------------------------------------
// HelloWorld_Example.rc2
// 		Windows specific resource description
// -----------------------------------------------------------------------------

#include "HelloWorld_Example.grc.rc2"
#include "HelloWorld_ExampleFix.grc.rc2"

RFIX.mac folder

RFIX.mac folder is for the macOS platform resource. This folder must contain the Info.plist file (which will be placed into the built bundle) and the compileGRCs.pl script. That script helps to compile the resource files and creates Localizable.strings file into the bundle.

Src folder

Src is for the C++ source code.
Main.cpp contains the 4 required functions of the Add-On (CheckEnvironment, RegisterInterface, Initialize, FreeData). It registers a menu, and tries to invoke the dialog when the registered menu was selected.

// -----------------------------------------------------------------------------
// Main.cpp
// -----------------------------------------------------------------------------

// ---------------------------------- Includes ---------------------------------

#include	"APIEnvir.h"
#include	"ACAPinc.h"		// also includes APIdefs.h

#include	"HelloWorldDialog.hpp"

// -----------------------------------------------------------------------------
// Show Dialog
// -----------------------------------------------------------------------------

static void	ShowDialog ()
{
	HelloWorldDialog dialog;
	dialog.Invoke ();
}

// -----------------------------------------------------------------------------
// Handles menu commands
// -----------------------------------------------------------------------------

GSErrCode __ACENV_CALL MenuCommandHandler (const API_MenuParams *menuParams)
{
	switch (menuParams->menuItemRef.menuResID) {
		case 32500:
			switch (menuParams->menuItemRef.itemIndex) {
				case 1:		ShowDialog ();		break;
				default:				break;
			}
			break;
		default:
			break;
	}

	return NoError;
}		// MenuCommandHandler

// =============================================================================
// Required functions
// =============================================================================

// -----------------------------------------------------------------------------
// CheckEnvironment
//		Dependency definitions
// -----------------------------------------------------------------------------

API_AddonType	__ACENV_CALL	CheckEnvironment (API_EnvirParams* envir)
{
	RSGetIndString (&envir->addOnInfo.name, 32000, 1, ACAPI_GetOwnResModule ());
	RSGetIndString (&envir->addOnInfo.description, 32000, 2, ACAPI_GetOwnResModule ());

	return APIAddon_Normal;
}		// CheckEnvironment

// -----------------------------------------------------------------------------
// RegisterInterface
//		Interface definitions
// -----------------------------------------------------------------------------

GSErrCode	__ACENV_CALL	RegisterInterface (void)
{
	GSErrCode err = ACAPI_Register_Menu (32500, 32510, MenuCode_UserDef, MenuFlag_Default);
	if (err != NoError)
		ACAPI_WriteReport ("RegisterInterface() ACAPI_Register_Menu failed\n", false);

	return err;
}		// RegisterInterface

// -----------------------------------------------------------------------------
// Initialize
//		Called when the Add-On has been loaded into memory
//		to perform an operation
// -----------------------------------------------------------------------------

GSErrCode	__ACENV_CALL Initialize	(void)
{
	GSErrCode err = ACAPI_Install_MenuHandler (32500, MenuCommandHandler);
	if (err != NoError)
		ACAPI_WriteReport ("Initialize() ACAPI_Install_MenuHandler failed\n", false);

	return err;
}		// Initialize

// -----------------------------------------------------------------------------
// FreeData
//		Called when the Add-On is going to be unloaded
// -----------------------------------------------------------------------------

GSErrCode __ACENV_CALL	FreeData (void)
{
	return NoError;
}		// FreeData

 

Dialog class

Now only the C++ class of the dialog lefts. That’s declared in the HelloWorldDialog.hpp.
The class must be inherited from the DG::ModalDialog (don’t forget to include DGModule.hpp) and from the monitored item’s observer classes.
DG::CompoundItemObserver is a utility class to help attach observers to each dialogitems at once using the AttachToAllItems method, I recommend to use it everytime when creating dialog classes.

// -----------------------------------------------------------------------------
// HelloWorldDialog.hpp
// -----------------------------------------------------------------------------

#ifndef HELLOWORLDDIALOG_HPP
#define HELLOWORLDDIALOG_HPP

// ---------------------------------- Includes ---------------------------------
#include	"DGModule.hpp"


// --- Class declaration: HelloWorldDialog -------------------------------------

class HelloWorldDialog : public DG::ModalDialog,
			 public DG::ButtonItemObserver,
			 public DG::CompoundItemObserver
{
protected:
	enum Controls {
		ButtonID	= 1,
		LeftTextID	= 2,
		IconItemID	= 3
	};

	DG::Button	closeButton;
	DG::LeftText	helloWorldText;
	DG::IconItem	earthIcon;

	virtual void	ButtonClicked	(const DG::ButtonClickEvent& ev) override;

public:
	HelloWorldDialog ();
	~HelloWorldDialog ();
};

#endif // HELLOWORLDDIALOG_HPP

The definition of the dialog class will be also such simple:

// -----------------------------------------------------------------------------
// HelloWorldDialog.cpp
// -----------------------------------------------------------------------------

// ---------------------------------- Includes ---------------------------------

#include	"APIEnvir.h"
#include	"ACAPinc.h"

#include	"HelloWorldDialog.hpp"

// --- Class definition: HelloWorldDialog --------------------------------------

HelloWorldDialog::HelloWorldDialog () :
	DG::ModalDialog (ACAPI_GetOwnResModule (), 32500, ACAPI_GetOwnResModule ()),
	closeButton	(GetReference (), ButtonID),
	helloWorldText	(GetReference (), LeftTextID),
	earthIcon	(GetReference (), IconItemID)
{
	AttachToAllItems (*this);
}

HelloWorldDialog::~HelloWorldDialog ()
{
}

void HelloWorldDialog::ButtonClicked (const DG::ButtonClickEvent& ev)
{
	if (ev.GetSource () == &closeButton)
		PostCloseRequest (Accept);
}

How it works?

You can download the whole sources of this Hello World Example Add-On here. Unzip the file and place the “HelloWorld_Example” folder into the “Examples” folder of the installed API Development Kit.
I attached the .vcxproj (for Visual Studio) and the .xcodeproj (for Xcode) project files also. So after you open the project files and click build, it should build successfully. The result will be HelloWorld_Example.apx on Windows platform and HelloWorld_Example.bundle on macOS.
Place that Add-On file into the Add-Ons folder (that folder’s name is localized, so it’s name depends on your localized version) next to ARCHICAD application.

That’s all, now you can start ARCHICAD and try “Hello World Dialog” menu item inside Test main menu.