Since ARCHICAD 23 the Dialog Manager modul was extended with a Browser control. The Browser control uses the Chromium Embedded Framework (CEF). Using this control an embedded browser can be implemented inside ARCHICAD, which can load any site from a given URL. Using JavaScript the loaded site can communicate with the C++ Add-On and through that it can retrieve information from or control ARCHICAD.
This blog post demonstrates how to use this new Browser control and how to estabilish connection with ARCHICAD from JavaScript.
I implemented a new Add-On named “Browser Control Test” with a palette (dockable modeless dialog) in ARCHICAD which will load an HTML file. Therefore this post is also a good example for “how to implement a modeless dialog in ARCHICAD”.
I encourage you to download it, build it and test it in ARCHICAD 23!
Preparations
I had to implement a new Add-On named “Browser Control Test”.
The first step of implementing a new Add-On is building up the skeleton structure of the Add-On. I created the Browser_Control folder inside the installed DevKit’s Examples folder and set up the common Add-On folder structure.
RFIX folder
RFIX folder contains the non-localizable platform independent resources (for example images). A GRC file must contain the descriptors of these non-localizable resources, so I created a text file (with UTF-8 with BOM encoding) Browser_ControlFix.grc
into the RFIX folder.
Each Add-On must have a unique identifier called MDID, since it does not require localization, my Fix.grc will contain this 'MDID'
resource. I generated a new identifier at my profile and copied the generated ids to this GRC file.
The GRC file will be processed by GRAPHISOFT Resource Converter tool (ResConv).
'MDID' 32500 "Add-On Identifier" {
628121456
719857724
}
RINT folder
RINT folder contains the localizable resources (for example strings and dialogs). I created the Browser_Control.grc
into this folder with the following content:
'STR#' 32000
: The string resource with 32000 identifier contains the main information of the Add-On: name and short description. It will be used byCheckEnvironment
function and these string will appear inside the Add-On Manager dialog in ARCHICAD to show information about my 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 “Browser Control” containing a single menu item “Selection Handler Palette”.
In ARCHICAD this menu system will look like the following way:'GDLG' 32500
: Dialog description of the dockable palette with a browser control. See the description below.
'STR#' 32000 "Add-on Name and Description" {
/* [ 1] */ "Browser Control Test"
/* [ 2] */ "Browser Control Example"
}
'STR#' 32500 "Strings for the Menu" {
/* [ ] */ "Test"
/* [ ] */ "Browser Control"
/* [ 1] */ "Selection Handler Palette^ES^EE^EI^ED^EW^E3^EL"
}
'GDLG' 32500 Palette | topCaption | close | grow 0 0 450 150 "Selection Handler" {
/* [ 1] */ Browser 0 0 450 150
}
'DLGH' 32500 DLG_32500_Browser_Palette {
1 "Browser Control" Browser_0
}
‘GDLG’ dialog descriptor
'GDLG'
resource defines a dialog. The Palette
keyword after the identifier of the 'GDLG'
resource means that it will be a palette, a dockable modeless dialog in ARCHICAD. topCaption
keyword is trivial, close
keyword means the dialog will have a close button in the corner and grow
keyword results that the dialog will be resizable both horizontally and vertically. The keywords are followed by four numbers, which describe the position and size parameters (X, Y, Width, Heigh). In case of a resizable dialog these width and height parameters determines the minimum size of the dialog. Finally, before the braces the title of the dialog can be given.
Between the braces the controls of the dialog are listed. I want only one control on my dialog, a Browser
control and it will completely fill my dialog, so it’s position is (0, 0) and it’s size is equal to the dialog’s size.'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 Browser_Control.rc2
file. The .rc2 will be processed by Windows system’s resource compiler tool. This file joins the separeted GRC resource files by including their compilation results, the .grc.rc2 files:
RFIX.mac folder
RFIX.mac folder is for the macOS platform specific resources. This folder must contain an 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. The Xcode project file will execute this script.
Src folder
Src folder contains the C/C++ source code.
In this case the Main.cpp contains the 4 required functions of the Add-On (CheckEnvironment
, RegisterInterface
, Initialize
, FreeData
). It registers a menu, and tries to show/hide the dialog when the registered menu was selected.
GSErrCode __ACENV_CALL MenuCommandHandler (const API_MenuParams *menuParams)
{
switch (menuParams->menuItemRef.menuResID) {
case BrowserPaletteMenuResId:
switch (menuParams->menuItemRef.itemIndex) {
case BrowserPaletteMenuItemIndex:
ShowOrHideBrowserPalette ();
break;
}
break;
}
return NoError;
}
API_AddonType __ACDLL_CALL CheckEnvironment (API_EnvirParams* envir)
{
RSGetIndString (&envir->addOnInfo.name, 32000, 1, ACAPI_GetOwnResModule ());
RSGetIndString (&envir->addOnInfo.description, 32000, 2, ACAPI_GetOwnResModule ());
return APIAddon_Preload;
}
GSErrCode __ACDLL_CALL RegisterInterface (void)
{
GSErrCode err = ACAPI_Register_Menu (BrowserPaletteMenuResId, 0, MenuCode_UserDef, MenuFlag_Default);
if (DBERROR (err != NoError))
return err;
return err;
}
GSErrCode __ACENV_CALL Initialize (void)
{
GSErrCode err = ACAPI_Install_MenuHandler (BrowserPaletteMenuResId, MenuCommandHandler);
if (DBERROR (err != NoError))
return err;
err = ACAPI_Notify_CatchSelectionChange (BrowserPalette::SelectionChangeHandler);
if (DBERROR (err != NoError))
return err;
err = BrowserPalette::RegisterPaletteControlCallBack ();
if (DBERROR (err != NoError))
return err;
return err;
}
GSErrCode __ACENV_CALL FreeData (void)
{
return NoError;
}
How to implement a modeless dialog in ARCHICAD
BrowserPalette
class implements a dockable modeless dialog (called palette in ARCHICAD). It’s a good example for a modeless dialog, feel free to reuse this code anytime if you want to implement one!
The BrowserPalette
class is derived from the DG::Palette
class and in the constructor it defines an own unique GUID for itself and pairs the dialog with the 'GDLG'
resource. All modeless dialog must be registered in ARCHICAD to allow ARCHICAD to show/hide/disable/enable the dialog when it’s necessary (for example if you closed ARCHICAD with opened palette, then ARCHICAD will reopen your palette automatically when you restart ARCHICAD with the last profile), use ACAPI_RegisterModelessWindow
function for this registration.
The logic behind the dialog
My plan is to implement a palette in ARCHICAD which lists the following information of the selected elements: GUID, type, element ID string. In addition it allows to remove specific elements from the selection and to add element identified by it’s GUID to the selection.
The constructor of the BrowserPalette
class calls InitBrowserControl
function. Beside initializing the starting page of the browser this function registers the ACAPI
JavaScript object and updates the selection info on the page by executing UpdateSelectedElements
JavaScript function, which is defined in the HTML code.
static const char* siteURL = "http://home.sch.bme.hu/~lorantfyt/Selection_Test.html";
static const GS::Guid paletteGuid ("{FEE27B6B-3873-4834-98B5-F0081AA4CD45}");
BrowserPalette::BrowserPalette () :
DG::Palette (ACAPI_GetOwnResModule (), BrowserPaletteResId, ACAPI_GetOwnResModule (), paletteGuid),
browser (GetReference (), BrowserId)
{
Attach (*this);
BeginEventProcessing ();
InitBrowserControl ();
}
BrowserPalette::~BrowserPalette ()
{
EndEventProcessing ();
}
void BrowserPalette::InitBrowserControl ()
{
browser.LoadURL (siteURL);
RegisterACAPIJavaScriptObject ();
UpdateSelectedElementsOnHTML ();
}
void BrowserPalette::UpdateSelectedElementsOnHTML ()
{
browser.ExecuteJS ("UpdateSelectedElements ()");
}
Upon startup this example loads the Selection_Test.html HTML page (the downloadable package contains the HTML file also). That HTML file defines an empty table and that table will be populated with the retrieved information of the selected elements from ARCHICAD using JavaScript by calling ACAPI.GetSelectedElements
function.
<html>
<head>
<title>Selection Test</title>
<script type="text/javascript">
function UpdateSelectedElements () {
ACAPI.GetSelectedElements ().then (function (elemInfos) {
...
// populate the list with the selection info
var index, len;
for (index = 0, len = elemInfos.length; index < len; ++index) {
...
removeButton.onclick = function (e) {
ACAPI.RemoveElementFromSelection (e.currentTarget.guid).then (function (res) {
});
};
...
}
...
});
}
function AddElementToSelection () {
var elemGuidToAddInput = document.getElementById ('elemGuidToAdd');
ACAPI.AddElementToSelection (elemGuidToAddInput.value).then (function (res) {
elemGuidToAddInput.value = '';
});
}
</script>
</head>
<body align="center">
...
<form>
<input type="text" id="elemGuidToAdd" size="40">
<input type="button" onclick="AddElementToSelection ()" value="+">
</form>
...
</body>
</html>
The ACAPI
JavaScript object is registered from the C++ code and 3 functions were added to it (GetSelectedElements
, AddElementToSelection
, RemoveElementFromSelection
). The given C++ lambda expressions (C++11 feature) define C++ callback functions, those will be executed when the JavaScript code calls the registered function.
void BrowserPalette::RegisterACAPIJavaScriptObject ()
{
DG::JSObject* jsACAPI = new DG::JSObject ("ACAPI");
jsACAPI->AddItem (new DG::JSFunction ("GetSelectedElements", [] (GS::Ref<DG::JSBase>) {
return ConvertToJavaScriptVariable (GetSelectedElements ());
}));
jsACAPI->AddItem (new DG::JSFunction ("AddElementToSelection", [] (GS::Ref<DG::JSBase> param) {
ModifySelection (GetStringFromJavaScriptVariable (param), AddToSelection);
return ConvertToJavaScriptVariable (true);
}));
jsACAPI->AddItem (new DG::JSFunction ("RemoveElementFromSelection", [] (GS::Ref<DG::JSBase> param) {
ModifySelection (GetStringFromJavaScriptVariable (param), RemoveFromSelection);
return ConvertToJavaScriptVariable (true);
}));
browser.RegisterAsynchJSObject (jsACAPI);
}
The SelectionChangeHandler
function collects the information of the selected elements, since it was registered by ACAPI_Notify_CatchSelectionChange
it will be called when the selection in ARCHICAD changes.
The callback associated to GetSelectedElements
JavaScript function returns the collected information converted to a JavaScript array object.
GS::Array<BrowserPalette::ElementInfo> BrowserPalette::GetSelectedElements ()
{
API_SelectionInfo selectionInfo;
GS::Array<API_Neig> selNeigs;
ACAPI_Selection_Get (&selectionInfo, &selNeigs, false, false);
BMKillHandle ((GSHandle*)&selectionInfo.marquee.coords);
GS::Array<BrowserPalette::ElementInfo> selectedElements;
for (const API_Neig& neig : selNeigs) {
API_Elem_Head elemHead = {};
elemHead.guid = neig.guid;
ACAPI_Element_GetHeader (&elemHead);
ElementInfo elemInfo;
elemInfo.guidStr = APIGuidToString (elemHead.guid);
ACAPI_Goodies (APIAny_GetElemTypeNameID, (void*)elemHead.typeID, &elemInfo.typeName);
ACAPI_Database (APIDb_GetElementInfoStringID, &elemHead.guid, &elemInfo.elemID);
selectedElements.Push (elemInfo);
}
return selectedElements;
}
void BrowserPalette::ModifySelection (const GS::UniString& elemGuidStr, BrowserPalette::SelectionModification modification)
{
ACAPI_Element_Select ({ API_Neig (APIGuidFromString (elemGuidStr.ToCStr ().Get ())) }, modification == AddToSelection);
}
It’s guaranteed that the registered JavaScript callback functions will be executed from the main thread, so it’s safe to execute any API functions inside them. The ACAPI_Element_Select
is used in the AddElementToSelection
and RemoveElementFromSelection
JavaScript callback functions to manipulate the selection in ARCHICAD.
How to test it?
Unzip the downloaded file and place the “Browser_Control” folder into the “Examples” folder of the installed API Development Kit 23.
Use the attached Visual Studio project (.vcxproj file) to build the Add-On for Windows, and the Xcode project file (.xcodeproj) for macOS platform. The result of the build will be Browser_Control.apx
on Windows platform and Browser_Control.bundle
on macOS.
Place that result Add-On file into the Add-Ons folder (note 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 the functionality of the new Browser control by selecting the Browser Control menu item inside Test main menu.
How to debug JavaScript in CEF
You are able to debug your JavaScript code in Chrome browser if you set a valid, free port for CEF debugging.
On Windows platform you need to set one of the following registry keys under Computer\HKEY_CURRENT_USER\SOFTWARE\GRAPHISOFT\Debug\DG: CefUseFloatingDebugPort, CefDebugPort or CefUseFixedDebugPort. Only one of these registry keys is used. If CefUseFloatingDebugPort is set to true then CefDebugPort and CefUseFixedDebugPort are ignored. If CefUseFloatingDebugPort is not set or false, and CefDebugPort is set and non-zero then CefUseFixedDebugPort is ignored. Note that in order to work, the debug port on each running application has to be distinct.
- CefUseFloatingDebugPort: If CefUseFloatingDebugPort is set to true, then all apps get a port number derived from their process id. (10000 + the last four digits of the process id). This is useful when running multiple instances of Archicad.
- CefDebugPort: If CefDebugPort is assigned a number, then the debug port for Archicad is that number.
- CefUseFixedDebugPort: If CefUseFixedDebugPort is set to true, then the debug port for Archicad is 9222.
On macOS platform you have to set that value in the com.graphisoft.debug plist file under the DG group.
Just start a Chrome Browser and open http://localhost:<CefDebugPort> (Note: change the <CefDebugPort> to the port you chose). There you can debug your JavaScript code: placing breakpoints and other useful features will help you to fix your code 😉