<<

Wrapping C++ Objects For Property Exposure In QML

Charley Bay Beckman Coulter, Inc. Review: C++ From Within QML

Integrating QML and C++: (1) Call (Invoke) C++ from QML

– Invoke (existing / internal) C++ API for Qt QML or Qt Quick – Apply Q_INVOKABLE to funcs in QObject-derived (2) Implement C++ Types for QML

– Derive from QObject

– Define “properties” (Q_PROPERTY())

– Register with QML qmlRegisterType() qmlRegisterUncreatableType() qmlRegisterInterface() qmlRegisterSingletonType() What's The Problem?

The Issue: "Deep / Rich" Types Are typically:

● Domain-specific

● Have business logic

● Often found in legacy systems

● Expected to be "reusable" within that domain

● Are often "key abstractions" (but not always)

● May be "lightweight" or "heavyweight" Deep / Rich Types: Ligthweight Lightweight: "Popcorn Types"

● Frequently come-and-go (are created-and-destroyed)

● Have non-trivial business logic

● Often imply rules needed throughout the system (e.g., point-of- entry data validation, back-end system adaptation across components/revisions of hardware, etc.)

● Often used to bridge sub-systems

● Often "serialized" to span sessions, setups, configurations

● Often comprise significant portions of the domain-specific APIs for "interface-state" (e.g., are "ubiquitous")

These are typically your desired QML "properties"! Deep / Rich Types: Lightweight Examples

Example "Lightweight-Deep/Rich" types

– class Wavelength

– class Voltage

– class Parameter

– class Detector

– class SpectraFilter Deep / Rich Types: Heavyweight Heavyweight: Typically "Key Abstractions"

● Often created at "system-start" with known configuration (e.g., "devices")

● Typically expressed through class hierarchies (e.g., across device revisions, devices specific to a product line evolution)

● Typically consistent across products / product-lines (e.g., "one software to control all devices in a family-of-product-lines)

● May be "plug-&-play" (e.g., subsystems may come-and-go as units during system-run)

● Is often what needs to be monitored / controlled

● Is part of higher-level system (coordination with other key abstractions is typical for data validation, denial of access/operations [e.g., "baton-passing"], etc.) You typically want to "hand-wrap" these for QML! Deep / Rich Types: Heavyweight Examples

Example "Heavyweight-Deep/Rich" types

– class FluidicsSubsystem

– class OpticsSubsystem

– class CytometerInstrument

– class ExperimentArchive

QObject Implies Alternative Control Flow

QObject and QMetaObject:

● signals/slots introduce coupling to imply "alternative- control-flow" (only relevant to designs intended for that control flow)

● QObject-derived types should typically be used as "identity" instances (not with value semantics, copy CTOR and operator=() not available)

Signals/slots are “side-effect” triggers used to assemble, communicate among, and “stitch-together” across sub-systems. While they can be used within a sub- system, they are often not needed where deterministic behavior is required within (even large) key-abstractions that represent functional business-logic state.

Wrapping Deep / Rich Types: QObject

C++ types sometimes cannot derive from QObject:

● If cross-platform embedded (to "very-small-footprint-deployment")

● If application-specific performance requirements

● If application-specific design may establish class hierarchies (which disallow QObject)

● If used where Qt is not used

● If "existing/legacy" code

rd ● If 3 Party code that cannot be modified

● If developed / tested at unit / functional level with as little coupling as possible An Example: class Wavelength

class Wavelength ● Lightweight class { (only data member is double value_nm_; //IS ONLY DATA MEMBER! “double”) public: Wavelength(double value_nm); Wavelength& operator=(double value_nm); ● Has domain-rules Wavelength& operator=(Wavelength& other);

– Range-bounds QString getAsLabel(void) const;

– Label formatting bool isBlue(void) const; bool isGreen(void) const; – Interpreted as “color” bool isOrange(void) const; bool isRed(void) const; ● Is ubiquitous bool isViolet(void) const; bool isYellow(void) const; throughout domain- specific APIs bool isVisible(void) const; }; const bool switch ( band ) { const bool Wavelength case 0: Wavelength const SdString ::setFromStringParseable( // wavelength_in_nm <= 380 (Invisible, but we need *some* color) ::operator<(const Wavelength& other) const Wavelength const SdString& string, r_0_to_1 = 0.35; { ::getAsString(void) const long& start_index) g_0_to_1 = 0; return isLessThan(other); { const bool { b_0_to_1 = 1; } //FILE: SdString string; Wavelength //------intensity_0_to_1 = 0.3;

// appendToString(string); ::hasStateWithWholeNm(void) const // ------break; const bool const bool return string; { //------Wavelength Wavelength #include // For std::numeric_limits } return hasState() && (!hasStateWithFractionalNm static const SdString()); STRING_NM_ (STR_NM_); case 1: ::IsWavelengthVisible(double wavelength_nanometers) ::operator<=(const Wavelength& other) const #include // For max() } //------// 380 < wavelength_in_nm <={ 420, violet { const bool //------r_0_to_1 = std::max(0.35, - (wavelength_in_nm return (wavelength_nanometers-440)/(440-380)); >= WAVELENGTH_NM_VISIBLE_MIN) return isLessThanOrEqual(other); #include "Wavelength.hpp" Wavelength const bool //------g_0_to_1 = 0; && } ::getAsStringParseable(SdString& string) const Wavelength b_0_to_1 = 1; (wavelength_nanometers <= WAVELENGTH_NM_VISIBLE_MAX);

#include "SdString.hpp" { ::hasWavelength(double wavelength_value clear();) const intensity_0_to_1 = 0.3 + 0.7*(wavelength_in_nm} -380)/(420-380); const bool #include "TypeUtil.hpp" string.clear(); { break; Wavelength return appendToStringParseable(string); return TypeUtil::IsValuesEqual long(wavelength_value start_index_temp, wavelength_nanometers = string.findIndexFirstCharNonWhitespace_); (start_index); const bool ::operator!=(const Wavelength& other) const } } if(start_index_temp >= 0) case 2: Wavelength { { // We have something in this string at-or-after the index // 420specified. < wavelength_in_nm <=:: IsWavelengthVisibleBlue440, blue (double wavelength_nanometers) return isNotEqual(other); //------const SdString const bool // r_0_to_1 = -(wavelength_in_nm{ -440)/(440-380); } //------Wavelength Wavelength if(string.isCharDigit(start_index_temp)) g_0_to_1 = 0; return (wavelength_nanometers >= WAVELENGTH_NM_BLUE_MIN)

//------::getAsStringParseable(void) const ::isEqual(const Wavelength& other) { const b_0_to_1 = 1; && const double { { double value; break; (wavelength_nanometers <= WAVELENGTH_NM_BLUE_MAX); Wavelength static const char* STR_NM_ = "nm"; SdString string; // if(this != &other) if(string.parseFloatingStartingAt(value, start_index_temp )) } ::operator+(const Wavelength& other) const appendToStringParseable(string); // { { case 3: { //------class return string; returnWavelength wavelength_nanometers _ == other.wavelength_nanometersif(IsOkWavelengthInNanometers_; ((double)value) || (value // 440 ==

Wavelength } // we should skip it. b_0_to_1 = 1; { const double ::getAsStringUserInterface(SdString& string) const if(start_index_temp >= 0) break; return (wavelength_nanometers >= WAVELENGTH_NM_GREEN_MIN) Wavelength { const bool { && ::operator+(double nm_value) const Wavelength::Wavelength(void) string.clear(); Wavelength if((start_index_temp = string.findIndexFirstCharNonWhitespace case 4: (start_index_temp )) >= (0)wavelength_nanometers <= WAVELENGTH_NM_GREEN_MAX); { :wavelength_nanometers_(WAVELENGTH_NM_NONE) return appendToStringUserInterface(string); ::isGreaterThan(const Wavelength& other) const{ // 490 < wavelength_in_nm <=} 510, deep blue green return ((INT32)wavelength_nanometers_ + (INT32)nm_value); { } { if(string.isMatchSubThis(start_index_temp , STRING_NM_, r_0_to_1 = false/*0; case_sensitive */)) } // clear(); // if(this != &other) { g_0_to_1 = 1; const bool

} const SdString // { start_index_temp += STRING_NM_.getLength (); b_0_to_1 = -(wavelength_in_nmWavelength-510)/(510 -490); const double Wavelength return wavelength_nanometers _ > other.wavelength_nanometers } _; break; ::IsWavelengthVisibleOrange(double wavelength_nanometers) Wavelength ::getAsStringUserInterface(void) const //FILE: Wavelength.hpp Wavelength::Wavelength(double wavelength_nanometers) // } } { ::operator-(const Wavelength& other) const { // :wavelength_nanometers_(WAVELENGTH_NM_NONE) // return false; } case 5: return (wavelength_nanometers >= WAVELENGTH_NM_ORANGE_MIN) { SdString string; { } setFromWavelengthInNanometers(value); // 510 < wavelength_in_nm <= 580, green && return operator-(other.wavelength_nanometers_); appendToStringUserInterface(string); #ifndef Wavelength_hpp // clear(); start_index = start_index_temp; r_0_to_1 = (wavelength_in_nm - 510)/(580 (wavelength_nanometers-510); <= WAVELENGTH_NM_ORANGE_MAX); } return string; #define Wavelength_hpp setFromWavelengthInNanometers(wavelength_nanometers); const bool return true; g_0_to_1 = 1; } } } Wavelength } b_0_to_1 = 0; const bool getAsString(SdString& string) const; const double ::isGreaterThanOrEqual(const Wavelength& } other) const break; const bool const SdString getAsString(void) const; Wavelength const bool #include "CppFix.hpp" Wavelength::Wavelength(const SdString& string_parseable) { } Wavelength const bool getAsStringParseable(SdString& string) const; ::operator-(double nm_value) const Wavelength :wavelength_nanometers_(WAVELENGTH_NM_NONE) // if(this != &other) // If we reach here, there was a parse error. We were case given 6: ::IsWavelengthVisibleRed(double wavelength_nanometers) const SdString getAsStringParseable(void) const; { ::getAsWavelengthAddOther( class SdString; { // { // a non-empty string, but we could not parse it as a Wavelength.// 580 < wavelength_in_nm <={ 645, orange const bool getAsStringUserInterface(SdString& string) const; return ((INT32)wavelength_nanometers_ - (INT32)nm_value Wavelength&); wavelength_as, // clear(); return wavelength_nanometers _ >= // other.wavelength_nanometers _; r_0_to_1 = 1; return (wavelength_nanometers >= WAVELENGTH_NM_RED_MIN) const SdString getAsStringUserInterface(void) const; } const Wavelength& other) const setFromStringParseable(string_parseable); // } return false; g_0_to_1 = (645-wavelength_in_nm)/(645 &&- 580); { /// The Wavelength is a utility class to represent a specific } // return false; } b_0_to_1 = 0; (wavelength_nanometers <= WAVELENGTH_NM_RED_MAX); const bool getAsWavelengthAddOther( //------wavelength_as.setFrom(*this); /// "wavelength", such as that emitted by a laser, or absorbed } break; } Wavelength& wavelength_as, // P U B L I C wavelength_as.addToWavelength(other); /// or emitted by a fluorescent dye. Wavelength::Wavelength(const Wavelength& other) // If we reach here, we were given an empty string, or there was only const Wavelength& other) const; //------return wavelength_as.hasState (); /// { const bool // whitespace at-and-after the location given. case 7: const bool const Wavelength getAsWavelengthAddOther(const Wavelength& other) const; } /// RECALL: Logically, we intend to represent wavelengths typical // clear(); Wavelength // // 645 < wavelength_in_nm <=Wavelength 700, red const bool getAsWavelengthSubtractOther( void /// for cytometry. So, that our measure is in "nanometer", and setFrom(other); ::isLessThan(const Wavelength& other)// RECALL: const This is NOT a parse error. Rather, we have an r_0_to_1 explicit = 1; ::IsWavelengthVisibleViolet(double wavelength_nanometers) Wavelength& wavelength_as, Wavelength const Wavelength /// we are useful for things related to lasers and flourescent } { // "empty" wavelength. g_0_to_1 = 0; { const Wavelength& other) const; ::addToWavelength(const Wavelength& nm_to_add) Wavelength /// dyes. return !isGreaterThanOrEqual (other); // b_0_to_1 = 0; return (wavelength_nanometers >= WAVELENGTH_NM_VIOLET_MIN) const Wavelength getAsWavelengthSubtractOther(const Wavelength& other) const; { ::getAsWavelengthAddOther( /// Wavelength::~Wavelength(void) } start_index = string.getLength(); break; && addToWavelength(nm_to_add.wavelength_nanometers_); const Wavelength& other) const class DECL_SD_DLL_UTILS_MATH Wavelength { ASSERT_LOGIC(!hasState()); (wavelength_nanometers <= WAVELENGTH_NM_VIOLET_MAX); void getRgbPercentForWavelength( } { { } const bool return true; case 8: } double& r_0_to_1, Wavelength wavelength_as; public: Wavelength } // 700 < wavelength_in_nm <= 780, deep red double& g_0_to_1, void getAsWavelengthAddOther( enum SpectrumNm { Wavelength& ::isLessThanOrEqual(const Wavelength& other) const r_0_to_1 = 1; const bool double& b_0_to_1, Wavelength wavelength_as, WAVELENGTH_NM_NONE = 0, Wavelength { void g_0_to_1 = 0; Wavelength double gamma_0_to_1 = 1.0) const; ::addToWavelength(double nm_to_add) other); ::operator=(const Wavelength& other) return !isGreaterThan(other);Wavelength b_0_to_1 = 0; ::IsWavelengthVisibleYellow(double wavelength_nanometers) { return wavelength_as; WAVELENGTH_NM_MIN = 1, { } ::setFromWavelengthInNanometers(double wavelength_nanometers intensity_0_to_1) = 0.3 + 0.7*(780{ -wavelength_in_nm)/(780-700); void getRgbValueForWavelength( // RECALL: We ENFORCE that our value CANNOT be negative,} so setFrom(other); { break; return (wavelength_nanometers >= WAVELENGTH_NM_YELLOW_MIN) UINT32& r_value, // we guard against "wrap" for the sign bit. WAVELENGTH_NM_VISIBLE_MIN = 380, return *this; const bool if(wavelength_nanometers == WAVELENGTH_NM_NONE) && UINT32& g_value, // const bool } Wavelength clear(); case 9: (wavelength_nanometers <= WAVELENGTH_NM_YELLOW_MAX); UINT32& b_value, if((wavelength_nanometers_ + nm_to_add) < 0) Wavelength WAVELENGTH_NM_VIOLET_MIN = 380, ::isNotEqual(const Wavelength& other)else const // 780 < wavelength_in_nm (Invisible,} but we need *some* color) double gamma_0_to_1 = 1.0, { ::getAsWavelengthSubtractOther( WAVELENGTH_NM_VIOLET_MAX = 449, Wavelength& { wavelength_nanometers_ = GetWavelengthClean(wavelength_nanometers r_0_to_1 = );1; UINT32 rgb_each_value_max = 255) const; clear(); Wavelength& wavelength_as, Wavelength return !isEqual(other); } g_0_to_1 = 0; void } const Wavelength& other) const WAVELENGTH_NM_BLUE_MIN = 450, ::operator+=(const Wavelength& other) } b_0_to_1 = 0; Wavelength const double getWavelengthInNanometers(void) const; else { WAVELENGTH_NM_BLUE_MAX = 494, { void intensity_0_to_1 = 0.3; ::SwapValues(Wavelength& wavelength0, const long getWavelengthInNanometersAsLong(void) const; { wavelength_as.setFrom(*this); addToWavelength(other); const bool Wavelength break; Wavelength& wavelength1) setFromWavelengthInNanometers( wavelength_as.subtractFromWavelength(other); WAVELENGTH_NM_GREEN_MIN = 495, return *this; Wavelength ::setFromWavelengthNmMax(void) } // end switch { const double getWavelengthInNanometersAddOther(const Wavelength& other) const; wavelength_nanometers_ + nm_to_add); return wavelength_as.hasState(); WAVELENGTH_NM_GREEN_MAX = 569, } ::isWavelengthAboveVisible(void){ const double temp = wavelength0.wavelength_nanometers_; const double getWavelengthInNanometersSubtractOther(const Wavelength& other) const; } } { setFromWavelengthInNanometers(WAVELENGTH_NM_MAX); // apply intensity and gamma corrections. wavelength0.wavelength_nanometers_ = wavelength1.wavelength_nanometers_;

WAVELENGTH_NM_YELLOW_MIN = 570, Wavelength& return IsWavelengthAboveVisible} (wavelength_nanometers_); intensity_0_to_1 *= gamma_0_to_1; wavelength1.wavelength_nanometers_ = temp; const bool hasState(void) const; //if(nm_to_add < 0) const Wavelength WAVELENGTH_NM_YELLOW_MAX = 589, Wavelength } r_0_to_1 *= intensity_0_to_1;} const bool hasStateWithFractionalNm(void) const; //{ // We are subtracting (i.e., adding a positiveWavelength number). ::operator+=(double nm_value) void g_0_to_1 *= intensity_0_to_1; const bool hasStateWithWholeNm(void) const; // if((wavelength_nanometers_ + nm_to_add) < 0) ::getAsWavelengthSubtractOther(const Wavelength& other) const WAVELENGTH_NM_ORANGE_MIN = 590, { const bool Wavelength b_0_to_1 *= intensity_0_to_1; // { // We wrapped below zero, so correct to zero.{ WAVELENGTH_NM_ORANGE_MAX = 619, addToWavelength(nm_value); Wavelength ::setFromWavelengthNmMin(void) } const bool hasWavelength(double wavelength_value) const; // clear(); Wavelength wavelength_as; return *this; ::isWavelengthBelowVisible(void){ const void // } getAsWavelengthSubtractOther( WAVELENGTH_NM_RED_MIN = 620, } { setFromWavelengthInNanometers(WAVELENGTH_NM_MIN); Wavelength const bool isEqual(const Wavelength& other) const; // else wavelength_as, WAVELENGTH_NM_RED_MAX = 750, return IsWavelengthBelowVisible} (wavelength_nanometers_); ::GetRgbValueForWavelength(UINT32& r_value, const bool isGreaterThan(const Wavelength& other) const; // { other); Wavelength& } UINT32& g_value, const bool isGreaterThanOrEqual(const Wavelength& other) const; // wavelength_nanometers_ += nm_to_add; return wavelength_as; WAVELENGTH_NM_VISIBLE_MAX = 750, Wavelength void UINT32& b_value, const bool isLessThan(const Wavelength& other) const; // } } ::operator-=(const Wavelength& other) const bool Wavelength double wavelength_in_nm, const bool isLessThanOrEqual(const Wavelength& other) const; //} WAVELENGTH_NM_MAX = 0x7FFF, { Wavelength ::subtractFromWavelength(const Wavelength& nm_to_subtract ) double gamma_0_to_1, const bool isNotEqual(const Wavelength& other) const; //else void }; subtractFromWavelength(other); ::isWavelengthVisible(void) const{ UINT32 rgb_each_value_max) //{ // We add a positive number. Wavelength enum { return *this; { subtractFromWavelength(nm_to_subtract.getWavelengthInNanometers{ ()); const bool isWavelengthAboveVisible(void) const; // if((wavelength_nanometers_ + nm_to_add) > WAVELENGTH_NM_MAX)::getRgbPercentForWavelength (double& r_0_to_1, WAVELENGTH_RANGE_NM_BELOW_VISIBLE = WAVELENGTH_NM_VISIBLE_MIN - WAVELENGTH_NM_MIN + 1, } return IsWavelengthVisible(wavelength_nanometers} _); double r_0_to_1, g_0_to_1, b_0_to_1; const bool isWavelengthBelowVisible(void) const; // { // We wrapped to high, so correct to the max. double& g_0_to_1, WAVELENGTH_RANGE_NM_ABOVE_VISIBLE = WAVELENGTH_NM_MAX - WAVELENGTH_NM_VISIBLE_MAX + 1, } GetRgbPercentForWavelength(r_0_to_1, const bool isWavelengthVisible(void) const; // wavelength_nanometers_ = WAVELENGTH_NM_MAX; double& b_0_to_1, Wavelength& void g_0_to_1, const bool isWavelengthVisibleBlue(void) const; // } double gamma_0_to_1) const WAVELENGTH_RANGE_NM_VISIBLE = WAVELENGTH_NM_VISIBLE_MAX - WAVELENGTH_NM_VISIBLE_MIN + 1, Wavelength const bool Wavelength b_0_to_1, const bool isWavelengthVisibleGreen(void) const; // else { ::operator-=(double nm_value) Wavelength ::subtractFromWavelength(double nm_to_subtract) wavelength_in_nm, const bool isWavelengthVisibleOrange(void) const; // { GetRgbPercentForWavelength(r_0_to_1, WAVELENGTH_RANGE_NM_VIOLET = WAVELENGTH_NM_VIOLET_MAX - WAVELENGTH_NM_VIOLET_MIN + 1, { ::isWavelengthVisibleBlue(void){ const gamma_0_to_1); const bool isWavelengthVisibleRed(void) const; // wavelength_nanometers_ += nm_to_add; g_0_to_1, WAVELENGTH_RANGE_NM_BLUE = WAVELENGTH_NM_BLUE_MAX - WAVELENGTH_NM_BLUE_MIN + 1, subtractFromWavelength(nm_value); { addToWavelength(-nm_to_subtract); const bool isWavelengthVisibleViolet(void) const; // } b_0_to_1, WAVELENGTH_RANGE_NM_GREEN = WAVELENGTH_NM_GREEN_MAX - WAVELENGTH_NM_GREEN_MIN + 1, return *this; return IsWavelengthVisibleBlue} (wavelength_nanometers_); // Convert to the allowed range (e.g., 0...255) const bool isWavelengthVisibleYellow(void) const; //} getWavelengthInNanometers(), WAVELENGTH_RANGE_NM_YELLOW = WAVELENGTH_NM_YELLOW_MAX - WAVELENGTH_NM_YELLOW_MIN + 1, } } r_value = (UINT32)(r_0_to_1 * rgb_each_value_max); } gamma_0_to_1); WAVELENGTH_RANGE_NM_ORANGE = WAVELENGTH_NM_ORANGE_MAX - WAVELENGTH_NM_ORANGE_MIN + 1, //------g_value = (UINT32)(g_0_to_1 * rgb_each_value_max); void setFrom(const Wavelength& other); } WAVELENGTH_RANGE_NM_RED = WAVELENGTH_NM_RED_MAX - WAVELENGTH_NM_RED_MIN + 1, Wavelength& const bool // S T A T I C b_value = (UINT32)(b_0_to_1 * rgb_each_value_max); void setFrom(double wavelength_nanometers); const bool }; Wavelength Wavelength //------} Wavelength void private: ::operator++(void) ::isWavelengthVisibleGreen(void) const const bool setFromStringParseable(const SdString& string); ::appendToString(SdString& string) const Wavelength // Track our state in nanometer units. { { const CompareResult& const SdString& const bool setFromStringParseable(const SdString& string, long& start_index); { ::getRgbValueForWavelength(UINT32& r_value, // // prefix return IsWavelengthVisibleGreenWavelength(wavelength_nanometers _); Wavelength return appendToStringUserInterface(string); UINT32& g_value, // RECALL: We have state if this value is >0 (zero addToWavelength(1); } ::Compare( ::GetStringNm(void) void setFromWavelengthInNanometers(double wavelength_nanometers); } UINT32& b_value, // means we have no state). return *this; const Wavelength& operand0, { double gamma_0_to_1, // } const bool const Wavelength& operand1) //------void setFromWavelengthNmMax(void); const bool UINT32 rgb_each_value_max) const // UINT16 wavelength_nanometers_; Wavelength { //------void setFromWavelengthNmMin(void); Wavelength { // RECALL: We MUST be able to handle "fractional" values const Wavelength ::isWavelengthVisibleOrange(void) if(&operand0 const != &operand1) //------::appendToStringMaybeAppendNm(SdString& string, GetRgbValueForWavelength(r_value, // of nanometers, so we are floationg point. Wavelength { { static const SdString STRING_NM_ (STR_NM_); void subtractFromWavelength(const Wavelength& nm_to_subtract); bool flag_append_nm) const g_value, // ::operator++(int) return IsWavelengthVisibleOrange return(wavelength_nanometers CompareResult::GetCompareResultFromFloatings_); ( //------void subtractFromWavelength(double nm_to_subtract); { b_value, double wavelength_nanometers_; { } operand0.wavelength_nanometers_, //------// RECALL: We ATTEMPT to append integer precision, unless getWavelengthInNanometers(), // postfix operand1.wavelength_nanometers_); //------//------// we have an explicit non-epsilon mantissa. gamma_0_to_1, public: Wavelength temp(*this); const bool } // S T A T I C if(hasStateWithFractionalNm()) rgb_each_value_max); Wavelength(void); this->operator ++(); Wavelength return CompareResult::GetInstanceIsEqual(); return STRING_NM_; //------{ // We have a fractional nanometer value. } explicit Wavelength(double wavelength_nanometers); return temp; ::isWavelengthVisibleRed(void)} const } // string.appendFromTypeFloating(wavelength_nanometers _, 1/*num_digits*/, false/*allow_scientific_notation*/); explicit Wavelength(const SdString& string_parseable); } { static const CompareResult& Compare( string.appendFromTypeFloating(wavelength_nanometersconst_, double 0/*num_decimal_places */); Wavelength(const Wavelength& other); return IsWavelengthVisibleRedconst(wavelength_nanometers Wavelength& _); const double const Wavelength& operand0, } Wavelength ~Wavelength(void); Wavelength& } Wavelength Wavelength const Wavelength& operand1); else ::getWavelengthInNanometers(void) const Wavelength ::GetInstanceEmpty(void) ::GetWavelengthClean(double wavelength_nanometers) { // We are an integer value, or we do not have state.{ Wavelength& operator=(const Wavelength& other); ::operator--(void) const bool { { static const Wavelength& GetInstanceEmpty(void); string.appendFromTypeInteger((long)wavelength_nanometers return wavelength_nanometers_); _; { Wavelength //------if(wavelength_nanometers > WAVELENGTH_NM_MAX) } } Wavelength& operator+=(const Wavelength& other); // prefix ::isWavelengthVisibleViolet(void) // ------const return WAVELENGTH_NM_MAX; static void GetRgbPercentForWavelength( Wavelength& operator+=(double nm_value); subtractFromWavelength(1); { //------if(wavelength_nanometers < WAVELENGTH_NM_MIN) double& r_0_to_1, if(flag_append_nm) const long Wavelength& operator-=(const Wavelength& other); return *this; return IsWavelengthVisibleViolet static(wavelength_nanometers const Wavelength INSTANCE_EMPTY_;_); return WAVELENGTH_NM_MIN; double& g_0_to_1, { Wavelength Wavelength& operator-=(double nm_value); } } //------return (double)wavelength_nanometers ; double& b_0_to_1, string.appendFrom(GetStringNm()); ::getWavelengthInNanometersAsLong(void) const //------} double wavelength_in_nm, } { Wavelength& operator++(void); // prefix const Wavelength const bool //------double gamma_0_to_1 = 1.0); return hasState(); // RECALL: We "round up" or "round down" to convert const Wavelength operator++(int); // postfix Wavelength Wavelength const bool static void GetRgbValueForWavelength( } // to a long. ::operator--(int) ::isWavelengthVisibleYellow(void) return const INSTANCE_EMPTY_; Wavelength UINT32& r_value, return (long)(wavelength_nanometers_ + 0.5); Wavelength& operator--(void); // prefix { { } ::IsOkWavelengthInNanometers(double wavelength_nanometers) UINT32& g_value, const bool } const Wavelength operator--(int); // postfix // postfix return IsWavelengthVisibleYellow (wavelength_nanometers_); { UINT32& b_value, Wavelength Wavelength temp(*this); } void return (wavelength_nanometers >= WAVELENGTH_NM_MIN) double wavelength_in_nm, ::appendToStringParseable(SdString& string) const const double const bool operator==(const Wavelength& other) const; this->operator --(); Wavelength && double gamma_0_to_1 = 1.0, { Wavelength const bool operator>(const Wavelength& other) const; return temp; void ::GetRgbPercentForWavelength(double& r_0_to_1, (wavelength_nanometers <= WAVELENGTH_NM_MAX); UINT32 rgb_each_value_max = 255); return appendToStringMaybeAppendNm(string, false/*::flag_append_nmgetWavelengthInNanometersAddOther*/); (const Wavelength& other) const const bool operator>=(const Wavelength& other) const; } Wavelength double& g_0_to_1, } } { const bool operator<(const Wavelength& other) const; ::setFrom(const Wavelength& other) double& b_0_to_1, static const SdString& GetStringNm(void); return wavelength_nanometers_ + other.wavelength_nanometers_; const bool operator<=(const Wavelength& other) const; const bool { double wavelength_in_nm, const bool const bool } const bool operator!=(const Wavelength& other) const; Wavelength // if(this != &other) double gamma_0_to_1) Wavelength static const double GetWavelengthClean(double wavelength_nanometers); Wavelength ::operator==(const Wavelength& other) const // { { ::IsWavelengthAboveVisible(double wavelength_nanometers) ::appendToStringUserInterface(SdString& string) constconst double const double operator+(const Wavelength& other) const; { wavelength_nanometers_ = other.wavelength_nanometers // red, green, blue component_; in range 0.0 .. 1.0. { static const bool IsOkWavelengthInNanometers(double wavelength_nanometers); { Wavelength const double operator+(double nm_value) const; return isEqual(other); // } r_0_to_1 = g_0_to_1 = b_0_to_1 = 0; return (wavelength_nanometers >= WAVELENGTH_NM_MIN) return appendToStringMaybeAppendNm(string, true/*::flag_append_nmgetWavelengthInNanometersSubtractOther*/); (const Wavelength& other) const const double operator-(const Wavelength& other) const; } } //intensity 0.0 .. 1.0 based on drop off in vision at low/high &&wavelengths static const bool IsWavelengthAboveVisible(double wavelength_nanometers); } { const double operator-(double nm_value) const; double intensity_0_to_1 = 1; (wavelength_nanometers < WAVELENGTH_NM_VISIBLE_MIN); static const bool IsWavelengthBelowVisible(double wavelength_nanometers); return wavelength_nanometers_ - other.wavelength_nanometers_; const bool void } static const bool IsWavelengthVisible(double wavelength_nanometers); void } //------Wavelength Wavelength // We use different linear interpolations on different bands. static const bool IsWavelengthVisibleBlue(double wavelength_nanometers); Wavelength // P U B L I C ::operator>(const Wavelength& other) const ::setFrom(double wavelength_nanometers // These) numbers mark the upper bound of each band. const bool static const bool IsWavelengthVisibleGreen(double wavelength_nanometers); ::clear(void) const bool //------{ { const static int num_bands = 10; Wavelength static const bool IsWavelengthVisibleOrange(double wavelength_nanometers); { Wavelength return isGreaterThan(other); setFromWavelengthInNanometers (constwavelength_nanometers static double bands[); num_bands] = { 380, 420, 440,::IsWavelengthBelowVisible 490, 510, 580, 645, 700,(double 780, stdwavelength_nanometers::numeric_limits::max()) }; static const bool IsWavelengthVisibleRed(double wavelength_nanometers); wavelength_nanometers_ = WAVELENGTH_NM_NONE; ::hasState(void) const void addToWavelength(const Wavelength& nm_to_add); } } { static const bool IsWavelengthVisibleViolet(double wavelength_nanometers); } { void addToWavelength(double nm_to_add); // Figure out which band we fall in. A point on the edge return (wavelength_nanometers > WAVELENGTH_NM_VISIBLE_MAX) static const bool IsWavelengthVisibleYellow(double wavelength_nanometers); return (wavelength_nanometers_ != WAVELENGTH_NM_NONE); const bool const bool // is considered part of the lower band. && const bool } const bool appendToString(SdString& string) const; Wavelength Wavelength int band = (num_bands - 1); (wavelength_nanometers <= WAVELENGTH_NM_MAX); static void SwapValues( Wavelength const bool appendToStringMaybeAppendNm( ::operator>=(const Wavelength& other) const ::setFromStringParseable(const SdStringfor ( int& string)i=0; i< num_bands; i++ ) { } Wavelength& operand0, ::getAsString(SdString& string) const const bool SdString& string, { { if ( wavelength_in_nm <= bands[i] ) { Wavelength& operand1); { Wavelength bool flag_append_nm) const; return isGreaterThanOrEqual(other); long start_index = 0; band = i; string.clear(); ::hasStateWithFractionalNm(void) const const bool appendToStringParseable(SdString& string) const; } return setFromStringParseable (string, break; start_index ); }; return appendToString(string); { const bool appendToStringUserInterface(SdString& string) const; } } } return hasState() && TypeUtil::IsValueNonZero((double)(wavelength_nanometers_ - ((long)wavelength_nanometers_))); } #endif } void clear(void);

Wrapped In QObject: "SdqWavelengthBox"

class SdqWavelengthBox : public QObject { Q_OBJECT Q_PROPERTY(const bool hasState READ hasState NOTIFY wavelengthChanged) ● We only care about Q_PROPERTY(const qreal wavelengthNm READ wavelengthNm WRITE setWavelengthNm NOTIFY wavelengthChanged) Q_PROPERTY(const QString asText READ asText NOTIFY wavelengthChanged) "properties"! Q_PROPERTY(const QColor color READ color NOTIFY wavelengthChanged)

private: Wavelength my_wavelength_; // WRAPPED DATA MEMBER! ● Some "get / set" signals: void wavelengthChanged(void) const; public: utility functions (to support // ...CTORs, DTOR… const bool hasState(void) const; const qreal wavelengthNm(void) const; properties) void setWavelengthNm(qreal value_new); const QString asText(void) const; const QColor color(void) const;

public: ● Plus (optional!): enum EnumRole { ENUM_ROLE_FIRST , ROLE_AS_TEXT = ENUM_ROLE_FIRST, ROLE_COLOR , ROLE_HAS_STATE , Utility "enums" and ROLE_WAVELENGTH_NM , ENUM_ROLE_LAST = ROLE_WAVELENGTH_NM, }; functions to support enum { NUM_ENUM_ROLES = ENUM_ROLE_LAST - ENUM_ROLE_FIRST + 1, QAbstractItemModel }; static QVariant GetQVariantForEnumRoleEnum( const SdqWavelengthBox& object_box_value, (for a "model / collection"- SdqWavelengthBox::EnumRole enum_role);

static const char** GetStrArrayEnumRoleNames(void); of-Wavelength" };

//------instances) // RECALL: To convert our type easily "to/from" "QVariant", // we must EXPLICITLY declare the meta-type. // Q_DECLARE_METATYPE(SdqWavelengthBox) //------#endif // SdqWavelengthBox_hpp Registering Our "SdqWavelengthBox"

In application, or QML- #include plugin derived from #include QQmlExtensionPlugin, // ... #include "SdqWavelengthBox.hpp" register the type into the class CytoQmlPlugin : public QQmlExtensionPlugin QML type-system. { Q_OBJECT Q_PLUGIN_METADATA(IID "com.bec.cyto")

public: This "translates" the void registerTypes(const char *uri) { "type-wrapper" of qmlRegisterType( "SdqWavelengthBox" so uri, //const char * uri, 1, //int versionMajor, that QML only sees the 0, //int versionMinor, type as "Wavelength" "Wavelength"); //const char * qmlName) } (which is ALWAYS what }; you want!) Using Our "SdqWavelengthBox"

// FILE: HelloWavelength.qml import QtQuick 2.1 import QtQuick.Controls 1.0 In QML file: import QtQuick.Window 2.0

import com.bec.cyto 1.0 as Bec ● Import your plugin (that ApplicationWindow { registered/exported title: qsTr("TestHelloWavelength") width: 360 Sdq Box height: 360 " Wavelength " to Bec.Wavelength { id: myWavelength the QML type system) wavelengthNm: mySlider.value } Rectangle { ● Use it (with the name id: myRect anchors.fill: parent "Wavelength") color: myWavelength.color Label { id: myLabel anchors.centerIn: myRect (This example imports to the text: myWavelength.asText font.pixelSize: 48 namespace "Bec", which is font.italic: true color: "white" } optional.) Slider { id: mySlider anchors.top: myLabel.bottom width: myRect.width maximumValue: 800 Launch minimumValue: 300 HelloWavelength.qml stepSize: 1.0 value: myWavelength.wavelengthNm } } } REVIEW: What Just Happened?

What we did:

1. Existing C++ class (did not derive from QObject, we did not "touch" it)

2. Defined “QObject-derived-wrapper" class

3. Exported “QObject-derived-wrapper" (to the QML type system)

4. Used type "natively" within QML

…There are implications from "(1)"… REVIEW: What Just Happened? (continued)

What we got:

1. Made available an existing C++ class to QML (without touching it!)

1. Do not need to re-compile existing (legacy) code

2. Do not need to re-validate / re-verify existing systems (BIG concern for regulated industries)

2. Abstracted to a "higher-level" a "new-layer" of properties for declarative-binding.

1. Interface for "declarative-QML-Wavelength" is DIFFERENT from "imperative-C++-Wavelength". THIS IS A GOOD THING.

2. New declarative-abstractions, with internal "bridging / binding", enables better interfaces and separation of subsystems (across modules, devices, and between GUI / UI and the "back-end") Some Issues With This Approach

● Not difficult work, but is tedious if must wrap many existing C++ classes (many systems have dozens, or hundreds of these domain-specific types)

● We achieved "value semantics":

– What if we want "reference" semantics to reference a "Wavelength" instance in the "back-end" system? (Could change our implementation to a "smart_ptr"…)

● Need for updates:

– If many QML-Wavelength instances "reference" a "bound-property- instance" in the "back-end", how to ensure all QML-Wavelength instances are "updated/notified" when back-end instance changes?

● Must manage the "two-interfaces" for "QML-Wavelength" and "C++-Wavelength" Proposed: Code Generator To Expose C++ to QML

1. Existing C++ class (not derived from QObject)

2. Define “interface-file” (for each C++ class)

3. Run Code Generator (creates C++ “wrapper- class(es)”)

4. Build, Link, Run

5. Profit! Due to additional features provide through the code-generator, we now refer to this as "Boxing" the C++ class – not "wrapping".

This relates to a tongue-in-cheek reference to the C++/CLI topic of "boxing/unboxing" in the .NET Common Language Runtime (CLR) that (implicitly) "boxes" a value-type to the type-object, where later (explicit) "unboxing" extracts the value-type from the object. Case Study: An Example Code Generator

1. Assume existing C++ "class Wavelength"

2. Define a "declarative-property-interface" in a new file, "Wavelength.sdgen_sdqbox"

// FILE: Wavelength.sdgen_sdqbox

bool hasState { token : HAS_STATE cppfuncget : hasState qmlnotify : wavelengthChanged } qreal wavelengthNm { token : WAVELENGTH_NM cppfuncget : getWavelengthInNanometers qmlwrite : setWavelengthNm cppfuncset : setFromWavelengthInNanometers cppfunchasvalue : hasWavelength qmlnotify : wavelengthChanged } QString asText { token : AS_TEXT cppfuncget : QtUtil::GetQStringFromSdString(getItemToBox()->getAsStringUserInterface()) qmlnotify : wavelengthChanged headersextra : QtUtil SdString } QColor color { token : COLOR cppfuncget : GuiCytoUtil::GetQColorBackgroundForWavelength(*getItemToBox()) qmlnotify : wavelengthChanged headersextra : GuiCytoUtil } Side Issue: Data Ontology Names Matter!

– C++: ReallyLongFunctionNamesAreFine

– QML: short property names! (e.g., "text", "color")

Create an Ontology: “An ontology provides a shared vocabulary, which can be used to model a domain, that is, the type of objects and/or concepts that exist, and their properties and relations.” --Ontology (information science) http://en.wikipedia.org/wiki/Ontology_(information_science) • Universal / ubiquitous: id, text, name, value • Domain-specific: voltage, gain, filter, detector • Different domain ontologies separated by namespaces: cyto, inst Case Study (continued): Organizing "Declarative Interface Files"

3. The "file-name-root" is ASSUMED to be the same as the C++ class that is to be "boxed".

4. Place all "*.sdgen_sdqbox" files into a common directory for the same "module" (same shared-library or plugin)

./MyWorkspace/. MyPlugin1/. C:\MyWorkspace\MyPlugin1> sdgen_sdqbox.exe Detector.sdgen_sdqbox ...generating... SpectraFilter.sdgen_sdqbox C:\MyWorkspace\MyPlugin1> dir /b Wavelength.sdgen_sdqbox Detector.sdgen_sdqbox SpectraFilter.sdgen_sdqbox 5. From that directory, run the Wavelength.sdgen_sdqbox

SdqModelListWavelength.cpp <==(generated!) "sdgen_sdqbox.exe" utility SdqModelListWavelength.hpp <==(generated!) SdqWavelengthBox.cpp <==(generated!) (code generator executable). It SdqWavelengthBox.hpp <==(generated!) SdqWavelengthBoxBack.cpp <==(generated!) globs all "*.sdgen_sdqbox" SdqWavelengthBoxBack.hpp <==(generated!) SdqWavelengthBoxSet.cpp <==(generated!) SdqWavelengthBoxSet.hpp <==(generated!) files and generates (C++) ...+generated for "SpectraFilter"... source code to that directory for ...+generated for "Detector"...

each "boxed-class". C:\MyWorkspace\MyPlugin1> Case Study (continued): Build & Link Declarative Box-Types

C:\MyWorkspace\MyPlugin1> dir /b 6. Build/Link all the files in that

Detector.sdgen_sdqbox directory as a "module" (e.g., "shared- SpectraFilter.sdgen_sdqbox Wavelength.sdgen_sdqbox library").

SdqModelListDetector.cpp <==(generated!) SdqModelListDetector.hpp <==(generated!) 1. SdqDetectorBox.cpp <==(generated!) A build setup that "globs" all SdqDetectorBox.hpp <==(generated!) "*.hpp/*.cpp" files in that directory SdqDetectorBoxBack.cpp <==(generated!) SdqDetectorBoxBack.hpp <==(generated!) SdqDetectorBoxSet.cpp <==(generated!) makes this simple SdqDetectorBoxSet.hpp <==(generated!) 2. SdqModelListSpectraFilter.cpp <==(generated!) Best Practice: SdqModelListSpectraFilter.hpp <==(generated!) SdqSpectraFilterBox.cpp <==(generated!) 1. Only "generated" files are found in this SdqSpectraFilterBox.hpp <==(generated!) SdqSpectraFilterBoxBack.cpp <==(generated!) directory (no "hand-maintained" files) SdqSpectraFilterBoxBack.hpp <==(generated!) SdqSpectraFilterBoxSet.cpp <==(generated!) 2. Both "*.sdgen_sdqbox" and generated SdqSpectraFilterBoxSet.hpp <==(generated!) "*.hpp/*.cpp" files are checked into the

SdqModelListWavelength.cpp <==(generated!) Version Control System SdqModelListWavelength.hpp <==(generated!) SdqWavelengthBox.cpp <==(generated!) SdqWavelengthBox.hpp <==(generated!) SdqWavelengthBoxBack.cpp <==(generated!) SdqWavelengthBoxBack.hpp <==(generated!) SdqWavelengthBoxSet.cpp <==(generated!) ./MyWorkspace/. SdqWavelengthBoxSet.hpp <==(generated!) MyBin/. CytoPlugin1.dll C:\MyWorkspace\MyPlugin1> CytoPlugin1.lib Case Study (continued): What Did The Generator Do?

What was generated? QObject smart_ptr<> Wavelength 1 1 Q_PROPERTY(text) 1 Q_PROPERTY(qreal) SdqWavelengthBoxBack Single Q_PROPERTY(color) 1 "interface" SdqWavelengthBox 1 n 1 to back-end "Handle" to "BoxBack" n SdqBoxBackManager (does NOT have ANY 1 instance-state) 1 QAbstractListModel Collection of SdqWavelength BoxSet "handles" (with 1 Collection of update-notification) "handles" 1 SdqModelListWavelength

Class Diagram: A single "BoxBack" instance represents "state". Many "Box" instances are "handles-to" a single "BoxBack" instance (each "Box" reflects the state represented in the "BoxBack"). Case Study (continued): Memory Management: Who Owns What?

Object Ownership Wavelength

// FILE: HelloWavelength.qml

import com.bec.cyto 1.0 SdqWavelengthBoxBack

Item { Wavelength { id: myWave1 SdqWavelengthBox Wavelength wavelengthNm: 488 myWave1 } Wavelength { id: myWave2 SdqWavelengthBoxBack wavelengthNm: 532 } } SdqWavelengthBox myWave2 Object Instantiation SdqBoxBackManager

● Creating "Box" triggers creation of "BoxBack" which triggers creation of "Wavelength"

● All "BoxBack" instances are "owned" by the "Manager" Case Study (continued): Updates From QML: How Does It Work? Referencing Instances in "back-end" Back-End SdqWavelengthBox myWave1 System

SdqWavelengthBoxBack Wavelength SdqWavelengthBox myWave2 Updates From QML:

1. "myWave1" ==> "write" property attempted

2. Call "forwards" to "BoxBack"

3. Value is changed in "Wavelength" instance

4. "BoxBack" notifies all "Box" instances of property change

1. myWave1 "wavelengthChanged"

2. myWave2 "wavelengthChanged" Case Study (continued): Updates From Back-End: How Does It Work?

Referencing Instances in "back-end" Back-End

SdqWavelengthBox System SdqWavelengthBoxBack myWave1 Wavelength

SdqWavelengthBox SdqLaserDeviceBox myWave2 myLaser LaserDevice Updates From Back-End System:

1. "myLaser" ==> notified of change by back-end system (can "poll", monitor network traffic, catch a "system-refresh" event, or otherwise be notified explicitly)

2. "myLaser" notifies all listeners its "Wavelength" property changed

3. "BoxBack" notifies all "Box" instances of property change

1. myWave1 "wavelengthChanged"

2. myWave2 "wavelengthChanged" Side Note: QML Plugins Are Awesome

(Side Note) "Soap-Box": Use QML Plugins!

● QML Plugins are Awesome

● QML Plugins make your life easier

● QML Plugins make you better-looking

● QML Plugins make you more appealing for romantic encounters

● QML Plugins make you taller

● QML Plugins are Awesome

● QML Plugins are Really Awesome

● QML Plugins are Really Really Awesome Summary: USE QML Plugins!!!

…or I will find you…

Side Note #2: Organize Your Projects! (Or you will Die Die Die!!!) ./MyWorkspace/. (Side Note #2) Project Organization: Do whatever Cyto/. you want, but this is a convention that works. Wavelength.hpp Wavelength.cpp (does not matter if you Peers within your workspace CytoBoxed/. maintain "one" or "more-than-one" cooperating "workspaces"): SdqModelListWavelength.cpp SdqModelListWavelength.hpp SdqWavelengthBox.cpp 1. C++ "Reference/Legacy/Hand-maintained" library/module SdqWavelengthBox.hpp SdqWavelengthBoxBack.cpp SdqWavelengthBoxBack.hpp 2. Generated-C++ Declarative-Interface code SdqWavelengthBoxSet.cpp SdqWavelengthBoxSet.hpp 3. QML-Plugin (shared-library) code (may be the same as #2, or several libs in #2 may be combined into a single QML-plugin; does not contain QML files, those are CytoPlugin/. CytoCommonPlugin.cpp developed/tested in (4)) CytoCommonPlugin.pro

4. QML resource files (will be deployed with your plugin shared-lib, commonly used CytoQml/. across your test-QML applications and test-compiled-applications, are referenced qmldir MyWavelengthControl.qml directly by the test applications in the workspace, and (3) for deployment) MyOtherControl.qml CytoCommonQml.pro 5. TestApplication code (for each compiled "*.exe" binary) CytoTestExe1/. main.cpp 6. Test QML Application Code (for each QML application) CytoCommonQml.pro

CytoTestExe2/. NOTE for (3) and (4), that even NESTED plugins are "peers" at the main.cpp workspace-level CytoCommonQml.pro

CytoTestQml1/. NOTE that each project "builds-and-deploys" to the "install-location" for main.cpp the machine when developing across modules/plugins CytoCommonQml.pro Case Study (continued): Deployment (What Do You Need?) When Deploying... Build Products ● Executable Binary – (e.g., "MyProgram.exe"), ● Business-logic shared Example: Users will launch your executable, your library Cyto.dll system is "locked-down" Cyto.lib MyApp.exe Cyto.dll CytoBoxed.dll ● Boxed-shared-library ● Shared Library – (e.g., "CytoBoxed.dll"), CytoBoxed.dll

Example: Other developers will link your DLL CytoBoxed.lib into their executable CytoBoxed/. ● Boxed-headers SdqModelListWavelength.hpp Cyto.dll CytoBoxed.dll SdqWavelengthBox.hpp CytoBoxed/. SdqWavelengthBoxBack.hpp SdqModelListWavelength.hpp Cyto.lib CytoBoxed.lib SdqWavelengthBoxSet.hpp SdqWavelengthBox.hpp SdqWavelengthBoxBack.hpp SdqWavelengthBoxSet.hpp ● QML Plugin – (e.g., "CytoPlugin.dll"), Example: Other developers will load your plugin; ● Plugin-Package Users will run your plugin CytoPlugin.dll qmldir CytoPlugin.dll Cyto.dll CytoBoxed.dll MyControl.qml qmldir MyControl.qml CytoBoxed.lib Cyto.lib Proprietary Headers No Headers are needed to deploy plugins! Are Never Deployed! Shipping C++ Headers (Expensive!) Shipping C++ Headers (*.hpp) is expensive:

● Are often volatile, especially early-in-development

● Can be volatile late-in-development, causing serious configuration problems for distributed teams

● Must be versioned (version must be considered in build-use configurations)

● May be extensive (many headers needed)

● May require significant build infrastructure (coupling to other headers/libraries; language-specific or technology-specific tooling; special build requirements [e.g., "code coverage", "memory/bounds-checking", "compile-enabled" logging or other diagnostics]; compiler versions likely significant)

● Must be managed for use (INCLUDE_PATH from "origin-workspace" may not integrate well with INCLUDE_PATH in "use-workspace")

rd ● May not be able to ship due to distribution restrictions (3 Party licensing, Corporate IP / Legal / Regulatory standards) Never Ship Domain-Specific Headers!

Ship Only What You Need!

● If shipping "linked-executable", application is "locked-down" for use (not for further development), no headers are shipped.

● If using code-generator, only generated-headers are shipped (never need to ship "Wavelength.hpp" !!) (Frees team to make changes as-needed!)

● If deploying QML plugins, no headers are shipped. (QML Plugins Are Awesome!)

Implications: Modules Are Cheap

Implication: Design through "modules-of- declarative-types"

● The "act" of creating a "plugin" is now:

1. Create a new "dir"

2. Define "property-definition-files" for the classes to expose

3. Generate / build / link / install the plugin What would you do if you could "slice" your domain code base (for "free"), where the "slices" (plugin-modules) can be arbitrarily combined? Leveraging Modules During Development

Time / Function-separation of development teams:

● HW / Back-End developers "more-active-earlier", can stabilize module interface files for GUI team

● GUI / Application developers "more-active-later", only need to pay attention when "declarative-interface-definition" files change. (HW designers merely re-compile and re-deploy QML-plugin files, GUI developers are completely unaffected unless properties are *removed*.) Separate HW / System verification from UI verification (HUGE BENEFIT!)

– Easier to "parallelize" work

– Easier to respond to requirements / design changes

– Easier to fix / repair / modify after deployment

Module Interface Documentation

"Declarative-interface-definition" files are clean- and-DOCUMENTED module interface

● Represent an "interface-contract" (through composition of declarative-properties)

● Are checked into version control

● Can be specification for external development teams

● Can be re-configured for different types of users "Slicing" Module Interfaces

Modules are "sliced / re-defined" to serve a function or purpose (by add/remove properties, add/remove def-files)

● Different "applications/end-users" (only accesses properties for "verified" product)

– Different user-products

– Customer extensions

– Customer workflow-customization

– 3rd-Party extensions

● Manufacturing Personnel (accesses factory-configured properties/state)

● Service Personnel (accesses field-diagnostic properties)

● Developers (can create "non-supported" and "experimental" configurations, access restricted properties that may damage hardware if used improperly) "Sliced" Module Benefit: Configuration

"Sliced" modules enable better configuration:

– Better device sub-system emulation (easier during development) – Better verification testing, better testing interfaces – Better field-diagnostics – Better product customization (e.g., gives Sales staff more options to configure product) – Better “forward-compatibility” interfacing with future devices, or 3rd-party devices "Design By Modules" Summary

"Modules-are-cheap" and "Modules-are-defined- through-declative-properties" results in:

● The act of "System Design" is the Definition of "Modules" and their relationships (as it should be!)

● Subsystems are better designed and documented (are more consistent through property conventions and ontologies!)

● Modules are easier to integrate (with better backward / forward- compatibility!)

● Through Modules, the "synchronous-get/set" operations implicitly become (somewhat) asynchronous-bound-property interfaces (this solves many system problems, including "refresh/update" issues!) Case Study Summary

With this example generator:

● Does not "touch" domain-specific C++ headers

● Exposes to QML (for cheap!) by "hooking-into" QWidget apps, MFC apps, etc. (any system with available C++ headers)

● These external QML apps can monitor, externally control, or otherwise "interface" with existing systems (without recompiling existing systems!)

● We NEVER ship legacy/proprietary headers (only "generated" headers if shipping DLLs, no headers if shipping QML plugins) Tricky Things For Your Generator

Generator considerations:

● Must handle “value” and “identity” semantics

● Must allow multiple “instances” in QML to represent the same “single-object” in the underlying system

● State change in underlying object must notify all QML “instances” referencing that underlying object

● Must read / write properties from QML, and from within underlying system

About: The Case-Study Generator

● Is implemented in C++ (would not be difficult to write it in Python / / Ruby)

● Uses C++ templates extensively, (both composition and inheritence)

● Is mostly header-only (next revision will likely make it header-only)

● Uses C++ “type-traits”, Example:

template struct SdqObjectBoxTraits { typedef ITEM_TYPE_TO_BOX TypeItemToBox; typedef SdqObjectBoxBack TypeSdqObjectBoxBack; typedef SdqObjectBox TypeSdqObjectBox; }; Future: Generator Case-Study

Possible Improvements to Generator:

● Adapt to other languages (e.g., generate "Python bindings")

● Generate "Test Cases" that exercise underlying system

● Integration with "build-system" (one-step from "dir-of-def-files" to installed-QML Plugin)

● Make generated "boxing" network-transparent (e.g., QML “boxed-Wavelength” in QML process directly reflects state from change in instance in another process [e.g., over TCP/IP])

● "Normalize" Data Ontologies, generate specification documentation (e.g., "Pretty-PDF" by module, across-modules)

● Make wrapper-lib "header-only" (is currently "mostly" header-only) Who Cares? What systems might benefit?

● Legacy systems, code bases (does not touch code)

● Domain-specific libraries (does not touch code)

● 3rd party libraries you cannot modify

● Embedded systems (external monitoring)

● Hardware control systems (external monitoring)

● Long-running processes (external monitoring)

● Time-sensitive components (does not interfere with internal timing)

● “Deployment” of system for different types of users (different interfaces exposed for “users”, “manufacturing-personnel”, “service-personnel”, “developers”)

● Can “re-wrap” GUI in QML on top of EXISTING SYSTEM implemented with MFC, QWidgets, or other any other GUI library for which you have (or can create) C++ headers!

Case Study Assertion

QML is NOT JUST for GUI. Exposing to QML is re-wrapping your (imperative) C++ abstractions to declarative-properties that may be bound. It enables “separation” on a scale that implicitly moves your “synchronous” API to a (somewhat) “asynchronous” API. THIS IS A BIG DEAL. Describing modules through QML enable modules to “hook-together” through BOUND PROPERTIES. This is like two modules with “super-magnets” for interfaces that “hook- together” seamlessly, with little effort, correctly, handling all the corner cases. THIS IS AMAZING.

Example: A "PeerNetwork" module (no GUI), and a "Device" module (no GUI), could "bind" to each other through "properties" (because they share the same Data Ontology), even though they were developed independently; They bind to each other (and do so correctly!) for the first time at deployment. Summary

In Conclusion:

● Use QML Plugins

● A code-generator can be useful

● "Modules" should be "cheap", at which point you can "Design By Modules", and you get:

– Better (Declarative) Interfaces,

– Suited to the purpose / function,

– With the ability to SCALE YOUR SYSTEMS. Thank You!

Special Thanks:

David Van Maren Beckman Coulter, Inc.

The Qt Community ([email protected], [email protected]) Alan Alpert