@m_magalhaes
Thanks for your message-example. I hadn't quite understood it the first time, and needed a re-trigger.
Inspired by your reply I came up with this solution instead.
While it still is based on messaging, I preferred to use the SpecialEventAdd. This way, I could pass the type value via a "custom message".
Obviously the customgui needs to be set up to react accordingly to this SpecialEventAdd, but this is what I sort of expected to already have been built in into the customgui, in order to be able to react to a trigger to update its additional attributes.
Now we only can react to the attribute at constructor time.
Maybe a request for a future update/addition?
Note for others reading this topic:
The code below is just a quick solution, having collected code from different sources and examples.
It might obviously be optimized and commented in a better way.
Also, make sure to register appropriate pluginIDs, as all ones used here are for demonstration purposes only.
For the example below I followed a different route for testing purposes.
Obviously it would lead me too far to provide a fully working plugin with NodeData etc ...
As such, I wen for a simple CommandData with a GeDialog containing a set of radio buttons and a customgui gadget.
The radio buttons allow to switch between black/white and r/g/b and c/m/y/k. With each selection the customgui displays a different set of options, showing a colored rectangle per option.
The code is R20, but easily adjustable for R21.
Main.cpp
// ========================
// Cinema 4D C++ plugin
//
// PluginName: Test
// Dummy "empty" plugin
// ========================
// Main.cpp
#include "c4d.h"
// === Registered pluginIDs ===
#define MYCOMMAND_PLUGIN_ID 1000000 // DUMMY VALUE for demonstration purposes only !!!
#define CUSTOMGUI_GADGET_ID 1000002 // DUMMY VALUE for demonstration purposes only !!!
// a specific message ID to trigger the gadget to set its type
#define CUSTOMGUI_GADGET_SETTYPEMSG_ID 100
// the gadget IDs
#define RADIO_BUTTONS 10000
#define CUSTOMGUI_GADGET 10010
extern Bool RegisterGadget();
// ====================================
// GeDialog
// ====================================
class MyDialog : public GeDialog
{
public:
MyDialog(void) {}
virtual ~MyDialog(void) {}
virtual Bool CreateLayout(void);
virtual Bool InitValues(void);
virtual void DestroyWindow(void);
virtual Bool Command(Int32 id, const BaseContainer& msg);
virtual Int32 Message(const BaseContainer& msg, BaseContainer& result);
};
Bool MyDialog::CreateLayout(void)
{
Bool res = GeDialog::CreateLayout();
// when using a GeLoadString(<string-id>)
// strings need to be defined in the main string resources, not in the dialogs subfolder
SetTitle("Test Dialog"_s);
GroupBegin(0, BFH_SCALEFIT | BFV_SCALEFIT, 1, 0, ""_s, 0);
{
GroupBorderSpace(4, 4, 4, 4);
GroupBegin(0, BFH_LEFT, 2, 0, maxon::String(), 0);
{
// 3 radio buttons vertically
AddRadioGroup(RADIO_BUTTONS, BFV_SCALEFIT, 0, 3);
AddChild(RADIO_BUTTONS, 2, "BW"_s);
AddChild(RADIO_BUTTONS, 3, "RGB"_s);
AddChild(RADIO_BUTTONS, 4, "CMYK"_s);
// the custom gadget
BaseContainer bc;
AddCustomGui(CUSTOMGUI_GADGET, CUSTOMGUI_GADGET_ID, String(), 0, 0, 0, bc);
}
GroupEnd();
}
GroupEnd();
return res;
}
Bool MyDialog::InitValues(void)
{
// first call the parent instance
if (!GeDialog::InitValues())
return false;
// do our thing ...
Int32 typevalue = 2;
SetInt32(RADIO_BUTTONS, typevalue); // set default to black and white
// and inform the gadget about it
SpecialEventAdd(CUSTOMGUI_GADGET_ID, CUSTOMGUI_GADGET_SETTYPEMSG_ID, typevalue);
return true;
}
void MyDialog::DestroyWindow(void) {}
Bool MyDialog::Command(Int32 id, const BaseContainer& msg)
{
if (id == RADIO_BUTTONS)
{
Int32 typevalue;
GetInt32(RADIO_BUTTONS, typevalue);
// send this to the customgui gadget to update its representation,
// using an EventAdd will trigger a "regular" EVMSG_CHANGE
// but we prefer to provide a SpecialEventAdd, as such we:
// 1. avoid that the gadget is triggered by every EVMSG_CHANGE
// 2. allow to specify a specific value to change the type to
//EventAdd();
SpecialEventAdd(CUSTOMGUI_GADGET_ID, CUSTOMGUI_GADGET_SETTYPEMSG_ID, typevalue);
}
return true;
}
Int32 MyDialog::Message(const BaseContainer& msg, BaseContainer& result)
{
return GeDialog::Message(msg, result);
}
// ====================================
// CommandData
// ====================================
class MyCommand : public CommandData
{
INSTANCEOF(MyCommand, CommandData)
public:
MyDialog dlg;
public:
virtual Bool Execute(BaseDocument* doc);
};
Bool MyCommand::Execute(BaseDocument* doc)
{
if (dlg.IsOpen() == false)
dlg.Open(DLG_TYPE::ASYNC, MYCOMMAND_PLUGIN_ID, -1, -1, 300, 200, 0);
return true;
}
Bool RegisterMyCommand(void)
{
return RegisterCommandPlugin(MYCOMMAND_PLUGIN_ID, "Test"_s, 0, AutoBitmap("icon.png"_s), "Test"_s, NewObjClear(MyCommand));
}
// ====================================
// Plugin Main
// ====================================
Bool PluginStart(void)
{
ApplicationOutput("Test"_s);
RegisterMyCommand();
RegisterGadget();
return true;
}
void PluginEnd(void)
{
}
Bool PluginMessage(Int32 id, void * data)
{
switch (id) {
case C4DPL_INIT_SYS:
if (!g_resource.Init())
return false;
return true;
case C4DMSG_PRIORITY:
return true;
case C4DPL_BUILDMENU:
break;
case C4DPL_ENDACTIVITY:
return true;
}
return false;
}
CustomGUI_Gadget.cpp
// CustomGUI_Gadget.cpp
// The custom gadget is a sort of horizontal graphical radio button group
// the number of buttons is dependent the gadget type
// type = 0 -> not initialized, no buttons
// type = 2 has 2 buttons (black and white)
// type = 3 has 3 buttons (red, green, blue)
// type = 4 has 4 buttons (cyan, magenta, yellow, black)
#include "c4d.h"
#include "lib_clipmap.h"
const Int32 kItemSize = 30; // the size of each "button"
// === Registered pluginIDs ===
#define CUSTOMGUI_GADGET_ID 1000002 // DUMMY VALUE for demonstration purposes only !!!
#define CUSTOMGUI_GADGET_ATTRIBUTE_ID 1000003 // DUMMY VALUE for demonstration purposes only !!!
#define USERAREA_ID 10001 // The ID of the UserArea GUI element.
// a specific message ID to trigger the gadget to set its type
#define CUSTOMGUI_GADGET_SETTYPEMSG_ID 100
//---------------------------
// The user area used to display the custom datatype
//---------------------------
class GadgetUserArea : public GeUserArea
{
public:
GadgetUserArea();
virtual ~GadgetUserArea();
virtual Bool Init();
virtual Bool InitValues();
virtual Bool GetMinSize(Int32& w, Int32& h);
virtual void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg);
virtual Bool InputEvent(const BaseContainer& msg);
Int32 mSelection;
Int32 mType;
Vector bw[2];
Vector rgb[3];
Vector cmyk[4];
// use a GeClipMap for drawing
AutoAlloc<GeClipMap> mClipmap;
};
GadgetUserArea::GadgetUserArea()
{
mSelection = 0;
mType = 0;
}
GadgetUserArea::~GadgetUserArea()
{
}
Bool GadgetUserArea::Init()
{
bw[0] = Vector(0);
bw[1] = Vector(255);
rgb[0] = Vector(255, 0, 0);
rgb[1] = Vector(0, 255, 0);
rgb[2] = Vector(0, 0, 255);
cmyk[0] = Vector(0, 255, 255);
cmyk[1] = Vector(255, 0, 255);
cmyk[2] = Vector(255, 255, 0);
cmyk[3] = Vector(0);
return true;
}
Bool GadgetUserArea::InitValues()
{
return true;
}
Bool GadgetUserArea::GetMinSize(Int32& w, Int32& h)
{
w = kItemSize * 4;
h = kItemSize;
return true;
}
void GadgetUserArea::DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)
{
OffScreenOn();
if (!mClipmap)
return;
const Int32 w = GetWidth();
const Int32 h = GetHeight();
mClipmap->Init(w, h, 32);
mClipmap->BeginDraw();
// background
Int32 r, g, b;
GetColorRGB(COLOR_BG, r, g, b);
mClipmap->SetColor(r, g, b, 255);
mClipmap->FillRect(x1, y1, x2, y2);
Vector color;
if (mType != 0)
{
// draw the possible options as background,
// then draw the current selected option on top
switch (mType)
{
case 2: // black and white
{
for (Int32 col = 0; col < 2; ++col)
{
color = bw[col];
mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255);
Int32 x = col * kItemSize;
mClipmap->FillRect(x + 2, 2, x + kItemSize - 2, kItemSize - 2);
}
break;
}
case 3: // RGB
{
for (Int32 col = 0; col < 3; ++col)
{
color = rgb[col];
mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255);
Int32 x = col * kItemSize;
mClipmap->FillRect(x + 2, 2, x + kItemSize - 2, kItemSize - 2);
}
break;
}
case 4: // CMYK
{
for (Int32 col = 0; col < 4; ++col)
{
color = cmyk[col];
mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255);
Int32 x = col * kItemSize;
mClipmap->FillRect(x + 2, 2, x + kItemSize - 2, kItemSize - 2);
}
break;
}
}
if (mSelection >= 0)
{
// orange "selection" color
GetColorRGB(COLOR_TEXTFOCUS, r, g, b);
mClipmap->SetColor(r, g, b, 255);
Int32 x = mSelection * kItemSize;
mClipmap->Rect(x, 0, x + kItemSize, kItemSize);
mClipmap->Rect(x + 1, 1, x + kItemSize - 1, kItemSize - 1);
}
}
mClipmap->EndDraw();
DrawBitmap(mClipmap->GetBitmap(), 0, 0, w, h, 0, 0, w, h, BMP_ALLOWALPHA);
}
Bool GadgetUserArea::InputEvent(const BaseContainer& msg)
{
// check the input device
switch (msg.GetInt32(BFM_INPUT_DEVICE))
{
// some mouse interaction
case BFM_INPUT_MOUSE:
{
// get the cursor position
Int32 mx = msg.GetInt32(BFM_INPUT_X);
Int32 my = msg.GetInt32(BFM_INPUT_Y);
Global2Local(&mx, &my);
// Note that the origin of a GeUserArea is upperleft
// (which is equal to the 4th quadrant of cartesian coordinate system)
mSelection = mx / kItemSize;
// inform the parent that the data has changed
BaseContainer m(BFM_ACTION);
m.SetInt32(BFM_ACTION_ID, GetId());
m.SetData(BFM_ACTION_VALUE, mSelection);
SendParentMessage(m);
//Redraw();
return true;
}
}
return false;
}
//----------------------------------------------------------------------------------------
// A custom GUI to display the ReferencePoint
//----------------------------------------------------------------------------------------
class iGadget : public iCustomGui
{
INSTANCEOF(iGadget, iCustomGui)
private:
// The current tristate.
Bool mTristate;
// instance of the userarea to display the ReferencePointer
GadgetUserArea mUA;
public:
iGadget(const BaseContainer &settings, CUSTOMGUIPLUGIN *plugin);
virtual Bool CreateLayout();
virtual Bool InitValues();
virtual Bool Command(Int32 id, const BaseContainer &msg);
virtual Int32 Message(const BaseContainer &msg, BaseContainer &result);
virtual Bool SetData(const TriState<GeData> &tristate);
virtual TriState<GeData> GetData();
virtual void CustomGuiRedraw();
};
iGadget::iGadget(const BaseContainer &settings, CUSTOMGUIPLUGIN *plugin) : iCustomGui(settings, plugin)
{
mUA.mType = settings.GetInt32(CUSTOMGUI_GADGET_ATTRIBUTE_ID);
//mUA.mType = mType; // pass along the type to the userarea
// Defining default values
mTristate = false;
};
Bool iGadget::CreateLayout()
{
GroupBegin(1000, BFH_SCALEFIT | BFV_FIT, 1, 1, String(), 0);
{
GroupSpace(0, 0);
// Attach the User Area to the custom GUI
AddUserArea(USERAREA_ID, BFH_SCALEFIT, 0, 0);
AttachUserArea(mUA, USERAREA_ID);
}
GroupEnd();
return SUPER::CreateLayout();
};
Bool iGadget::InitValues()
{
// The data and it's tristate are handled automatically.
this->SetInt32(USERAREA_ID, mUA.mSelection, mTristate);
return true;
};
Bool iGadget::Command(Int32 id, const BaseContainer &msg)
{
switch (id)
{
case USERAREA_ID:
{
// The Gadget was changed.
// Update GUI
this->InitValues();
// Send message to parent object to update the parameter value.
BaseContainer m(BFM_ACTION);
m.SetInt32(BFM_ACTION_ID, GetId());
m.SetData(BFM_ACTION_VALUE, msg.GetInt32(BFM_ACTION_VALUE));
SendParentMessage(m);
return true;
break;
}
}
return SUPER::Command(id, msg);
}
Int32 iGadget::Message(const BaseContainer &msg, BaseContainer &result)
{
switch (msg.GetId())
{
case BFM_CORE_MESSAGE:
{
if (!CheckCoreMessage(msg))
{
break;
}
else
{
if (msg.GetInt32(BFM_CORE_ID) == EVMSG_CHANGE)
{
// get the parameter and update the userarea if needed
//ApplicationOutput("iGadget::Message() detected EVMSG_CHANGE");
}
if (msg.GetInt32(BFM_CORE_ID) == CUSTOMGUI_GADGET_ID)
{
UInt par1 = (UInt)msg.GetVoid(BFM_CORE_PAR1);
UInt par2 = (UInt)msg.GetVoid(BFM_CORE_PAR2);
//ApplicationOutput("iGadget::Message() detected a SpecialEventAdd @ @", par1, par2);
if (par1 == CUSTOMGUI_GADGET_SETTYPEMSG_ID)
{
// The SpecialEventAdd which is responsable for this message
// uses the CUSTOMGUI_GADGET_ID as message ID,
// a specific value CUSTOMGUI_GADGET_SETTYPEMSG_ID as first parameter to indicate we want to set the gadget's type,
// and finally the type value as the second parameter
// -> SpecialEventAdd(CUSTOMGUI_GADGET_ID, CUSTOMGUI_GADGET_SETTYPEMSG_ID, typevalue);
// accept the type change for the gadget and trigger an update
// (see iGadget::Command, where user interaction in the userarea triggers an update)
mUA.mType = (Int32)par2;
// reset the selection to avoid "out of range" depending the type
mUA.mSelection = 0;
CustomGuiRedraw();
}
}
}
break;
}
}
return SUPER::Message(msg, result);
}
Bool iGadget::SetData(const TriState<GeData> &tristate)
{
// The data is changed from the outside.
mUA.mSelection = tristate.GetValue().GetInt32();
mTristate = tristate.GetTri();
this->InitValues();
// need to update the userarea
this->mUA.Redraw();
return true;
};
TriState<GeData> iGadget::GetData()
{
// The data is requested from the outside.
TriState<GeData> tri;
tri.Add(mUA.mSelection);
return tri;
};
void iGadget::CustomGuiRedraw()
{
this->mUA.Redraw();
}
//----------------------------------------------------------------------------------------
// This CustomGuiData class registers a new custom GUI for the ReferencePoint datatype.
//----------------------------------------------------------------------------------------
class Gadget : public CustomGuiData
{
public:
virtual Int32 GetId();
virtual CDialog* Alloc(const BaseContainer& settings);
virtual void Free(CDialog* dlg, void* userdata);
virtual const Char* GetResourceSym();
virtual CustomProperty* GetProperties();
virtual Int32 GetResourceDataType(Int32*& table);
};
static Int32 g_stringtable[] = { DTYPE_LONG }; //< This array defines the applicable datatypes.
static CustomProperty g_GadgetType[] =
{
{ CUSTOMTYPE::LONG, CUSTOMGUI_GADGET_ATTRIBUTE_ID, "TYPE" },
{ CUSTOMTYPE::END, 0, "" }
};
Int32 Gadget::GetId()
{
return CUSTOMGUI_GADGET_ID;
};
CDialog* Gadget::Alloc(const BaseContainer& settings)
{
// Creates and returns a new sub-dialog.
iGadget* dlg = NewObjClear(iGadget, settings, GetPlugin());
if (!dlg)
return nullptr;
CDialog *cdlg = dlg->Get();
if (!cdlg)
return nullptr;
return cdlg;
};
void Gadget::Free(CDialog* dlg, void* userdata)
{
// Destroys the given subdialog.
if (!dlg || !userdata)
return;
iGadget* sub = static_cast<iGadget*>(userdata);
DeleteObj(sub);
};
const Char* Gadget::GetResourceSym()
{
// Returns the resource symbol. This symbol can be used in resource files in combination with "CUSTOMGUI".
return "TYPE";
};
CustomProperty* Gadget::GetProperties()
{
// This method can return a pointer to a data structure holding various additional properties.
return g_GadgetType;
};
Int32 Gadget::GetResourceDataType(Int32*& table)
{
// Returns the applicable datatypes defined in the stringtable array.
table = g_stringtable;
return sizeof(g_stringtable) / sizeof(Int32);
};
Bool RegisterGadget()
{
// only register the custom GUI when not already registered by another plugin
if (IsLibraryInstalled(CUSTOMGUI_GADGET_ID))
return true;
static BaseCustomGuiLib myGadgetLib;
ClearMem(&myGadgetLib, sizeof(myGadgetLib));
FillBaseCustomGui(myGadgetLib);
if (!InstallLibrary(CUSTOMGUI_GADGET_ID, &myGadgetLib, 1000, sizeof(myGadgetLib)))
return false;
if (!RegisterCustomGuiPlugin(/*GeLoadString(IDS_CUSTOMGUISTRING)*/"Gadget"_s, 0, NewObjClear(Gadget)))
return false;
return true;
}
With this I guess the topic can be closed. But feel free to provide further comments if I overlooked something.