+ All Categories
Home > Documents > Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In...

Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In...

Date post: 19-Aug-2019
Category:
Upload: lydan
View: 226 times
Download: 0 times
Share this document with a friend
45
Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.
Transcript
Page 1: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Wrapping C++ Objects

For Property Exposure In QML

Charley Bay

Beckman Coulter, Inc.

Page 2: Wrapping C++ Objects For Property Exposure In QML · 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()

Page 3: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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"

Page 4: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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"!

Page 5: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Deep / Rich Types: Lightweight Examples

Example "Lightweight-Deep/Rich" types

– class Wavelength

– class Voltage

– class Parameter

– class Detector

– class SpectraFilter

Page 6: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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!

Page 7: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Deep / Rich Types: Heavyweight Examples

Example "Heavyweight-Deep/Rich" types

– class FluidicsSubsystem

– class OpticsSubsystem

– class CytometerInstrument

– class ExperimentArchive

Page 8: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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.

Page 9: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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

● If 3rd Party code that cannot be modified

● If developed / tested at unit / functional level with as little

coupling as possible

Page 10: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

An Example: class Wavelength

● Lightweight class (only data member is “double”)

● Has domain-rules

– Range-bounds

– Label formatting

– Interpreted as “color”

● Is ubiquitous

throughout domain-

specific APIs

bool isBlue(void) const;

bool isGreen(void) const;

bool isOrange(void) const;

bool isRed(void) const;

bool isViolet(void) const;

bool isYellow(void) const;

bool isVisible(void) const;

class Wavelength

{

double value_nm_; //IS ONLY DATA MEMBER!

public:

Wavelength(double value_nm);

Wavelength& operator=(double value_nm);

Wavelength& operator=(Wavelength& other);

};

QString getAsLabel(void) const;

Page 11: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

//FILE: Wavelength.hpp

//

#ifndef Wavelength_hpp

#define Wavelength_hpp

#include "CppFix.hpp"

class SdString;

/// The Wavelength is a utility class to represent a specific

/// "wavelength", such as that emitted by a laser, or absorbed

/// or emitted by a fluorescent dye.

///

/// RECALL: Logically, we intend to represent wavelengths typical

/// for cytometry. So, that our measure is in "nanometer", and

/// we are useful for things related to lasers and flourescent

/// dyes.

///

class DECL_SD_DLL_UTILS_MATH Wavelength

{

public:

enum SpectrumNm {

WAVELENGTH_NM_NONE = 0,

WAVELENGTH_NM_MIN = 1,

WAVELENGTH_NM_VISIBLE_MIN = 380,

WAVELENGTH_NM_VIOLET_MIN = 380,

WAVELENGTH_NM_VIOLET_MAX = 449,

WAVELENGTH_NM_BLUE_MIN = 450,

WAVELENGTH_NM_BLUE_MAX = 494,

WAVELENGTH_NM_GREEN_MIN = 495,

WAVELENGTH_NM_GREEN_MAX = 569,

WAVELENGTH_NM_YELLOW_MIN = 570,

WAVELENGTH_NM_YELLOW_MAX = 589,

WAVELENGTH_NM_ORANGE_MIN = 590,

WAVELENGTH_NM_ORANGE_MAX = 619,

WAVELENGTH_NM_RED_MIN = 620,

WAVELENGTH_NM_RED_MAX = 750,

WAVELENGTH_NM_VISIBLE_MAX = 750,

WAVELENGTH_NM_MAX = 0x7FFF,

};

enum {

WAVELENGTH_RANGE_NM_BELOW_VISIBLE = WAVELENGTH_NM_VISIBLE_MIN - WAVELENGTH_NM_MIN + 1,

WAVELENGTH_RANGE_NM_ABOVE_VISIBLE = WAVELENGTH_NM_MAX - WAVELENGTH_NM_VISIBLE_MAX + 1,

WAVELENGTH_RANGE_NM_VISIBLE = WAVELENGTH_NM_VISIBLE_MAX - WAVELENGTH_NM_VISIBLE_MIN + 1,

WAVELENGTH_RANGE_NM_VIOLET = WAVELENGTH_NM_VIOLET_MAX - WAVELENGTH_NM_VIOLET_MIN + 1,

WAVELENGTH_RANGE_NM_BLUE = WAVELENGTH_NM_BLUE_MAX - WAVELENGTH_NM_BLUE_MIN + 1,

WAVELENGTH_RANGE_NM_GREEN = WAVELENGTH_NM_GREEN_MAX - WAVELENGTH_NM_GREEN_MIN + 1,

WAVELENGTH_RANGE_NM_YELLOW = WAVELENGTH_NM_YELLOW_MAX - WAVELENGTH_NM_YELLOW_MIN + 1,

WAVELENGTH_RANGE_NM_ORANGE = WAVELENGTH_NM_ORANGE_MAX - WAVELENGTH_NM_ORANGE_MIN + 1,

WAVELENGTH_RANGE_NM_RED = WAVELENGTH_NM_RED_MAX - WAVELENGTH_NM_RED_MIN + 1,

};

private:

// Track our state in nanometer units.

//

// RECALL: We have state if this value is >0 (zero

// means we have no state).

//

// UINT16 wavelength_nanometers_;

// RECALL: We MUST be able to handle "fractional" values

// of nanometers, so we are floationg point.

//

double wavelength_nanometers_;

public:

Wavelength(void);

explicit Wavelength(double wavelength_nanometers);

explicit Wavelength(const SdString& string_parseable);

Wavelength(const Wavelength& other);

~Wavelength(void);

Wavelength& operator=(const Wavelength& other);

Wavelength& operator+=(const Wavelength& other);

Wavelength& operator+=(double nm_value);

Wavelength& operator-=(const Wavelength& other);

Wavelength& operator-=(double nm_value);

Wavelength& operator++(void); // prefix

const Wavelength operator++(int); // postfix

Wavelength& operator--(void); // prefix

const Wavelength operator--(int); // postfix

const bool operator==(const Wavelength& other) const;

const bool operator>(const Wavelength& other) const;

const bool operator>=(const Wavelength& other) const;

const bool operator<(const Wavelength& other) const;

const bool operator<=(const Wavelength& other) const;

const bool operator!=(const Wavelength& other) const;

const double operator+(const Wavelength& other) const;

const double operator+(double nm_value) const;

const double operator-(const Wavelength& other) const;

const double operator-(double nm_value) const;

//------------------------------------------------------------------------

// P U B L I C

//------------------------------------------------------------------------

void addToWavelength(const Wavelength& nm_to_add);

void addToWavelength(double nm_to_add);

const bool appendToString(SdString& string) const;

const bool appendToStringMaybeAppendNm(

SdString& string,

bool flag_append_nm) const;

const bool appendToStringParseable(SdString& string) const;

const bool appendToStringUserInterface(SdString& string) const;

void clear(void);

const bool getAsString(SdString& string) const;

const SdString getAsString(void) const;

const bool getAsStringParseable(SdString& string) const;

const SdString getAsStringParseable(void) const;

const bool getAsStringUserInterface(SdString& string) const;

const SdString getAsStringUserInterface(void) const;

const bool getAsWavelengthAddOther(

Wavelength& wavelength_as,

const Wavelength& other) const;

const Wavelength getAsWavelengthAddOther(const Wavelength& other) const;

const bool getAsWavelengthSubtractOther(

Wavelength& wavelength_as,

const Wavelength& other) const;

const Wavelength getAsWavelengthSubtractOther(const Wavelength& other) const;

void getRgbPercentForWavelength(

double& r_0_to_1,

double& g_0_to_1,

double& b_0_to_1,

double gamma_0_to_1 = 1.0) const;

void getRgbValueForWavelength(

UINT32& r_value,

UINT32& g_value,

UINT32& b_value,

double gamma_0_to_1 = 1.0,

UINT32 rgb_each_value_max = 255) const;

const double getWavelengthInNanometers(void) const;

const long getWavelengthInNanometersAsLong(void) const;

const double getWavelengthInNanometersAddOther(const Wavelength& other) const;

const double getWavelengthInNanometersSubtractOther(const Wavelength& other) const;

const bool hasState(void) const;

const bool hasStateWithFractionalNm(void) const;

const bool hasStateWithWholeNm(void) const;

const bool hasWavelength(double wavelength_value) const;

const bool isEqual(const Wavelength& other) const;

const bool isGreaterThan(const Wavelength& other) const;

const bool isGreaterThanOrEqual(const Wavelength& other) const;

const bool isLessThan(const Wavelength& other) const;

const bool isLessThanOrEqual(const Wavelength& other) const;

const bool isNotEqual(const Wavelength& other) const;

const bool isWavelengthAboveVisible(void) const;

const bool isWavelengthBelowVisible(void) const;

const bool isWavelengthVisible(void) const;

const bool isWavelengthVisibleBlue(void) const;

const bool isWavelengthVisibleGreen(void) const;

const bool isWavelengthVisibleOrange(void) const;

const bool isWavelengthVisibleRed(void) const;

const bool isWavelengthVisibleViolet(void) const;

const bool isWavelengthVisibleYellow(void) const;

void setFrom(const Wavelength& other);

void setFrom(double wavelength_nanometers);

const bool setFromStringParseable(const SdString& string);

const bool setFromStringParseable(const SdString& string, long& start_index);

void setFromWavelengthInNanometers(double wavelength_nanometers);

void setFromWavelengthNmMax(void);

void setFromWavelengthNmMin(void);

void subtractFromWavelength(const Wavelength& nm_to_subtract);

void subtractFromWavelength(double nm_to_subtract);

//------------------------------------------------------------------------

// S T A T I C

//------------------------------------------------------------------------

static const CompareResult& Compare(

const Wavelength& operand0,

const Wavelength& operand1);

static const Wavelength& GetInstanceEmpty(void);

static void GetRgbPercentForWavelength(

double& r_0_to_1,

double& g_0_to_1,

double& b_0_to_1,

double wavelength_in_nm,

double gamma_0_to_1 = 1.0);

static void GetRgbValueForWavelength(

UINT32& r_value,

UINT32& g_value,

UINT32& b_value,

double wavelength_in_nm,

double gamma_0_to_1 = 1.0,

UINT32 rgb_each_value_max = 255);

static const SdString& GetStringNm(void);

static const double GetWavelengthClean(double wavelength_nanometers);

static const bool IsOkWavelengthInNanometers(double wavelength_nanometers);

static const bool IsWavelengthAboveVisible(double wavelength_nanometers);

static const bool IsWavelengthBelowVisible(double wavelength_nanometers);

static const bool IsWavelengthVisible(double wavelength_nanometers);

static const bool IsWavelengthVisibleBlue(double wavelength_nanometers);

static const bool IsWavelengthVisibleGreen(double wavelength_nanometers);

static const bool IsWavelengthVisibleOrange(double wavelength_nanometers);

static const bool IsWavelengthVisibleRed(double wavelength_nanometers);

static const bool IsWavelengthVisibleViolet(double wavelength_nanometers);

static const bool IsWavelengthVisibleYellow(double wavelength_nanometers);

static void SwapValues(

Wavelength& operand0,

Wavelength& operand1);

};

#endif

//FILE:

//

#include <limits> // For std::numeric_limits<double>

#include <algorithm> // For max()

#include "Wavelength.hpp"

#include "SdString.hpp"

#include "TypeUtil.hpp"

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

static const char* STR_NM_ = "nm";

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

Wavelength::Wavelength(void)

:wavelength_nanometers_(WAVELENGTH_NM_NONE)

{

// clear();

}

Wavelength::Wavelength(double wavelength_nanometers)

:wavelength_nanometers_(WAVELENGTH_NM_NONE)

{

// clear();

setFromWavelengthInNanometers(wavelength_nanometers);

}

Wavelength::Wavelength(const SdString& string_parseable)

:wavelength_nanometers_(WAVELENGTH_NM_NONE)

{

// clear();

setFromStringParseable(string_parseable);

}

Wavelength::Wavelength(const Wavelength& other)

{

// clear();

setFrom(other);

}

Wavelength::~Wavelength(void)

{

}

Wavelength&

Wavelength

::operator=(const Wavelength& other)

{

setFrom(other);

return *this;

}

Wavelength&

Wavelength

::operator+=(const Wavelength& other)

{

addToWavelength(other);

return *this;

}

Wavelength&

Wavelength

::operator+=(double nm_value)

{

addToWavelength(nm_value);

return *this;

}

Wavelength&

Wavelength

::operator-=(const Wavelength& other)

{

subtractFromWavelength(other);

return *this;

}

Wavelength&

Wavelength

::operator-=(double nm_value)

{

subtractFromWavelength(nm_value);

return *this;

}

Wavelength&

Wavelength

::operator++(void)

{

// prefix

addToWavelength(1);

return *this;

}

const Wavelength

Wavelength

::operator++(int)

{

// postfix

Wavelength temp(*this);

this->operator ++();

return temp;

}

Wavelength&

Wavelength

::operator--(void)

{

// prefix

subtractFromWavelength(1);

return *this;

}

const Wavelength

Wavelength

::operator--(int)

{

// postfix

Wavelength temp(*this);

this->operator --();

return temp;

}

const bool

Wavelength

::operator==(const Wavelength& other) const

{

return isEqual(other);

}

const bool

Wavelength

::operator>(const Wavelength& other) const

{

return isGreaterThan(other);

}

const bool

Wavelength

::operator>=(const Wavelength& other) const

{

return isGreaterThanOrEqual(other);

}

const bool

Wavelength

::operator<(const Wavelength& other) const

{

return isLessThan(other);

}

const bool

Wavelength

::operator<=(const Wavelength& other) const

{

return isLessThanOrEqual(other);

}

const bool

Wavelength

::operator!=(const Wavelength& other) const

{

return isNotEqual(other);

}

const double

Wavelength

::operator+(const Wavelength& other) const

{

return operator+(other.wavelength_nanometers_);

}

const double

Wavelength

::operator+(double nm_value) const

{

return ((INT32)wavelength_nanometers_ + (INT32)nm_value);

}

const double

Wavelength

::operator-(const Wavelength& other) const

{

return operator-(other.wavelength_nanometers_);

}

const double

Wavelength

::operator-(double nm_value) const

{

return ((INT32)wavelength_nanometers_ - (INT32)nm_value);

}

//------------------------------------------------------------------------

// P U B L I C

//------------------------------------------------------------------------

void

Wavelength

::addToWavelength(const Wavelength& nm_to_add)

{

addToWavelength(nm_to_add.wavelength_nanometers_);

}

void

Wavelength

::addToWavelength(double nm_to_add)

{

// RECALL: We ENFORCE that our value CANNOT be negative, so

// we guard against "wrap" for the sign bit.

//

if((wavelength_nanometers_ + nm_to_add) < 0)

{

clear();

}

else

{

setFromWavelengthInNanometers(

wavelength_nanometers_ + nm_to_add);

}

//if(nm_to_add < 0)

//{ // We are subtracting (i.e., adding a positive number).

// if((wavelength_nanometers_ + nm_to_add) < 0)

// { // We wrapped below zero, so correct to zero.

// clear();

// }

// else

// {

// wavelength_nanometers_ += nm_to_add;

// }

//}

//else

//{ // We add a positive number.

// if((wavelength_nanometers_ + nm_to_add) > WAVELENGTH_NM_MAX)

// { // We wrapped to high, so correct to the max.

// wavelength_nanometers_ = WAVELENGTH_NM_MAX;

// }

// else

// {

// wavelength_nanometers_ += nm_to_add;

// }

//}

}

const bool

Wavelength

::appendToString(SdString& string) const

{

return appendToStringUserInterface(string);

}

const bool

Wavelength

::appendToStringMaybeAppendNm(SdString& string,

bool flag_append_nm) const

{

// RECALL: We ATTEMPT to append integer precision, unless

// we have an explicit non-epsilon mantissa.

if(hasStateWithFractionalNm())

{ // We have a fractional nanometer value.

// string.appendFromTypeFloating(wavelength_nanometers_, 1/*num_digits*/, false/*allow_scientific_notation*/);

string.appendFromTypeFloating(wavelength_nanometers_, 0/*num_decimal_places*/);

}

else

{ // We are an integer value, or we do not have state.

string.appendFromTypeInteger((long)wavelength_nanometers_);

}

if(flag_append_nm)

{

string.appendFrom(GetStringNm());

}

return hasState();

}

const bool

Wavelength

::appendToStringParseable(SdString& string) const

{

return appendToStringMaybeAppendNm(string, false/*flag_append_nm*/);

}

const bool

Wavelength

::appendToStringUserInterface(SdString& string) const

{

return appendToStringMaybeAppendNm(string, true/*flag_append_nm*/);

}

void

Wavelength

::clear(void)

{

wavelength_nanometers_ = WAVELENGTH_NM_NONE;

}

const bool

Wavelength

::getAsString(SdString& string) const

{

string.clear();

return appendToString(string);

}

const SdString

Wavelength

::getAsString(void) const

{

SdString string;

appendToString(string);

return string;

}

const bool

Wavelength

::getAsStringParseable(SdString& string) const

{

string.clear();

return appendToStringParseable(string);

}

const SdString

Wavelength

::getAsStringParseable(void) const

{

SdString string;

appendToStringParseable(string);

return string;

}

const bool

Wavelength

::getAsStringUserInterface(SdString& string) const

{

string.clear();

return appendToStringUserInterface(string);

}

const SdString

Wavelength

::getAsStringUserInterface(void) const

{

SdString string;

appendToStringUserInterface(string);

return string;

}

const bool

Wavelength

::getAsWavelengthAddOther(

Wavelength& wavelength_as,

const Wavelength& other) const

{

wavelength_as.setFrom(*this);

wavelength_as.addToWavelength(other);

return wavelength_as.hasState();

}

const Wavelength

Wavelength

::getAsWavelengthAddOther(

const Wavelength& other) const

{

Wavelength wavelength_as;

getAsWavelengthAddOther(

wavelength_as,

other);

return wavelength_as;

}

const bool

Wavelength

::getAsWavelengthSubtractOther(

Wavelength& wavelength_as,

const Wavelength& other) const

{

wavelength_as.setFrom(*this);

wavelength_as.subtractFromWavelength(other);

return wavelength_as.hasState();

}

const Wavelength

Wavelength

::getAsWavelengthSubtractOther(const Wavelength& other) const

{

Wavelength wavelength_as;

getAsWavelengthSubtractOther(

wavelength_as,

other);

return wavelength_as;

}

void

Wavelength

::getRgbPercentForWavelength(double& r_0_to_1,

double& g_0_to_1,

double& b_0_to_1,

double gamma_0_to_1) const

{

GetRgbPercentForWavelength(r_0_to_1,

g_0_to_1,

b_0_to_1,

getWavelengthInNanometers(),

gamma_0_to_1);

}

void

Wavelength

::getRgbValueForWavelength(UINT32& r_value,

UINT32& g_value,

UINT32& b_value,

double gamma_0_to_1,

UINT32 rgb_each_value_max) const

{

GetRgbValueForWavelength(r_value,

g_value,

b_value,

getWavelengthInNanometers(),

gamma_0_to_1,

rgb_each_value_max);

}

const double

Wavelength

::getWavelengthInNanometers(void) const

{

return wavelength_nanometers_;

}

const long

Wavelength

::getWavelengthInNanometersAsLong(void) const

{

// RECALL: We "round up" or "round down" to convert

// to a long.

return (long)(wavelength_nanometers_ + 0.5);

}

const double

Wavelength

::getWavelengthInNanometersAddOther(const Wavelength& other) const

{

return wavelength_nanometers_ + other.wavelength_nanometers_;

}

const double

Wavelength

::getWavelengthInNanometersSubtractOther(const Wavelength& other) const

{

return wavelength_nanometers_ - other.wavelength_nanometers_;

}

const bool

Wavelength

::hasState(void) const

{

return (wavelength_nanometers_ != WAVELENGTH_NM_NONE);

}

const bool

Wavelength

::hasStateWithFractionalNm(void) const

{

return hasState() && TypeUtil::IsValueNonZero((double)(wavelength_nanometers_ - ((long)wavelength_nanometers_)));

}

const bool

Wavelength

::hasStateWithWholeNm(void) const

{

return hasState() && (!hasStateWithFractionalNm());

}

const bool

Wavelength

::hasWavelength(double wavelength_value) const

{

return TypeUtil::IsValuesEqual(wavelength_value, wavelength_nanometers_);

}

const bool

Wavelength

::isEqual(const Wavelength& other) const

{

// if(this != &other)

// {

return wavelength_nanometers_ == other.wavelength_nanometers_;

// }

// return true;

}

const bool

Wavelength

::isGreaterThan(const Wavelength& other) const

{

// if(this != &other)

// {

return wavelength_nanometers_ > other.wavelength_nanometers_;

// }

// return false;

}

const bool

Wavelength

::isGreaterThanOrEqual(const Wavelength& other) const

{

// if(this != &other)

// {

return wavelength_nanometers_ >= other.wavelength_nanometers_;

// }

// return false;

}

const bool

Wavelength

::isLessThan(const Wavelength& other) const

{

return !isGreaterThanOrEqual(other);

}

const bool

Wavelength

::isLessThanOrEqual(const Wavelength& other) const

{

return !isGreaterThan(other);

}

const bool

Wavelength

::isNotEqual(const Wavelength& other) const

{

return !isEqual(other);

}

const bool

Wavelength

::isWavelengthAboveVisible(void) const

{

return IsWavelengthAboveVisible(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthBelowVisible(void) const

{

return IsWavelengthBelowVisible(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthVisible(void) const

{

return IsWavelengthVisible(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthVisibleBlue(void) const

{

return IsWavelengthVisibleBlue(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthVisibleGreen(void) const

{

return IsWavelengthVisibleGreen(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthVisibleOrange(void) const

{

return IsWavelengthVisibleOrange(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthVisibleRed(void) const

{

return IsWavelengthVisibleRed(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthVisibleViolet(void) const

{

return IsWavelengthVisibleViolet(wavelength_nanometers_);

}

const bool

Wavelength

::isWavelengthVisibleYellow(void) const

{

return IsWavelengthVisibleYellow(wavelength_nanometers_);

}

void

Wavelength

::setFrom(const Wavelength& other)

{

// if(this != &other)

// {

wavelength_nanometers_ = other.wavelength_nanometers_;

// }

}

void

Wavelength

::setFrom(double wavelength_nanometers)

{

setFromWavelengthInNanometers(wavelength_nanometers);

}

const bool

Wavelength

::setFromStringParseable(const SdString& string)

{

long start_index = 0;

return setFromStringParseable(string, start_index);

}

const bool

Wavelength

::setFromStringParseable(

const SdString& string,

long& start_index)

{

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

static const SdString STRING_NM_ (STR_NM_);

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

clear();

long start_index_temp = string.findIndexFirstCharNonWhitespace(start_index);

if(start_index_temp >= 0)

{ // We have something in this string at-or-after the index specified.

//

if(string.isCharDigit(start_index_temp))

{

double value;

if(string.parseFloatingStartingAt(value, start_index_temp))

{

if(IsOkWavelengthInNanometers((double)value) || (value == WAVELENGTH_NM_NONE))

{ // We read an acceptable value, or successfully parsed a zero.

// RECALL: We MAY have an optional "nm", so if we see it,

// we should skip it.

if(start_index_temp >= 0)

{

if((start_index_temp = string.findIndexFirstCharNonWhitespace(start_index_temp)) >= 0)

{

if(string.isMatchSubThis(start_index_temp, STRING_NM_, false/*case_sensitive*/))

{

start_index_temp += STRING_NM_.getLength();

}

}

}

setFromWavelengthInNanometers(value);

start_index = start_index_temp;

return true;

}

}

}

// If we reach here, there was a parse error. We were given

// a non-empty string, but we could not parse it as a Wavelength.

//

return false;

}

// If we reach here, we were given an empty string, or there was only

// whitespace at-and-after the location given.

//

// RECALL: This is NOT a parse error. Rather, we have an explicit

// "empty" wavelength.

//

start_index = string.getLength();

ASSERT_LOGIC(!hasState());

return true;

}

void

Wavelength

::setFromWavelengthInNanometers(double wavelength_nanometers)

{

if(wavelength_nanometers == WAVELENGTH_NM_NONE)

clear();

else

wavelength_nanometers_ = GetWavelengthClean(wavelength_nanometers);

}

void

Wavelength

::setFromWavelengthNmMax(void)

{

setFromWavelengthInNanometers(WAVELENGTH_NM_MAX);

}

void

Wavelength

::setFromWavelengthNmMin(void)

{

setFromWavelengthInNanometers(WAVELENGTH_NM_MIN);

}

void

Wavelength

::subtractFromWavelength(const Wavelength& nm_to_subtract)

{

subtractFromWavelength(nm_to_subtract.getWavelengthInNanometers());

}

void

Wavelength

::subtractFromWavelength(double nm_to_subtract)

{

addToWavelength(-nm_to_subtract);

}

//------------------------------------------------------------------------

// S T A T I C

//------------------------------------------------------------------------

const CompareResult&

Wavelength

::Compare(

const Wavelength& operand0,

const Wavelength& operand1)

{

if(&operand0 != &operand1)

{

return CompareResult::GetCompareResultFromFloatings(

operand0.wavelength_nanometers_,

operand1.wavelength_nanometers_);

}

return CompareResult::GetInstanceIsEqual();

}

const Wavelength&

Wavelength

::GetInstanceEmpty(void)

{

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

static const Wavelength INSTANCE_EMPTY_;

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

return INSTANCE_EMPTY_;

}

void

Wavelength

::GetRgbPercentForWavelength(double& r_0_to_1,

double& g_0_to_1,

double& b_0_to_1,

double wavelength_in_nm,

double gamma_0_to_1)

{

// red, green, blue component in range 0.0 .. 1.0.

r_0_to_1 = g_0_to_1 = b_0_to_1 = 0;

//intensity 0.0 .. 1.0 based on drop off in vision at low/high wavelengths

double intensity_0_to_1 = 1;

// We use different linear interpolations on different bands.

// These numbers mark the upper bound of each band.

const static int num_bands = 10;

const static double bands[num_bands] = { 380, 420, 440, 490, 510, 580, 645, 700, 780, std::numeric_limits<double>::max() };

// Figure out which band we fall in. A point on the edge

// is considered part of the lower band.

int band = (num_bands - 1);

for ( int i=0; i<num_bands; i++ ) {

if ( wavelength_in_nm <= bands[i] ) {

band = i;

break;

}

}

switch ( band ) {

case 0:

// wavelength_in_nm <= 380 (Invisible, but we need *some* color)

r_0_to_1 = 0.35;

g_0_to_1 = 0;

b_0_to_1 = 1;

intensity_0_to_1 = 0.3;

break;

case 1:

// 380 < wavelength_in_nm <= 420, violet

r_0_to_1 = std::max(0.35, -(wavelength_in_nm-440)/(440-380));

g_0_to_1 = 0;

b_0_to_1 = 1;

intensity_0_to_1 = 0.3 + 0.7*(wavelength_in_nm-380)/(420-380);

break;

case 2:

// 420 < wavelength_in_nm <= 440, blue

r_0_to_1 = -(wavelength_in_nm-440)/(440-380);

g_0_to_1 = 0;

b_0_to_1 = 1;

break;

case 3:

// 440 < wavelength_in_nm <= 490, blue green

r_0_to_1 = 0;

g_0_to_1 = (wavelength_in_nm-440)/(490-440);

b_0_to_1 = 1;

break;

case 4:

// 490 < wavelength_in_nm <= 510, deep blue green

r_0_to_1 = 0;

g_0_to_1 = 1;

b_0_to_1 = -(wavelength_in_nm-510)/(510-490);

break;

case 5:

// 510 < wavelength_in_nm <= 580, green

r_0_to_1 = (wavelength_in_nm-510)/(580-510);

g_0_to_1 = 1;

b_0_to_1 = 0;

break;

case 6:

// 580 < wavelength_in_nm <= 645, orange

r_0_to_1 = 1;

g_0_to_1 = (645-wavelength_in_nm)/(645-580);

b_0_to_1 = 0;

break;

case 7:

// 645 < wavelength_in_nm <= 700, red

r_0_to_1 = 1;

g_0_to_1 = 0;

b_0_to_1 = 0;

break;

case 8:

// 700 < wavelength_in_nm <= 780, deep red

r_0_to_1 = 1;

g_0_to_1 = 0;

b_0_to_1 = 0;

intensity_0_to_1 = 0.3 + 0.7*(780-wavelength_in_nm)/(780-700);

break;

case 9:

// 780 < wavelength_in_nm (Invisible, but we need *some* color)

r_0_to_1 = 1;

g_0_to_1 = 0;

b_0_to_1 = 0;

intensity_0_to_1 = 0.3;

break;

} // end switch

// apply intensity and gamma corrections.

intensity_0_to_1 *= gamma_0_to_1;

r_0_to_1 *= intensity_0_to_1;

g_0_to_1 *= intensity_0_to_1;

b_0_to_1 *= intensity_0_to_1;

}

void

Wavelength

::GetRgbValueForWavelength(UINT32& r_value,

UINT32& g_value,

UINT32& b_value,

double wavelength_in_nm,

double gamma_0_to_1,

UINT32 rgb_each_value_max)

{

double r_0_to_1, g_0_to_1, b_0_to_1;

GetRgbPercentForWavelength(r_0_to_1,

g_0_to_1,

b_0_to_1,

wavelength_in_nm,

gamma_0_to_1);

// Convert to the allowed range (e.g., 0...255)

r_value = (UINT32)(r_0_to_1 * rgb_each_value_max);

g_value = (UINT32)(g_0_to_1 * rgb_each_value_max);

b_value = (UINT32)(b_0_to_1 * rgb_each_value_max);

}

const SdString&

Wavelength

::GetStringNm(void)

{

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

static const SdString STRING_NM_ (STR_NM_);

//------------------------------------------------------------------------

//------------------------------------------------------------------------

//------------------------------------------------------------------------

return STRING_NM_;

}

const double

Wavelength

::GetWavelengthClean(double wavelength_nanometers)

{

if(wavelength_nanometers > WAVELENGTH_NM_MAX)

return WAVELENGTH_NM_MAX;

if(wavelength_nanometers < WAVELENGTH_NM_MIN)

return WAVELENGTH_NM_MIN;

return (double)wavelength_nanometers;

}

const bool

Wavelength

::IsOkWavelengthInNanometers(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_MAX);

}

const bool

Wavelength

::IsWavelengthAboveVisible(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_MIN)

&&

(wavelength_nanometers < WAVELENGTH_NM_VISIBLE_MIN);

}

const bool

Wavelength

::IsWavelengthBelowVisible(double wavelength_nanometers)

{

return (wavelength_nanometers > WAVELENGTH_NM_VISIBLE_MAX)

&&

(wavelength_nanometers <= WAVELENGTH_NM_MAX);

}

const bool

Wavelength

::IsWavelengthVisible(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_VISIBLE_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_VISIBLE_MAX);

}

const bool

Wavelength

::IsWavelengthVisibleBlue(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_BLUE_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_BLUE_MAX);

}

const bool

Wavelength

::IsWavelengthVisibleGreen(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_GREEN_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_GREEN_MAX);

}

const bool

Wavelength

::IsWavelengthVisibleOrange(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_ORANGE_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_ORANGE_MAX);

}

const bool

Wavelength

::IsWavelengthVisibleRed(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_RED_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_RED_MAX);

}

const bool

Wavelength

::IsWavelengthVisibleViolet(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_VIOLET_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_VIOLET_MAX);

}

const bool

Wavelength

::IsWavelengthVisibleYellow(double wavelength_nanometers)

{

return (wavelength_nanometers >= WAVELENGTH_NM_YELLOW_MIN)

&&

(wavelength_nanometers <= WAVELENGTH_NM_YELLOW_MAX);

}

void

Wavelength

::SwapValues(Wavelength& wavelength0,

Wavelength& wavelength1)

{

double temp = wavelength0.wavelength_nanometers_;

wavelength0.wavelength_nanometers_ = wavelength1.wavelength_nanometers_;

wavelength1.wavelength_nanometers_ = temp;

}

As It Really Exists: class Wavelength

Page 12: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

static QVariant GetQVariantForEnumRoleEnum(

const SdqWavelengthBox& object_box_value,

SdqWavelengthBox::EnumRole enum_role);

static const char** GetStrArrayEnumRoleNames(void);

public:

enum EnumRole {

ENUM_ROLE_FIRST ,

ROLE_AS_TEXT = ENUM_ROLE_FIRST,

ROLE_COLOR ,

ROLE_HAS_STATE ,

ROLE_WAVELENGTH_NM ,

ENUM_ROLE_LAST = ROLE_WAVELENGTH_NM,

};

enum {

NUM_ENUM_ROLES = ENUM_ROLE_LAST - ENUM_ROLE_FIRST + 1,

};

Wrapped In QObject: "SdqWavelengthBox"

● We only care about

"properties"!

● Some "get / set"

utility functions (to support

properties)

● Plus (optional!):

Utility "enums" and

functions to support QAbstractItemModel

(for a "model / collection"-of-Wavelength"

instances)

class SdqWavelengthBox : public QObject

{

Q_OBJECT

Q_PROPERTY(const bool hasState READ hasState NOTIFY wavelengthChanged)

Q_PROPERTY(const qreal wavelengthNm READ wavelengthNm WRITE

setWavelengthNm NOTIFY wavelengthChanged)

Q_PROPERTY(const QString asText READ asText NOTIFY wavelengthChanged)

Q_PROPERTY(const QColor color READ color NOTIFY wavelengthChanged)

private:

Wavelength my_wavelength_; // WRAPPED DATA MEMBER!

signals:

void wavelengthChanged(void) const;

public:

// ...CTORs, DTOR…

const bool hasState(void) const;

const qreal wavelengthNm(void) const;

void setWavelengthNm(qreal value_new);

const QString asText(void) const;

const QColor color(void) const;

};

//-----------------

// RECALL: To convert our type easily "to/from" "QVariant",

// we must EXPLICITLY declare the meta-type.

//

Q_DECLARE_METATYPE(SdqWavelengthBox)

//-----------------

#endif // SdqWavelengthBox_hpp

Page 13: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Registering Our "SdqWavelengthBox"

In application, or QML-

plugin derived from QQmlExtensionPlugin,

register the type into the

QML type-system.

This "translates" the

"type-wrapper" of "SdqWavelengthBox" so

that QML only sees the type as "Wavelength"

(which is ALWAYS what

you want!)

#include <QtQml/QQmlExtensionPlugin>

#include <QtQml/qqml.h>

// ...

#include "SdqWavelengthBox.hpp"

class CytoQmlPlugin : public QQmlExtensionPlugin

{

Q_OBJECT

Q_PLUGIN_METADATA(IID "com.bec.cyto")

public:

void registerTypes(const char *uri)

{

qmlRegisterType<SdqWavelengthBox>( uri, //const char * uri,

1, //int versionMajor,

0, //int versionMinor,

"Wavelength"); //const char * qmlName) }

};

Page 14: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Using Our "SdqWavelengthBox"

In QML file:

● Import your plugin (that

registered/exported "SdqWavelengthBox" to

the QML type system)

● Use it (with the name "Wavelength")

(This example imports to the namespace "Bec", which is

optional.)

// FILE: HelloWavelength.qml

import QtQuick 2.1

import QtQuick.Controls 1.0

import QtQuick.Window 2.0

import com.bec.cyto 1.0 as Bec

ApplicationWindow {

title: qsTr("TestHelloWavelength")

width: 360

height: 360

Bec.Wavelength {

id: myWavelength

wavelengthNm: mySlider.value

} Rectangle {

id: myRect

anchors.fill: parent

color: myWavelength.color

Label {

id: myLabel

anchors.centerIn: myRect

text: myWavelength.asText

font.pixelSize: 48

font.italic: true

color: "white"

}

Slider {

id: mySlider

anchors.top: myLabel.bottom

width: myRect.width

maximumValue: 800

minimumValue: 300

stepSize: 1.0

value: myWavelength.wavelengthNm

}

}

}

Launch

HelloWavelength.qml

Page 15: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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)"…

Page 16: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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")

Page 17: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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<Wavelength>"…)

● 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"

Page 18: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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.

Page 19: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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

}

Page 20: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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

Page 21: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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/.

Detector.sdgen_sdqbox

SpectraFilter.sdgen_sdqbox

Wavelength.sdgen_sdqbox

5. From that directory, run the "sdgen_sdqbox.exe" utility

(code generator executable). It globs all "*.sdgen_sdqbox"

files and generates (C++)

source code to that directory for

each "boxed-class".

C:\MyWorkspace\MyPlugin1> sdgen_sdqbox.exe ...generating...

C:\MyWorkspace\MyPlugin1> dir /b

Detector.sdgen_sdqbox

SpectraFilter.sdgen_sdqbox

Wavelength.sdgen_sdqbox

SdqModelListWavelength.cpp <==(generated!)

SdqModelListWavelength.hpp <==(generated!)

SdqWavelengthBox.cpp <==(generated!)

SdqWavelengthBox.hpp <==(generated!)

SdqWavelengthBoxBack.cpp <==(generated!)

SdqWavelengthBoxBack.hpp <==(generated!)

SdqWavelengthBoxSet.cpp <==(generated!)

SdqWavelengthBoxSet.hpp <==(generated!)

...+generated for "SpectraFilter"...

...+generated for "Detector"...

C:\MyWorkspace\MyPlugin1>

Page 22: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Case Study (continued):

Build & Link Declarative Box-Types

6. Build/Link all the files in that

directory as a "module" (e.g., "shared-

library").

1. A build setup that "globs" all "*.hpp/*.cpp" files in that directory

makes this simple

2. Best Practice:

1. Only "generated" files are found in this

directory (no "hand-maintained" files)

2. Both "*.sdgen_sdqbox" and generated

"*.hpp/*.cpp" files are checked into the

Version Control System

./MyWorkspace/.

MyBin/.

CytoPlugin1.dll

CytoPlugin1.lib

C:\MyWorkspace\MyPlugin1> dir /b

Detector.sdgen_sdqbox

SpectraFilter.sdgen_sdqbox

Wavelength.sdgen_sdqbox

SdqModelListDetector.cpp <==(generated!)

SdqModelListDetector.hpp <==(generated!)

SdqDetectorBox.cpp <==(generated!)

SdqDetectorBox.hpp <==(generated!)

SdqDetectorBoxBack.cpp <==(generated!)

SdqDetectorBoxBack.hpp <==(generated!)

SdqDetectorBoxSet.cpp <==(generated!)

SdqDetectorBoxSet.hpp <==(generated!)

SdqModelListSpectraFilter.cpp <==(generated!)

SdqModelListSpectraFilter.hpp <==(generated!)

SdqSpectraFilterBox.cpp <==(generated!)

SdqSpectraFilterBox.hpp <==(generated!)

SdqSpectraFilterBoxBack.cpp <==(generated!)

SdqSpectraFilterBoxBack.hpp <==(generated!)

SdqSpectraFilterBoxSet.cpp <==(generated!)

SdqSpectraFilterBoxSet.hpp <==(generated!)

SdqModelListWavelength.cpp <==(generated!)

SdqModelListWavelength.hpp <==(generated!)

SdqWavelengthBox.cpp <==(generated!)

SdqWavelengthBox.hpp <==(generated!)

SdqWavelengthBoxBack.cpp <==(generated!)

SdqWavelengthBoxBack.hpp <==(generated!)

SdqWavelengthBoxSet.cpp <==(generated!)

SdqWavelengthBoxSet.hpp <==(generated!)

C:\MyWorkspace\MyPlugin1>

Page 23: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Case Study (continued):

What Did The Generator Do?

What was generated?

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").

SdqWavelengthBox

n

SdqWavelengthBoxBack 1

1 SdqWavelengthBoxSet

1 1

"Handle" to "BoxBack" (does NOT have ANY

instance-state)

Collection of "handles"

QObject Wavelength

1 1

smart_ptr<>

Single "interface" to back-end

QAbstractListModel

1 SdqModelListWavelength

Collection of "handles" (with

update-notification)

Q_PROPERTY(text)

Q_PROPERTY(qreal)

Q_PROPERTY(color)

1 1

SdqBoxBackManager<Wavelength>

1 n

Page 24: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Case Study (continued):

Memory Management: Who Owns What?

Object Ownership

Object Instantiation

● Creating "Box" triggers creation of

"BoxBack" which triggers creation of

"Wavelength"

● All "BoxBack" instances are "owned"

by the "Manager<Wavelength>"

SdqWavelengthBox

myWave1

SdqWavelengthBoxBack

Wavelength

SdqBoxBackManager<Wavelength>

// FILE: HelloWavelength.qml

import com.bec.cyto 1.0

Item {

Wavelength {

id: myWave1

wavelengthNm: 488

}

Wavelength {

id: myWave2

wavelengthNm: 532

} }

SdqWavelengthBox

myWave2

SdqWavelengthBoxBack

Wavelength

Page 25: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Back-End

System

Case Study (continued):

Updates From QML: How Does It Work?

Referencing Instances in "back-end"

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"

SdqWavelengthBoxBack

Wavelength

SdqWavelengthBox

myWave1

SdqWavelengthBox

myWave2

Page 26: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Back-End

System

Case Study (continued):

Updates From Back-End: How Does It Work?

Referencing Instances in "back-end"

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"

SdqWavelengthBoxBack

Wavelength

SdqWavelengthBox

myWave1

SdqWavelengthBox

myWave2 SdqLaserDeviceBox

myLaser LaserDevice

Page 27: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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…

Page 28: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Side Note #2:

Organize Your Projects! (Or you will Die Die Die!!!)

(Side Note #2) Project Organization: Do whatever

you want, but this is a convention that works.

./MyWorkspace/.

Cyto/.

Wavelength.hpp

Wavelength.cpp

CytoBoxed/.

SdqModelListWavelength.cpp

SdqModelListWavelength.hpp

SdqWavelengthBox.cpp

SdqWavelengthBox.hpp

SdqWavelengthBoxBack.cpp

SdqWavelengthBoxBack.hpp

SdqWavelengthBoxSet.cpp

SdqWavelengthBoxSet.hpp

CytoPlugin/.

CytoCommonPlugin.cpp

CytoCommonPlugin.pro

CytoQml/.

qmldir

MyWavelengthControl.qml

MyOtherControl.qml

CytoCommonQml.pro

CytoTestExe1/.

main.cpp

CytoCommonQml.pro

CytoTestExe2/.

main.cpp

CytoCommonQml.pro

CytoTestQml1/.

main.cpp

CytoCommonQml.pro

Peers within your workspace (does not matter if you

maintain "one" or "more-than-one" cooperating "workspaces"):

1. C++ "Reference/Legacy/Hand-maintained" library/module

2. Generated-C++ Declarative-Interface code

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

developed/tested in (4))

4. QML resource files (will be deployed with your plugin shared-lib, commonly used

across your test-QML applications and test-compiled-applications, are referenced

directly by the test applications in the workspace, and (3) for deployment)

5. TestApplication code (for each compiled "*.exe" binary)

6. Test QML Application Code (for each QML application)

NOTE for (3) and (4), that even NESTED plugins are "peers" at the

workspace-level

NOTE that each project "builds-and-deploys" to the "install-location" for

the machine when developing across modules/plugins

Page 29: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Case Study (continued):

Deployment (What Do You Need?)

Cyto.dll

CytoBoxed/.

SdqModelListWavelength.hpp

SdqWavelengthBox.hpp

SdqWavelengthBoxBack.hpp

SdqWavelengthBoxSet.hpp

CytoPlugin.dll

qmldir

MyControl.qml

When Deploying...

Cyto.lib

CytoBoxed.lib

CytoBoxed.dll

● Executable Binary – (e.g., "MyProgram.exe"),

Example: Users will launch your executable, your

system is "locked-down"

● Shared Library – (e.g., "CytoBoxed.dll"),

Example: Other developers will link your DLL

into their executable

Cyto.lib

Cyto.dll

Build Products

● Business-logic shared

library

● Boxed-shared-library

● Boxed-headers

● Plugin-Package

CytoBoxed.lib

CytoBoxed.dll

CytoBoxed/.

SdqModelListWavelength.hpp

SdqWavelengthBox.hpp

SdqWavelengthBoxBack.hpp

SdqWavelengthBoxSet.hpp

Cyto.lib

Cyto.dll

CytoBoxed.lib

CytoBoxed.dll

● QML Plugin – (e.g., "CytoPlugin.dll"),

Example: Other developers will load your plugin;

Users will run your plugin

CytoPlugin.dll

qmldir

MyControl.qml

MyApp.exe Cyto.dll CytoBoxed.dll

No Headers are needed to deploy plugins!

Proprietary Headers

Are Never Deployed!

Page 30: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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")

● May not be able to ship due to distribution restrictions (3rd Party licensing,

Corporate IP / Legal / Regulatory standards)

Page 31: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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!)

Page 32: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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?

Page 33: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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

Page 34: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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

Page 35: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

"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)

Page 36: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

"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

Page 37: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

"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!)

Page 38: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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)

Page 39: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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

Page 40: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

About: The Case-Study Generator

● Is implemented in C++ (would not be difficult to write it in

Python / Perl / 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<class ITEM_TYPE_TO_BOX>

struct SdqObjectBoxTraits

{

typedef ITEM_TYPE_TO_BOX TypeItemToBox;

typedef SdqObjectBoxBack TypeSdqObjectBoxBack;

typedef SdqObjectBox TypeSdqObjectBox;

};

Page 41: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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)

Page 42: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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!

Page 43: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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.

Page 44: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

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.

Page 45: Wrapping C++ Objects For Property Exposure In QML · Wrapping C++ Objects For Property Exposure In QML Charley Bay Beckman Coulter, Inc.

Thank You!

Special Thanks:

David Van Maren

Beckman Coulter, Inc.

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

Alan Alpert


Recommended