CustomGUI gadget triggered by other gadget
-
I have read through quite a few topics handling
iCustomGui
but haven't been able to find a solution.The actual gadget I need is quite complex, but all its nooks and crannies would lead us too far.
As such, I am providing an explanation using a made-up custom gadget, this in order to simplify the discussion.Following is a photoshopped composition of the said made-up gadget(s).
It's main usage will be part of a set of Attribute Manager settings for a
NodeData
derived plugin.
The main focus of the discussion is about the "Orientation" and "Reference" gadgets.The Reference gadget allows a choice of 3 alignment options (much like a group of radio buttons would). The Orientation gadget allows to select between an horizontal and vertical alignment.
When changing the Orientation, it should trigger the Reference to update its visualization to display vertical lines instead of horizontal ones. The values from Reference are the same no matter the choice of the Orientation gadget, only its visualization depends on what is selected in Orientation.I understand how to implement both gadgets, each as a separate
iCustomGui
with its ownGeUserArea
etc, ...
I also know there is a customdatatype example, but I have no need for that, as I merely need an Int32 to be stored.I also understand how to provide additional attributes for the custom gadget, which can be provided in the res file to initialize the custom gadget.
This additional attribute is what I would want for the Reference gadget, as this is the value that would represent its internal orientation value.What I don't seem to understand, nor find in the documentation is how this additional attribute can be updated after the iCustomGui's constructor has been called. To me, it seems the additional attribute can only be passed and processed during constructor:
#define CUSTOMGUI_ATTRIBUTE_ID <unique-pluginID> ReferenceCustomGui::ReferenceCustomGui(const BaseContainer & settings, CUSTOMGUIPLUGIN * t_plugin) { mUserArea.mOrientation = settings.GetInt32(CUSTOMGUI_ATTRIBUTE_ID); mUserArea.mReference = ... ... }
With such additional attribute I could pass the Orientation value to the Reference gadget, dynamically when the Orientation's value is changed, intercepted in
NodeData::SetDParameter
Bool MyNodeData::SetDParameter(GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_SET& flags) { if (!node) return FALSE; switch (id[0].id) { case ORIENTATION_GADGET_ID: { // we can perform node->SetParameter(REFERENCE_GADGET_ID, ...) // this will call the gadget's iCustomGui::SetData // but we don't want to set its value, // we want to set the additional attribute's value // .. how to ? break; } } return SUPER::SetDParameter(node, id, t_data, flags); }
-
Hello,
Why you just don't create one customGui storing both Orientation and reference ?
You could have options to show both options or only orientation if you needed.That will make your life way easier to communicate between fields. (you could have simply pointers)
Cheers,
Manuel -
@m_magalhaes
Thanks for the suggestion, but I want to keep the gadgets as separate customgui, since I also want to use them separately in other NodeData derived objects.
The triggering from one gadget to the other would be accomplished with the specified NodeData::SetDParameter of one object. Hence in other NodeData derived objects I can still use the gadgets separately. -
hello,
I agree, that's why i'm saying you could have option to show what field you need, Orientation, Reference or Both.
As you said, the values of References are the same. So you can use bits of a simple Integer to store all the combinaisons.
Your data could be a simple Int32 and you would have to check the bits to see what is active.We got the same kind of option with the date/time gadget where you can show or hide the "now" button etc.
If you want separate gadget, you will have to know what is the ID of the other one, and send a message that sound a lot more "complicated" to me.
Cheers,
Manuel -
@m_magalhaes
In this particular case I could indeed provide a single customgui.
Note however, that the "simplified" example of the Orientation and Reference was just trying to explain the situation.
What if I want a radio group as orientation, and a customgui for Reference ...I am trying to figure out if and how the additional attributes of a customgui can be used and dynamically updated.
Before I created this topic I already finalized the implementation, using a single Int32 and parsing the bits, just as you mention. But by posting this topic I was hoping for a more generic solution, which might be useful for future projects.
As far as I understand there is no way of manipulating the additional attributes of a customgui, except processing it at constructor time? And since you seem to steer the discussion into a different direction, I can only assume there is indeed no solution to manipulate these additional attributes.
-
Hello,
now i understand.
You could probably use the Message function and react to the CoreMessage sent.If you have initialize the reference gadget passing the gadgetID of the orientation, you could check the value of the other parameter and update the UserArea.
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 } } break; } } return iCustomGui::Message(msg, result);
The other way to do it is using SendParentMessage But you have to put some code in the nodeData to react to it and send a message to the other gadget.
Cheers,
Manuel -
hello,
This thread will be considered as solve tomorrow.
Cheers,
Manuel -
@m_magalhaes
I still haven't received an answer to the main question being:How to access/manipulate (from outside) the additional attributes of a customgui, except processing it at constructor time?
However, if you feel this topic is resolved, then feel free to close it.
-
hi,
Don't my previous message solved your issue, using a message.
If you change the Orientation value, it will trigger a CoreMessage and you can catch that event in the Reference as i did.
You can initialize the reference gadget with the ID of the orientation to retrieve it value and update the GeUserArea as you wish.Cheers,
Manuel -
@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 simpleCommandData
with aGeDialog
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.