Proposed Changes to Categories

Current Design

Attribute Category Information is currently stored as a set of string (representing the names of the categories) and a combination mode. When inheriting the information (currently only the set of strings are passed to parent items, attributes and possibly children items (based on their inherit category mode).

The Problem

Without the combination mode, the inherited categories allow attributes and items to be displayed with no internal children. Consider the following example:

  • Attribute Definition A has no local categories but contains 2 Items
  • Item i1 has local categories (c1, c2) with combination mode ALL
  • Item i2 has local categories (c3, c4) with combination mode ALL

In the current system A inherits categories (c1, c2, c3, c4) with mode ANY. If the GUI is displaying information associated with c1, any attributes of type A will be displayed but will have no content.

Proposed Solution

Replace the std::set<std:string> , mode representation of category information with a class called smtk::attribute::Categories that provides the following functionality:

  • Represents sets of categories along with the combination mode
  • Provides a mechanism of combining 2 Categories instances
  • Provides a passes method that takes in a set of category strings and returns true if the Categories instance would be “include” in that set.
namespace smtk {
namespace attribute {
class Categories 
{
   class Set
   {
      public:
         enum class CombinationMode
            { Any, All };
         CombinationMode mode() const { return m_mode;}
         void setMode(const Set::CombinationMode& newMode);
         const std::set<std::string>& values { return m_set;}
         void set(const std::set<std::string>& values, CombinationMode mode);
         bool empty() const { return m_categoryNames.empty(); }
         std::size_t size() const { return m_categoryNames.size(); }
         bool operator<(const Set& rhs) const;
      private:
         Combination m_mode;
         std::set<std::string> m_set;
   };
public: 
   bool passes(const std::set<std::string>& cats) const;
   void append(const Set& set) { m_sets.insert(set); }
   void reset() { m_sets.clear(); }
   const std::set<Set>& sets() const { return m_sets;}
   std::size_t size() const { return m_sets.size(); }
private:
   std::set<Set> m_sets;
};
};
};

API Changes (Updated 1/4/2020)

  • Attribute
    • New Methods
      • categories() - returns a reference to the Categories object associated with the Attribute
    • Removed Methods
      • isMemberOf(…) → replace with categories().passes(…)
  • Definition
    • Changed Methods
      • categories() - now returns a reference to the Categories object associated with the Definition
      • localCategories() - now returns a reference to the Categories::Set object associated with the Definition’s local categories
      • applyCategories - method now takes in Categories and a set of strings
    • Removed Methods
      • isMemberOf(…) → replace with categories().passes(…)
      • numberOfCategories() - replaced with categories().size()
      • addLocalCategories(…) - replaced with localCategories().insert(…)
      • removeLocalCategories(…) - replaced with localCategories().erase(…)
  • ItemDefinition
    • Changed Methods
      • categories() - now returns a reference to the Categories object associated with the ItemDefinition
      • localCategories() - now returns a reference to the Categories::Set object associated with the ItemDefinition’s local categories
      • applyCategories - method now takes in Categories and a set of strings
    • CategoryCheckMode Enums removed - replaced by Categories::Set::CombinationMode
    • Removed Methods
      • isMemberOf(…) → replace with categories().passes(…)
      • numberOfCategories() - replaced with categories().size()
      • addLocalCategories(…) - replaced with localCategories().insert(…)
      • removeLocalCategories(…) - replaced with localCategories().erase(…)
      • setCategoryCheckMode(…) - replaced with localCategories().setMode(…)
      • categoryCheckMode() - replaced with localCategories().mode()
  • Item
    • New Methods
      • categories() - returns a reference to the Categories object associated with the Item
    • Removed Methods
      • isMemberOf(…) → replace with categories().passes(…)
      • passCategoryCheck(…) - replaced with categories().passes(…)
  • ValueItemDefinition
    • Changed Methods
      • setEnumCategories - now takes in Categories::Set instead of a set of strings
      • enumCategories - now returns a const reference to a Categories::Set instead of a set of strings

This looks great. The API changes are not drastic. Thanks.

This change might allow us to make some other enhancements for categories as well. They’re not important now though. I’ll share what I have in mind in a separate reply once I get some time to type it up.

Thank you for your patience. Got tied up in my cross-country move before and during the break. Finally got some time to think about this subject more. As I did, I realized I needed some clarification on a couple of things:

  1. How will the inheritance take place in the proposed solution?

From what I currently understand Sets are inherited as they are from child items. For example, assuming the proposed solution and the use-case below:

  • item i1’s Categories instance will contain one Set with combination mode ALL and categories (c1, c2).
  • Similarly, item i2’s Categories instance will contain one Set with combination mode ALL and categories (c3, c4).
  • therefore, Att Def A’s Categories instance will contain 2 sets, one from i1 and one from i2.
  1. How will the passes method work?

In general does this check each category string in cats against each Set and returns true as soon as at least one category meets at least one Set’s requirements?

I agree. I think the new API will provide greater flexibility and uniformity. For example all local categories (including Definitions and Enums) can now have the All option.

I did example the list of changes but in many ways it cleans up the existing API.

In terns of inheritance your interpretation is on the nose. Attribute A will now have 2 sets {c1 && c2} || {c3 && c4} (assuming && means All and || means or) . Let’s make the example a little more complicated by assigning A a local category {c5 && c6} . the following would be the result of calling categories() on A, i1, and i2:

object categories()
A {c1&&c2} || {c3&&c4} || {c5&&c6}
i1 {c1&&c2} || {c5&&c6}
i2 {c3&&c4} || {c5&&c6}

You are 50% :slight_smile: correct in your interpretation of how passes work. In the above example if the input categories are {c2, c3, c4}, A and i2 will pass but i1 would fail.

If the combination mode had been Any, you would be right - as soon as a category in the input set matched the Set’s requirements the method would return true. In both yours and mine examples the mode is All which is treated differently.

In this case an “All” Set’s categories are checked agains the input set and if we encounter a category not present that set does not pass and we move on to the next set if one exists or we return false, else if we have found all of the set’s categories in the input set we return true.

Now that parent ItemDefinitions and Definitions inherit the exact category constraints of their children (instead of assuming an Any condition) I think we can also simplify the GUI logic when determining if an Attribute or Item is “empty”. Assuming the designer’s category assignment is consistent then an empty Attribute should correspond to the case where the Attribute would not pass its category check.

Does that clear things up?