Unit Schemes -- Slot
Class Architecture
Phil Weinstein, CADSWES -- see also source code
samples
Document History / Status:
This document describes the enhancements to the Slot class hierarchy for support of Unit Schemes.
With the introduction of Unit Schemes in RiverWare 6.1, a slot's numeric values can be presented using one of several distinct "schemes" of units and other display attributes defined by the user. A Value Display Attribute Scheme provides the following attributes for numeric slots -- and for slot columns in multiple-column numeric slots:
In the RiverWare session, one particular value display attribute scheme is active at any one time. This will be one of the following:
Value Display Attributes will be encapsulated in a new ValueDisplayAttributeGroup class.
class NumDisplayAttribs { private: ScaledUnitPtr _scaledUnit; // scale (double) and Unit int _dispPrecision; // number of fractional decimal digits char _dispFormatChar; // 'f'-float, 'e'-scientific notation bool _isDefined; // 'false' indicates required referal to a different // source of display attributes. |
This class is immutable. (It doesn't define field-setter methods). A default constructor is required for use in collection classes (e.g. QList) and for the "not defined" state which indicates that the display attributes need to be provided from a lower-priority source. The default constructed value has a _scaledUnit of (1.0 NOUNITS), a precision of 2, the "float" format, and an "isDefined" value of 'false'.
The legacy "display format" attribute is a QString -- by convention, a single character of the values 'f' (float), 'e' (scientific notation) or 'i' (integer). The NumDisplayAttribs will instead represent the "display format" attribute as a char. The 'i' (integer) format will show up only in AttribsList coming from the legacy "local" scheme. The new "mapped" schemes will not generate integer format configurations. Note that configuring a slot or slot column as "integer" does not enforce the assignment of only integer values to the slot. It only causes fractional digits to be hidden (with rounding) in the display of the value -- and that is equivalent to using the "float" format with a precision of zero.
The AttribGroup data fields are ordered to optimize "packing" of that data within memory. (It would otherwise have been logical to place the '_dispFormatChar' field before the '_dispPrecision' field, and to put the '_isDefined' field first).
While some virtual methods to support multiple-column slots have been introduced in the past (e.g. for Table Slot repeatable "column block" support), this enhancement will formalize the concept of any slot potentially having multiple columns. The mechanism being proposed in this document will make use of these new column-related virtual functions:
class Slot : public Root { ... ... ... virtual int colCount() const { return (1); } |
The 'colCount()' method needs to be overridden in only slot subclasses supporting multiple columns, i.e. AggSeriesSlot, MultiSlot and TableSlot.
The 'primUnitType()' method needs to be overridden in all numeric Slot classes, i.e. all but ListSlot, excluding slot subclasses further derived from those sufficiently defining this method.
The 'altUnitType()' method needs to be overridden only in the SeriesSlot and AggSeriesSlot classes. It should return NOUNITS for slot instances not supporting "Alt" units. (In the case of alternate units, NOUNITS means that the slot column doesn't support 'alt' units rather than supporting unitless values).
The AttribsList for a slot's columns which are computed for the current (or recently current) "mapped" scheme will be cached within this new internal private "AttribCache" class, for which there will be one private instance:
private: class Slot::MappedValDisplayAttribCache { private: const Slot* const _slot; // owner slot int _mappedSchemeSerialNum; QList<NumDisplayAttribs> _colAttribsList_prim; // primary QList<NumDisplayAttribs> _colAttribsList_alt; // alt units public: MappedValDisplayAttribCache (Slot* s); const NumDisplayAttribs& colGroupRef_prim (int col); // primary const NumDisplayAttribs& colGroupRef_alt (int col); // alt units private: void checkCurrent(); }; private: mutable MappedValDisplayAttribCache _mappedValDisplayAttribCache; |
The '_slot' field is required by the AttribCache's internal mechanism to compute its column AttribGroup lists. It is provided via the constructor when the slot's AttribCache instance is created (in the slot constructor's AttribCache initializer, using the slot's "this" pointer).
The cached lists of AttribsList -- one for primary units on each of the slot's columns, and one for alt units on each of the slot's columns -- are known to be current by comparing the saved "Value Display Scheme Serial Number" with the global version of that number stored on the SchemeManager (see below) -- and also insuring that the number of columns in the cache matches the number of columns in the slot. This is assessed by the "checkCurrent()" method (see below) called at the beginning of the AttribGroup "getter" methods.
The AttribCache class doesn't provide any "setter" methods. Its AttribGroup list values can only be computed internally.
// private void Slot::MappedValDisplayAttribCache::checkCurrent() { // IF the locally saved scheme serial number doesn't match the // manager's serial number OR IF the number of columns in the slot // has changed, then recompute the column attribute groups for the // slot (using the Value Display Attribute Scheme Manager). ValDisplayAttribSchemeMgr* mgr = ValDisplayAttribSchemeMgr::inst(); if ( (_mappedSchemeSerialNum != mgr->mappedSchemeSerialNum()) || (_colAttribsList_prim.size() != _slot->colCount()) ) { _colAttribsList_prim = mgr->computeColAttribsList (_slot, false); // not alt units if (_slot->supportsAltUnits()) { _colAttribsList_alt = mgr->computeColAttribsList (_slot, true); // alt units } _mappedSchemeSerialNum = mgr->mappedSchemeSerialNum(); } } |
The implementation the "checkCurrent()" method (above) makes use of another new virtual Slot class method:
virtual bool supportsAltUnits() const { return false; }
It is assumed that whether or not a given slot supports "Alt" units is a constant property. The default implementation of this method (which returns 'false') needs to be overridden only in the SeriesSlot method, which will effectively return 'true' for only certain accounting system SeriesSlots.
The only access to the AttribCache's AttribsList will be through these two methods which first call the "checkCurrent()" method:
// public const NumDisplayAttribs& Slot::MappedValDisplayAttribCache::colGroupRef_prim (int col) { checkCurrent(); if (rwAssert (col >= 0) && rwAssert (col < _colAttribsList_prim.size())) { return _colAttribsList_prim [col]; } static const NumDisplayAttribs NullValDispAttribGroup; return (NullValDispAttribGroup); } // public const NumDisplayAttribs& Slot::MappedValDisplayAttribCache::colGroupRef_alt (int col) { checkCurrent(); // Note: Don't fail an assertion for the column index being beyond // the size of the _colAttribsList_alt list. That list will be // empty if Alt units are not supported on a slot. Attempting to // access Alt units at this level when they don't exist is allowed. if (rwAssert (col >= 0) && (col < _colAttribsList_alt.size())) { return _colAttribsList_alt [col]; } static const NumDisplayAttribs NullValDispAttribGroup; return (NullValDispAttribGroup); } |
These public Slot methods allow read-only access to AttribsList for the slot and the slot's columns:
|
|
|
|
|
As demonstrated in the implementation of one of the "mapped" AttribGroup methods (first group above), if the current Mapped Scheme doesn't provide an AttribGroup for the particular slot column, the "local" AttribGroup is returned instead (i.e. the display attributes configured on the slot):
// public const NumDisplayAttribs& Slot::mappedNumDisplayAttribs_prim (int col /*=0*/) const { const NumDisplayAttribs& mappedAttribGroup = _mappedValDisplayAttribCache.colGroupRef_prim (col); if (mappedAttribGroup.isDefined()) { return mappedAttribGroup; } // If the current Mapped Scheme doesn't provide display attributes // for the requested slot column, then defer to the Local Scheme. return (localNumDisplayAttribs_prim (col)); } |
The "local" AttribGroup methods must be implemented in the slot subclasses. It is recommended, for run-time efficiency and consistency, that the individual unit and display attribute fields (including lists of fields, for multiple-column slots) be "repackaged" within AttribGroup or AttribGroup list fields. If that isn't done, then it might not make sense for these methods to return a reference to (or pointer to) an AttribGroup, as an actual AttribGroup instance would have to "live" somewhere. Also, we should take this opportunity to change the "format" fields from QStrings to a single-byte 'char'.
For reference, here are the current display attribute fields on the SeriesSlot and the TableSlot:
|
|
|
The "active" AttribGroup methods switch between the "mapped" and "local" schemes, depending on state information provided by the Scheme Manager, for example:
// public const NumDisplayAttribs& Slot::activeNumDisplayAttribs_prim (int col /*=0*/) const { if (ValDisplayAttribSchemeMgr::inst()->localSchemeActive()) return localNumDisplayAttribs_prim (col); // virtual call return mappedNumDisplayAttribs_prim (col); } |
The Value Display Attribute Scheme Manager ("SchemeManager") is a singleton which manages the set of named "mapped" Unit Schemes. It also supports the identification of the current "active" scheme (which could also be the "local" scheme), and supports the maintenance of the AttribCaches on all of the slot instances in the model. Only this latter responsibility is relevant for this section. Relevant members are defined here in this "stub" implementation:
class ValDisplayAttribSchemeMgr { private: static ValDisplayAttribSchemeMgr* _instance; int _mappedSchemeSerialNum; private: ValDisplayAttribSchemeMgr() : _mappedSchemeSerialNum (0) {} ~ValDisplayAttribSchemeMgr() { _instance = NULL; } public: static ValDisplayAttribSchemeMgr* inst(); int mappedSchemeSerialNum() const { return _mappedSchemeSerialNum; } QList<NumDisplayAttribs> computeColAttribsList (const Slot*, bool alt) const; bool localSchemeActive() const; }; |
The '_mappedSchemeSerialNum' field is incremented each time the user selects a different "mapped" scheme to be the active scheme -- and each time the active "mapped" scheme is edited by the user. The serial number doesn't need to be incremented when changing to the "local" scheme, or changing back to the same "mapped" scheme, unless that mapped scheme has since been edited. By the way, there is no problem with the serial number "wrapping" (overflowing) to huge negative numbers after 2 billion plus changes. That won't happen anyway.
The 'localSchemeActive()' value is just one part of the "active" scheme selection data maintained by the SchemeManager. If some "mapped" scheme is active, the "local" scheme will not be active. As currently designed, slots' AttribCaches don't need to know which "mapped" scheme is active, though we could consider recording that information in the slots' AttribCaches -- perhaps just the mapped scheme's name for diagnostic purposes. (However, we wouldn't want to implement an unnecessary management mechanism with slots, to track that mapped scheme's life-cycle or name changes).
The maintenance of the set of named "mapped" schemes and support for the selection of the "active" scheme are not discussed here, but those will also be supported by the SchemeManager.
--- (end) ---