diff --git a/cwUi.cpp b/cwUi.cpp index af383f7..a347391 100644 --- a/cwUi.cpp +++ b/cwUi.cpp @@ -2,6 +2,7 @@ #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" +#include "cwFileSys.h" #include "cwThread.h" #include "cwObject.h" #include "cwWebSock.h" @@ -37,13 +38,14 @@ namespace cw typedef struct ui_str { - unsigned eleAllocN; // size of eleA[] - unsigned eleN; // count of ele's in use - ele_t** eleA; // eleA[ eleAllocN ] + unsigned eleAllocN; // size of eleA[] + unsigned eleN; // count of ele's in use + ele_t** eleA; // eleA[ eleAllocN ] uiCallback_t uiCbFunc; // app. cb func void* uiCbArg; // app. cb func arg. sendCallback_t sendCbFunc; void* sendCbArg; + object_t* uiRsrc; // default ui resource object appIdMapRecd_t* appIdMap; // map of application parent/child/js id's char* buf; // buf[bufN] output message formatting buffer unsigned bufN; // @@ -87,7 +89,8 @@ namespace cw mem::release(p->sessA); mem::release(p->eleA); - mem::release(p->buf); + mem::release(p->buf); + p->uiRsrc->free(); mem::release(p); return rc; @@ -543,10 +546,10 @@ namespace cw rc_t _createFromObj( ui_t* p, const object_t* o, unsigned wsSessId, unsigned parentUuId ) { - rc_t rc = kOkRC; - const object_t* po = nullptr; - ele_t* parentEle = nullptr; - char* eleName = nullptr; + rc_t rc = kOkRC; + const object_t* po = nullptr; + ele_t* parentEle = nullptr; + char* eleName = nullptr; // locate the the 'parent' ele name value object if((po = o->find("parent",kNoRecurseFl | kOptionalFl)) == nullptr ) @@ -726,6 +729,15 @@ namespace cw return rc; } + // Instantiate any resource elements refered to by the ui cfg 'uiCfgFn'. + rc_t _initDefaultUiRsrc( ui_t* p, unsigned wsSessId ) + { + rc_t rc = kOkRC; + if( p->uiRsrc != nullptr ) + if((rc = _createFromObj( p, p->uiRsrc, wsSessId, kInvalidId )) != kOkRC ) + rc = cwLogError(rc,"Default UI creation failed."); + return rc; + } } } @@ -736,6 +748,7 @@ cw::rc_t cw::ui::create( void* sendCbArg, uiCallback_t uiCbFunc, void* uiCbArg, + const object_t* uiRsrc, const appIdMap_t* appIdMapA, unsigned appIdMapN, unsigned fmtBufByteN ) @@ -764,7 +777,8 @@ cw::rc_t cw::ui::create( p->sendCbArg = sendCbArg; p->buf = mem::allocZ(fmtBufByteN); p->bufN = fmtBufByteN; - + p->uiRsrc = uiRsrc == nullptr ? nullptr : uiRsrc->duplicate(); + // create the root element if((ele = _createEle(p, nullptr, kRootAppId, "uiDivId" )) == nullptr || ele->uuId != kRootUuId ) { @@ -870,6 +884,9 @@ cw::rc_t cw::ui::onReceive( handle_t h, unsigned wsSessId, const void* msg, unsi switch( opId ) { case kInitOpId: + // if the app cfg included a reference to a UI resource file then instantiate it here + _initDefaultUiRsrc( p, wsSessId ); + // Pass on the 'init' msg to the app. p->uiCbFunc( p->uiCbArg, wsSessId, opId, kInvalidId, kInvalidId, kInvalidId, nullptr ); break; @@ -1203,11 +1220,81 @@ namespace cw } } +cw::rc_t cw::ui::ws::parseArgs( const object_t& o, args_t& args, const char* object_label ) +{ + rc_t rc = kOkRC; + const object_t* op = &o; + char* uiCfgFn = nullptr; + + memset(&args,0,sizeof(args)); + + // if no 'ui' cfg record was given then skip + if( object_label == nullptr ) + op = &o; + else + if((op = o.find(object_label)) == nullptr ) + return cwLogError(kLabelNotFoundRC,"The ui configuration label '%s' was not found.", cwStringNullGuard(object_label)); + + if((rc = op->getv( + "physRootDir", args.physRootDir, "dfltPageFn", args.dfltPageFn, "port", args.port, "rcvBufByteN", args.rcvBufByteN, + "xmtBufByteN", args.xmtBufByteN, "fmtBufByteN", args.fmtBufByteN, "websockTimeOutMs", args.wsTimeOutMs, "uiCfgFn", uiCfgFn )) != kOkRC ) + { + rc = cwLogError(rc,"'ui' cfg. parse failed."); + } + + // if a default UI resource script was given then convert it into an object + if( uiCfgFn != nullptr ) + { + char* fn = filesys::makeFn( args.physRootDir, uiCfgFn, nullptr, nullptr ); + + if((rc = objectFromFile(fn,args.uiRsrc)) != kOkRC ) + rc = cwLogError(rc,"An error occurred while parsing the UI resource script in '%s'.", cwStringNullGuard(uiCfgFn)); + + mem::release(fn); + } + + return rc; +} + +cw::rc_t cw::ui::ws::releaseArgs( args_t& args ) +{ + if( args.uiRsrc != nullptr ) + args.uiRsrc->free(); + return kOkRC; +} + +cw::rc_t cw::ui::ws::create( handle_t& h, + const args_t& args, + void* cbArg, + uiCallback_t uiCbFunc, + const object_t* uiRsrc, + const appIdMap_t* appIdMapA, + unsigned appIdMapN, + websock::cbFunc_t wsCbFunc ) +{ + return create(h, + args.port, + args.physRootDir, + cbArg, + uiCbFunc, + uiRsrc, + appIdMapA, + appIdMapN, + wsCbFunc, + args.dfltPageFn, + args.wsTimeOutMs, + args.rcvBufByteN, + args.xmtBufByteN, + args.fmtBufByteN ); +} + + cw::rc_t cw::ui::ws::create( handle_t& h, unsigned port, const char* physRootDir, void* cbArg, uiCallback_t uiCbFunc, + const object_t* uiRsrc, const appIdMap_t* appIdMapA, unsigned appIdMapN, websock::cbFunc_t wsCbFunc, @@ -1242,7 +1329,7 @@ cw::rc_t cw::ui::ws::create( handle_t& h, } // create the ui - if((rc = ui::create(p->uiH, _webSockSend, p, uiCbFunc, cbArg, appIdMapA, appIdMapN, fmtBufByteN )) != kOkRC ) + if((rc = ui::create(p->uiH, _webSockSend, p, uiCbFunc, cbArg, uiRsrc, appIdMapA, appIdMapN, fmtBufByteN )) != kOkRC ) { cwLogError(rc,"UI object create failed."); goto errLabel; @@ -1281,7 +1368,7 @@ cw::rc_t cw::ui::ws::destroy( handle_t& h ) return rc; } -cw::rc_t cw::ui::ws::exec( handle_t h, unsigned timeOutMs ) +cw::rc_t cw::ui::ws::exec( handle_t h ) { rc_t rc = kOkRC; ui_ws_t* p = _handleToPtr(h); @@ -1327,7 +1414,6 @@ namespace cw { ws::handle_t wsUiH; thread::handle_t thH; - unsigned wsTimeOutMs; } ui_ws_srv_t; ui_ws_srv_t* _handleToPtr(handle_t h ) @@ -1352,7 +1438,7 @@ namespace cw ui_ws_srv_t* p = static_cast(arg); rc_t rc; - if((rc = ws::exec(p->wsUiH,p->wsTimeOutMs)) != kOkRC ) + if((rc = ws::exec(p->wsUiH)) != kOkRC ) { cwLogError(rc,"Websocket UI exec failed."); } @@ -1368,6 +1454,7 @@ cw::rc_t cw::ui::srv::create( handle_t& h, const char* physRootDir, void* cbArg, uiCallback_t uiCbFunc, + const object_t* uiRsrc, const appIdMap_t* appIdMapA, unsigned appIdMapN, websock::cbFunc_t wsCbFunc, @@ -1383,7 +1470,7 @@ cw::rc_t cw::ui::srv::create( handle_t& h, ui_ws_srv_t* p = mem::allocZ(); - if((rc = ws::create(p->wsUiH, port, physRootDir, cbArg, uiCbFunc, appIdMapA, appIdMapN, wsCbFunc, dfltPageFn, websockTimeOutMs, rcvBufByteN, xmtBufByteN, fmtBufByteN )) != kOkRC ) + if((rc = ws::create(p->wsUiH, port, physRootDir, cbArg, uiCbFunc, uiRsrc,appIdMapA, appIdMapN, wsCbFunc, dfltPageFn, websockTimeOutMs, rcvBufByteN, xmtBufByteN, fmtBufByteN )) != kOkRC ) { cwLogError(rc,"The websock UI creationg failed."); goto errLabel; @@ -1395,8 +1482,6 @@ cw::rc_t cw::ui::srv::create( handle_t& h, goto errLabel; } - p->wsTimeOutMs = websockTimeOutMs; - h.set(p); errLabel: @@ -1407,7 +1492,7 @@ cw::rc_t cw::ui::srv::create( handle_t& h, } cw::rc_t cw::ui::srv::create( handle_t& h, - const args_t& args, + const ws::args_t& args, void* cbArg, uiCallback_t uiCbFunc, const appIdMap_t* appIdMapA, @@ -1419,13 +1504,14 @@ cw::rc_t cw::ui::srv::create( handle_t& h, args.physRootDir, cbArg, uiCbFunc, + args.uiRsrc, appIdMapA, appIdMapN, wsCbFunc, - args.dfltHtmlPageFn, - args.timeOutMs, - args.recvBufByteN, - args.xmitBufByteN, + args.dfltPageFn, + args.wsTimeOutMs, + args.rcvBufByteN, + args.xmtBufByteN, args.fmtBufByteN ); } diff --git a/cwUi.h b/cwUi.h index 9d1facd..7dbfb81 100644 --- a/cwUi.h +++ b/cwUi.h @@ -9,63 +9,14 @@ namespace cw { typedef handle handle_t; - enum - { - kHttpProtocolId = 1, - kUiProtocolId = 2 - }; - - typedef enum - { - kInvalidOpId, - kConnectOpId, - kInitOpId, - kValueOpId, - kEchoOpId, - kIdleOpId, - kDisconnectOpId - } opId_t; - typedef enum - { - kInvalidTId, - kBoolTId, - kIntTId, - kUIntTId, - kFloatTId, - kDoubleTId, - kStringTId - } dtypeId_t; - - - enum - { - kRootUuId = 0, - kRootAppId, - }; - - typedef struct - { - dtypeId_t tid; - union - { - bool b; - int i; - unsigned u; - float f; - double d; - const char* s; - } u; - } value_t; - - typedef struct appIdMap_str - { - unsigned parentAppId; - unsigned appId; - const char* eleName; - } appIdMap_t; - + // Callback for application notification. + // (e.g. the GUI changed the value of a UI element) typedef rc_t (*uiCallback_t)( void* cbArg, unsigned wsSessId, opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* value ); + + // Callback with messages for the GUI as JSON strings + // { "op":"create", "type":<>, "appId":<>, "parentUuId":<>, "name":<> (type specific fields) } + // { "op":""value", "uuid":<> "value":<> } typedef rc_t (*sendCallback_t)( void* cbArg, unsigned wsSessId, const void* msg, unsigned msgByteN ); rc_t create( @@ -74,6 +25,7 @@ namespace cw void* sendCbArg, uiCallback_t uiCbFunc, void* uiCbArg, + const object_t* uiRsrc = nullptr, const appIdMap_t* appIdMapA = nullptr, unsigned appIdMapN = 0, unsigned fmtBufByteN = 4096 ); @@ -87,7 +39,7 @@ namespace cw rc_t onDisconnect( handle_t h, unsigned wsSessId ); rc_t onReceive( handle_t h, unsigned wsSessId, const void* msg, unsigned byteN ); - unsigned findElementAppId( handle_t h, unsigned parentUuId, const char* eleName ); + unsigned findElementAppId( handle_t h, unsigned parentUuId, const char* eleName ); unsigned findElementUuId( handle_t h, unsigned parentUuId, const char* eleName ); unsigned findElementUuId( handle_t h, unsigned parentUuId, unsigned appId ); const char* findElementName( handle_t h, unsigned uuId ); @@ -124,6 +76,7 @@ namespace cw // Register parent/child/name app id's rc_t registerAppIdMap( handle_t h, const appIdMap_t* map, unsigned mapN ); + // Send a value from the application to the UI via a JSON messages. rc_t sendValueBool( handle_t h, unsigned wsSessId, unsigned uuId, bool value ); rc_t sendValueInt( handle_t h, unsigned wsSessId, unsigned uuId, int value ); rc_t sendValueUInt( handle_t h, unsigned wsSessId, unsigned uuId, unsigned value ); @@ -135,11 +88,36 @@ namespace cw { typedef handle handle_t; + typedef struct args_str + { + const char* physRootDir; + const char* dfltPageFn; + object_t* uiRsrc; + unsigned port; + unsigned rcvBufByteN; + unsigned xmtBufByteN; + unsigned fmtBufByteN; + unsigned wsTimeOutMs; + } args_t; + + rc_t parseArgs( const object_t& o, args_t& args, const char* object_label=nullptr ); + rc_t releaseArgs( args_t& args ); + + rc_t create( handle_t& h, + const args_t& args, + void* cbArg, + uiCallback_t uiCbFunc, + const object_t* uiRsrc = nullptr, + const appIdMap_t* appIdMapA = nullptr, + unsigned appIdMapN = 0, + websock::cbFunc_t wsCbFunc = nullptr ); + rc_t create( handle_t& h, unsigned port, const char* physRootDir, void* cbArg, uiCallback_t uiCbFunc, + const object_t* uiRsrc = nullptr, const appIdMap_t* appIdMapA = nullptr, unsigned appIdMapN = 0, websock::cbFunc_t wsCbFunc = nullptr, @@ -153,7 +131,7 @@ namespace cw // This function should be called periodically to send and receive // queued messages to and from the websocket. - rc_t exec( handle_t h, unsigned timeOutMs ); + rc_t exec( handle_t h ); // This function executes the internal default websock callback function. // It is useful if the user provides a custom websock callback function @@ -170,23 +148,12 @@ namespace cw namespace srv { - typedef struct args_str - { - const char* physRootDir; - const char* dfltHtmlPageFn; - unsigned port; - unsigned timeOutMs; - unsigned recvBufByteN; - unsigned xmitBufByteN; - unsigned fmtBufByteN; - } args_t; - typedef handle handle_t; rc_t create( handle_t& h, - const args_t& args, + const ws::args_t& args, void* cbArg, - uiCallback_t uiCbFunc, + uiCallback_t uiCbFunc, const appIdMap_t* appIdMapA = nullptr, unsigned appIdMapN = 0, websock::cbFunc_t wsCbFunc = nullptr ); @@ -196,6 +163,7 @@ namespace cw const char* physRootDir, void* cbArg, uiCallback_t uiCbFunc, + const object_t* uiRsrc = nullptr, const appIdMap_t* appIdMapA = nullptr, unsigned appIdMapN = 0, websock::cbFunc_t wsCbFunc = nullptr, diff --git a/cwUiDecls.h b/cwUiDecls.h index f173de4..dc2136f 100644 --- a/cwUiDecls.h +++ b/cwUiDecls.h @@ -5,6 +5,61 @@ namespace cw { namespace ui { + enum + { + kHttpProtocolId = 1, + kUiProtocolId = 2 + }; + + typedef enum + { + kInvalidOpId, + kConnectOpId, // A new user interface instance was connected + kInitOpId, // A user interface instance was created and is available (new ui elements can now be added) + kValueOpId, // Used by the user interface instance to send a value of a ui element to the application. + kEchoOpId, // Used by the user interface instance to request the current value of a ui element from the application. + kIdleOpId, // The application is idle and waiting for the next event from the ui instance. + kDisconnectOpId // A user interface instance was disconnected + } opId_t; + + typedef enum + { + kInvalidTId, + kBoolTId, + kIntTId, + kUIntTId, + kFloatTId, + kDoubleTId, + kStringTId + } dtypeId_t; + + + enum + { + kRootUuId = 0, + kRootAppId, + }; + + typedef struct + { + dtypeId_t tid; + union + { + bool b; + int i; + unsigned u; + float f; + double d; + const char* s; + } u; + } value_t; + + typedef struct appIdMap_str + { + unsigned parentAppId; + unsigned appId; + const char* eleName; + } appIdMap_t; } } diff --git a/cwUiTest.cpp b/cwUiTest.cpp index 9545f81..f4edc74 100644 --- a/cwUiTest.cpp +++ b/cwUiTest.cpp @@ -15,11 +15,13 @@ namespace cw { typedef struct ui_test_str { - const char* uiCfgFn; - srv::handle_t wsUiSrvH; + + //const char* uiCfgFn; // Resource file name + srv::handle_t wsUiSrvH; // std::atomic quitFl; - + + // Application values bool appCheckFl; unsigned appSelOptAppId; int appInteger; @@ -33,8 +35,10 @@ namespace cw } ui_test_t; + // Application Id's for UI elements enum { + // Programatically created UI elements kDivId, kBtnId, kCheckId, @@ -48,6 +52,7 @@ namespace cw kFloatId, kProgressId, + // Resource Based elements kPanelDivId, kPanelBtn1Id, kPanelCheck1Id, @@ -70,8 +75,7 @@ namespace cw handle_t uiH = srv::uiHandle(p->wsUiSrvH); - - //registerAppIdMap(uiH, mapA, sizeof(mapA)/sizeof(mapA[0])); + // Create a UI elements programatically. if((rc = createDiv( uiH, divUuId, wsSessId, kInvalidId, "myDivId", kDivId, "divClass", "My Panel" )) != kOkRC ) goto errLabel; @@ -106,8 +110,8 @@ namespace cw if((rc = createProg( uiH, uuid, wsSessId, divUuId, "myProgressId", kProgressId, "progressClass", "Progress", 0, 10, 5 )) != kOkRC ) goto errLabel; - if((rc = createFromFile( uiH, p->uiCfgFn, wsSessId )) != kOkRC ) - goto errLabel; + //if((rc = createFromFile( uiH, p->uiCfgFn, wsSessId )) != kOkRC ) + // goto errLabel; errLabel: return rc; @@ -290,20 +294,12 @@ namespace cw } } -cw::rc_t cw::ui::test( ) +cw::rc_t cw::ui::test( const object_t* cfg ) { - rc_t rc = kOkRC; - const char* physRootDir = "/home/kevin/src/cwtest/src/libcw/html/uiTest"; - const char* dfltPageFn = "index.html"; - int port = 5687; - unsigned rcvBufByteN = 2048; - unsigned xmtBufByteN = 2048; - unsigned fmtBufByteN = 4096; - unsigned websockTimeOutMs = 50; - ui_test_t* app = mem::allocZ(); - - app->quitFl.store(false); - + rc_t rc = kOkRC; + ui::ws::args_t args = {}; + + // Application Id's for the resource based UI elements. appIdMap_t mapA[] = { { kRootAppId, kPanelDivId, "panelDivId" }, @@ -316,11 +312,28 @@ cw::rc_t cw::ui::test( ) { kSelId, kOpt1Id, "myOpt1" }, { kSelId, kOpt2Id, "myOpt2" }, { kSelId, kOpt3Id, "myOpt3" }, - }; unsigned mapN = sizeof(mapA)/sizeof(mapA[0]); + ui_test_t* app = mem::allocZ(); + + if( cfg == nullptr ) + { + cwLogError(kInvalidArgRC,"ui::test() was not passed a valid cfg. object."); + goto errLabel; + } + if((rc = parseArgs(*cfg, args )) != kOkRC ) + { + cwLogError(rc,"UI parse args failed in ui::test()"); + goto errLabel; + } + + + + app->quitFl.store(false); + + // Initial values for the test applications app->appCheckFl = true; app->appSelOptAppId = kOption1Id; app->appInteger = 5; @@ -333,10 +346,10 @@ cw::rc_t cw::ui::test( ) app->appSelId = kOpt3Id; - app->uiCfgFn = "/home/kevin/src/cwtest/src/libcw/html/uiTest/ui.cfg"; + //app->uiCfgFn = "/home/kevin/src/cwtest/src/libcw/html/uiTest/ui.cfg"; // create the UI server - if((rc = srv::create(app->wsUiSrvH, port, physRootDir, app, _uiTestCallback, mapA, mapN, nullptr, dfltPageFn, websockTimeOutMs, rcvBufByteN, xmtBufByteN, fmtBufByteN )) != kOkRC ) + if((rc = srv::create(app->wsUiSrvH, args, app, _uiTestCallback, mapA, mapN, nullptr )) != kOkRC ) return rc; @@ -354,6 +367,8 @@ cw::rc_t cw::ui::test( ) } errLabel: + ui::ws::releaseArgs(args); + rc_t rc1 = kOkRC; if( app->wsUiSrvH.isValid() ) rc1 = srv::destroy(app->wsUiSrvH); diff --git a/cwUiTest.h b/cwUiTest.h index 2301076..b78e4ac 100644 --- a/cwUiTest.h +++ b/cwUiTest.h @@ -2,6 +2,6 @@ namespace cw { namespace ui { - rc_t test(); + rc_t test(const object_t* cfg ); } }