Properties framework для Qt

  
 
 
Проблема
У Qt існує чудова річ — Q_PROPERTY, яка дозволяє додати необхідну властивість до будь-якого QObject класу. Але в деяких випадках користуватися ними незручно.
 
Наприклад, у вас є додаток, в якому існують (або можна створювати) багато різних об'єктів і, які необхідно налаштовувати (видавати діалог з властивостями обраного об'єкта). Прикладом таких програм може бути майже будь-яка інженерна програма, редактор векторної графіки, Visual Studio зрештою.
 
 
Отже, існує безліч об'єктів, які користувач може «настроювати». Логічно мати для цього якийсь універсальний механізм, адже інакше доведеться для кожного об'єкта реалізовувати свій власний діалог настройки. Q_PROPERTY для цього не зовсім підходить. По-перше, немає стандартного віджету для редагування Q_PROPERTY. По-друге, навіть якщо ви скористаєтеся стороннім віджетом для цих цілей, то поряд з вашими властивостями, будуть показуватися стандартні (як мінімум objectName). Далі, Q_PROPERTY не підтримує ієрархії (іноді корисно згрупувати пов'язані властивості); немає description (текстова рядок, що пояснює дане властивість); немає ніяких засобів для налаштування зовнішнього вигляду Q_PROPERTY в GUI (для поліпшення usability).
 
Для програміста іноді зручно працювати з властивостями, як з окремим об'єктом, незалежними від об'єкта-власника. Їх тоді можна передавати кудись, не передаючи весь об'єкт-власник цілком, або навіть передавати окрему підгрупу властивостей.
 
 
Рішення
Намагаючись зробити зручні властивості, я створив проект QtnProperty . Далі я розповім чим гарний цей проект і як цією бібліотекою користуватися.
Насамперед бібліотека складається з декількох частин:
 
     
  • QtnPropertyCore — бібліотека з класами, не пов'язаними з GUI
  •  
  • QtnPropertyWidget — віджети для редагування властивостей, плюс делегати для кастомізації зовнішнього вигляду і редагування різних властивостей
  •  
  • QtnPEG — додаток (подібне moc генератору), яке створює С + + клас набору властивостей PropertySet з *. pef файлу (це більш простий спосіб опису властивостей, по синтаксису подібний QML)
  •  
  • QtnPropertyTests — тести для бібліотеки QtnPropertyCore
  •  
  • QtnPropertyDemo — демо додаток, де можна редагувати властивості за допомогою QtnPropertyWidget , а також з скрипта і задаючи текст у вигляді рядків «SuperProperty.SubProperty = value»
  •  
 
QtnPropertyCore
Насамперед, бібліотека визначає два основні класи QtnProperty і QtnPropertySet . Перший є базовим для всіх типів властивостей (наприклад QtnPropertyBool або QtnPropertyFloat ), а другий представляє групу властивостей. І QtnProperty і QtnPropertySet мають загального батька QtnPropertyBase , який, у свою чергу, є нащадком QObject. У QtnPropertyBase класі визначено все, що є спільного між QtnProperty і QtnPropertySet :
 
     
  1. QString name — ім'я property або propertyset (збігається з objectName)
  2.  
  3. QString description — текстовий опис, яке може відображатимуться в нижній панелі QtnPropertyWidget
  4.  
  5. qint32 id — унікальний ID властивості серед інших в групі (використовується при збереженні в бінарному вигляді)
  6.  
  7. QtnPropertyState state — стан властивості (наприклад QtnPropertyStateImmutable — Нередагована властивість чи QtnPropertyStateInvisible — невидиме)
  8.  
  9. load / save — збереження / завантаження через QDataStream
  10.  
  11. fromStr / toStr — конвертація значення властивості з / в QString
  12.  
  13. fromVariant / toVariant — конвертація значення властивості з / в QVariant
  14.  
  15. propertyWillChange — сигнал, який спрацьовує перед зміною властивості (при зміні не тільки значення а й інших аттрибутов — name, description, id, state тощо)
  16.  
  17. propertyDidChange — сигнал, який спрацьовує після зміною властивості (симетричний propertyWillChange)
  18.  
 QtnPropertySet клас містить ще список дочірніх властивостей і методи роботи з ними. Для кожного типу властивості (наприклад целочисленного) існує два класи (QtnPropertyInt і QtnPropertyIntCallback ). Перший зберігає значення в класі, а другий викликає деяку функцію для одержання або збереження значення.
Ось список властивостей, який реалізований безпосередньо в модулі QtnPropertyCore :
 
     
  1. QtnPropertyBool — представляє булево значення.
  2.  
  3. QtnPropertyInt — представляє знакова цілочисельне значення.
  4.  
  5. QtnPropertyUInt — представляє беззнакове цілочисельне значення.
  6.  
  7. QtnPropertyFloat — представляє дійсне значення з плаваючою точкою.
  8.  
  9. QtnPropertyDouble — представляє дійсне значення подвійної точності.
  10.  
  11. QtnPropertyEnum — представляє значення з перерахування.
  12.  
  13. QtnPropertyEnumFlags — представляє комбінацію значень з перерахування.
  14.  
  15. QtnPropertyQString — представляє строкове значення.
  16.  
  17. QtnPropertyQPoint — представляє значення QPoint.
  18.  
  19. QtnPropertyQSize — представляє значення QSize.
  20.  
  21. QtnPropertyQRect — представляє значення QRect.
  22.  
  23. QtnPropertyQFont — представляє значення QFont.
  24.  
  25. QtnPropertyQColor — представляє значення QColor.
  26.  
Всі чисельні властивості (QtnPropertyInt , QtnPropertyUInt , QtnPropertyFloat , QtnPropertyDouble ) мають три додаткових методу:
 
     
  1. minValue — мінімально допустиме значення.
  2.  
  3. maxValue — максимально допустиме значення.
  4.  
  5. stepValue — крок зміни значення.
  6.  
 
QtnPropertyWidget
У цьому модулі містяться два віджета і делегати властивостей.
Клас QtnPropertyView реалізує деревовидний віджет для набору властивостей.
Клас QtnPropertyWidget є складовим і містить QtnPropertyView і QLabel в якості нижньої панелі.
 
Що б поміняти зовнішній вигляд або поведінку деякого окремого властивості, були введені делегати. Наприклад, для QtnPropertyBool існують два делегати: QtnPropertyDelegateBoolCheck і QtnPropertyDelegateBoolCombobox . Перший відображає checkbox, а другий — combobox з двома налаштованим значеннями (для значень true і false). Можна створювати свої делегати і виставляти їх як дефолтних, тоді всі властивості даного типу будуть відображатися за допомогою нового делегата. Іноді легше і зручніше створювати новий тип делегата, ніж новий тип властивості. Наприклад, властивість для зберігання шляху до файлу реалізовано через спеціальний делегат QtnPropertyDelegateQStringFile для QtnPropertyQString .
 
 
Як користуватися
Для прикладу розглянемо невеликий набір властивостей для текстового редактора.
Ось що ми хочемо показати користувачеві.
 
А ось що потрібно зробити програмісту, що б отримати такий property set:
 
QtnPropertySet* textProperties = new QtnPropertySet(owner);

    QtnPropertyBool* enableWrapping = new enableWrapping(textProperties);
    enableWrapping->setName(tr("enableWrapping"));
    enableWrapping->setDescription(tr("Enable/disable text wrapping"));
    enableWrapping->setValue(true);

    QtnPropertyQColor* textColor = new QtnPropertyQColor(textProperties);
    textColor->setName(tr("textColor"));
    textColor->setDescription(tr("Foreground text color"));
    textColor->setValue(QColor(0, 0, 0));

    QtnPropertySet* Tabulation = new QtnPropertySet(textProperties)
    Tabulation->setName(tr("Tabulation"));
    Tabulation->setDescription(tr("Tabulation settings"));

    QtnPropertyBool* replaceWithSpaces = new QtnPropertyBool(Tabulation);
    replaceWithSpaces->setName(tr("replaceWithSpaces");
    replaceWithSpaces->setDescription(tr("Automatically replace tabs with spaces"));
    replaceWithSpaces->setValue(false);

    QtnPropertyUInt* tabSize = new QtnPropertyUInt(Tabulation);
    tabSize->setName(tr("tabSize"));
    tabSize->setDescription(tr("Number of spaces to be placed."));
    tabSize->setState(QtnPropertyStateImmutable);
    tabSize->setValue(4);
    tabSize->setMinValue(1);
    tabSize->setMaxValue(10);

    // создаём обработчик сигнала propertyDidChange
    auto lambda =  [tabSize, replaceWithSpaces](const QtnPropertyBase* changedProperty,
                             const QtnPropertyBase* firedProperty,
                             QtnPropertyChangeReason reason) {
        // если изменилось значение
        if (reason & QtnPropertyChangeReasonValue)
        {
            if (*replaceWithSpaces)
                // делаем tabSize изменяемым
                tabSize->removeState(QtnPropertyStateImmutable);
            else
                // делаем tabSize не изменяемым
                tabSize->addState(QtnPropertyStateImmutable);
    };
    QObject::connect(replaceWithSpaces, &QtnProperty::propertyDidChange, lambda);

    // и еще код для настройки делегата replaceWithSpaces
    ...

Щоб полегшити життя програмістам (або навіть дати можливість описувати властивості не-програмістам) був розроблений невеликий кодо-генератор QtnPEG .
Ось так виглядає опис нашого прикладу в Text.pef файл:
 
property_set Text
{
    Bool enableWrapping
    {
        description = "Enable/disable text wrapping";
        value = true;
    }

    QColor textColor
    {
        description = "Foreground text color";
        value = QColor(0, 0, 0);
    }

    property_set Tabs Tabulation
    {
        description = "Tabulation settings";
        Bool replaceWithSpaces
        {
            description = "Automatically replace tabs with spaces";
            value = false;

            // устанавливаем специальный делегат
            delegate ComboBox
            {
                //  назначаем текст для значения true
                labelTrue = "On";
                //  назначаем текст для значения false
                labelFalse = "Off";
            }

            // определяем код слота для сигнала replaceWithSpaces.propertyDidChange
            slot propertyDidChange
            {
                tabSize.switchState(QtnPropertyStateImmutable, !replaceWithSpaces);
            }
        }

        UInt tabSize
        {
            description = "Number of spaces to be placed.";
            state = QtnPropertyStateImmutable;
            value = 4;
        }
    }
}

Досить просто і лаконічно. QtnPEG створює два файли: Text.peg.h і Text.peg.cpp, де знаходяться два класи. QtnPropertySetText і QtnPropertySetTabs — класи спадкоємці від QtnPropertySet .
У згенерованих класах можна побачити наступну декларацію подсвойств, наприклад для QtnPropertySetText :
 
// start children declarations
    QtnPropertyBool& enableWrapping;
    QtnPropertyQColor& textColor;
    QtnPropertySetTabs& Tabulation;
    // end children declarations

Таким чином програміст в C + + коді може працювати з цим класом, як зі структурою з полями. Наприклад:
 
void doSomething(const QtnPropertySetText& textParams, QString& text)
{
    if (textParams.Tabulation.replaceWithSpaces)
    {
        QString spaces(QChar::Space, textParams.Tabulation.tabSize);
        text.replace(QChar::Tabulation, spaces);
    } 
}

 
Підсумок
Сподіваюся я зміг дати деяке уявлення про мій проект.
Буду радий, якщо комусь він стане в нагоді, особливо там, де є багато налаштувань і розробляти GUI для всього і вся накладно. Якщо, на вашу думку, для повноцінного використання QtnProperty не вистачає якоїсь функціональності, прошу коментувати.
  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.