как работает css в Qt

Начинаем разбираться как управлять стилями css в Qt.

Для начала надо хотя бы понять где примерно искать начало проблемы, а точнее где обрабатываются css инструкции (текстовые). Берез и ищем в исходниках Qt например слово "checked" (именно с кавычками), это часто встречающийся параметр в css.

Находим файл qcssparser.cpp , где есть массив QCssKnownValue , где опубликованы все имена.   Именам соответствют просто некие зарезервированные номера, например у "checked" номер PseudoClass_Checked.  Кстати номера битово поделены. 

Далее находим  что PseudoClass_Checked  используется в файле qstylesheetstyle.cpp  в методе renderRule(...).

Отладчиком сразу видно, что renderRule вызывается многочисленное количество раз при открытии окна программы, то есть предположительно отрисовка проходит через этот метод. Уже хорошо.

QRenderRule QStyleSheetStyle::renderRule(const QWidget *w, const QStyleOption *opt, int pseudoElement) const

Методу renderRule передается указатель на Виджет, понятно с целью отрисовать данный виджет. И передается в QStyleOption, чтобы указать как отрисовать его (в каком прямоугольнике (QRect) и т.д.). И последний параметр это из перечисления PseudoElement номер псевдо элемента из файла qstylesheetstyle.cpp .

 PseudoElement
enum PseudoElement {
    PseudoElement_None,
    PseudoElement_DownArrow,
    PseudoElement_UpArrow,
    PseudoElement_LeftArrow,
    PseudoElement_RightArrow,
    PseudoElement_Indicator,
    PseudoElement_ExclusiveIndicator,
    PseudoElement_PushButtonMenuIndicator,
    PseudoElement_ComboBoxDropDown,
    PseudoElement_ComboBoxArrow,
    PseudoElement_Item,
    PseudoElement_SpinBoxUpButton,
    PseudoElement_SpinBoxUpArrow,
    PseudoElement_SpinBoxDownButton,
    PseudoElement_SpinBoxDownArrow,
    PseudoElement_GroupBoxTitle,
    PseudoElement_GroupBoxIndicator,
    PseudoElement_ToolButtonMenu,
    PseudoElement_ToolButtonMenuArrow,
    PseudoElement_ToolButtonDownArrow,
    PseudoElement_ToolBoxTab,
    PseudoElement_ScrollBarSlider,
    PseudoElement_ScrollBarAddPage,
    PseudoElement_ScrollBarSubPage,
    PseudoElement_ScrollBarAddLine,
    PseudoElement_ScrollBarSubLine,
    PseudoElement_ScrollBarFirst,
    PseudoElement_ScrollBarLast,
    PseudoElement_ScrollBarUpArrow,
    PseudoElement_ScrollBarDownArrow,
    PseudoElement_ScrollBarLeftArrow,
    PseudoElement_ScrollBarRightArrow,
    PseudoElement_SplitterHandle,
    PseudoElement_ToolBarHandle,
    PseudoElement_ToolBarSeparator,
    PseudoElement_MenuScroller,
    PseudoElement_MenuTearoff,
    PseudoElement_MenuCheckMark,
    PseudoElement_MenuSeparator,
    PseudoElement_MenuIcon,
    PseudoElement_MenuRightArrow,
    PseudoElement_TreeViewBranch,
    PseudoElement_HeaderViewSection,
    PseudoElement_HeaderViewUpArrow,
    PseudoElement_HeaderViewDownArrow,
    PseudoElement_ProgressBarChunk,
    PseudoElement_TabBarTab,
    PseudoElement_TabBarScroller,
    PseudoElement_TabBarTear,
    PseudoElement_SliderGroove,
    PseudoElement_SliderHandle,
    PseudoElement_SliderAddPage,
    PseudoElement_SliderSubPage,
    PseudoElement_SliderTickmark,
    PseudoElement_TabWidgetPane,
    PseudoElement_TabWidgetTabBar,
    PseudoElement_TabWidgetLeftCorner,
    PseudoElement_TabWidgetRightCorner,
    PseudoElement_DockWidgetTitle,
    PseudoElement_DockWidgetCloseButton,
    PseudoElement_DockWidgetFloatButton,
    PseudoElement_DockWidgetSeparator,
    PseudoElement_MdiCloseButton,
    PseudoElement_MdiMinButton,
    PseudoElement_MdiNormalButton,
    PseudoElement_TitleBar,
    PseudoElement_TitleBarCloseButton,
    PseudoElement_TitleBarMinButton,
    PseudoElement_TitleBarMaxButton,
    PseudoElement_TitleBarShadeButton,
    PseudoElement_TitleBarUnshadeButton,
    PseudoElement_TitleBarNormalButton,
    PseudoElement_TitleBarContextHelpButton,
    PseudoElement_TitleBarSysMenu,
    PseudoElement_ViewItem,
    PseudoElement_ViewItemIcon,
    PseudoElement_ViewItemText,
    PseudoElement_ViewItemIndicator,
    PseudoElement_ScrollAreaCorner,
    PseudoElement_TabBarTabCloseButton,
    NumPseudoElements

};

В перечислении PseudoElement содержится список всех виджетов.

Теперь попробуем изменить поведение какого-нибудь виджета, точнее его отрисовки. Недавно мы играли с QHeaderView, а именно с горизонтальным  QHeaderView в таблице QTableView. И мы заметили, что css параметр "checked" нормально срабатывает в стилях, "selected" срабатывает нормально. 

Но нам никак не избавится от рамки в заголовках (от стиля "checked") в случае, когда у нас selectRows в SelectionBehavior (и строка подсвечивается нормально, но и рамка у горизонтального заголовка тоже у всех секций подсвечивается ).

фотка 1

На картинке вариант, когда таблица в режиме только просмотра.

Наследуемся от класса QHeaderView и переопределяем метод paintSection(...) понятно с какой целью. Сюда сначала будет заходить событие отрисовать QHeaderView, а потом оно уже пойдет в renderRule(...). Значит в  paintSection мы может кое-что подправить в отрисовке.

void Horizontal_Header::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const

И тут получаем облом. В чем именно? QStyleOption (точнее QStyleOptionHeader) создается внутри QHeaderView::paintSection(...) и там же сразу отрисовывается. Никаких нужных виртуальных функций, чтобы переопределить  QStyleOptionHeader мы не наблюдаем.

От безисходности проходит отладчиком по методу paintSection(..).

void QHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
    Q_D(const QHeaderView);
    if (!rect.isValid())
        return;
    // get the state of the section
    QStyleOptionHeader opt;
    initStyleOption(&opt);
    QStyle::State state = QStyle::State_None;
    if (isEnabled())
        state |= QStyle::State_Enabled;
    if (window()->isActiveWindow())
        state |= QStyle::State_Active;
    if (d->clickableSections) {
        if (logicalIndex == d->hover)
            state |= QStyle::State_MouseOver;
        if (logicalIndex == d->pressed)
            state |= QStyle::State_Sunken;
        else if (d->highlightSelected) {
            if (d->sectionIntersectsSelection(logicalIndex))
                state |= QStyle::State_On;
            if (d->isSectionSelected(logicalIndex))
                state |= QStyle::State_Sunken;
        }

    }
    if (isSortIndicatorShown() && sortIndicatorSection() == logicalIndex)
        opt.sortIndicator = (sortIndicatorOrder() == Qt::AscendingOrder)
                            ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;

    // setup the style options structure
    QVariant textAlignment = d->model->headerData(logicalIndex, d->orientation,
                                                  Qt::TextAlignmentRole);
    opt.rect = rect;
    opt.section = logicalIndex;
    opt.state |= state;
    opt.textAlignment = Qt::Alignment(textAlignment.isValid()
                                      ? Qt::Alignment(textAlignment.toInt())
                                      : d->defaultAlignment);

    opt.iconAlignment = Qt::AlignVCenter;
    opt.text = d->model->headerData(logicalIndex, d->orientation,
                                    Qt::DisplayRole).toString();
    if (d->textElideMode != Qt::ElideNone)
        opt.text = opt.fontMetrics.elidedText(opt.text, d->textElideMode , rect.width() - 4);

    QVariant variant = d->model->headerData(logicalIndex, d->orientation,
                                    Qt::DecorationRole);
    opt.icon = qvariant_cast(variant);
    if (opt.icon.isNull())
        opt.icon = qvariant_cast(variant);
    QVariant foregroundBrush = d->model->headerData(logicalIndex, d->orientation,
                                                    Qt::ForegroundRole);
    if (foregroundBrush.canConvert())
        opt.palette.setBrush(QPalette::ButtonText, qvariant_cast(foregroundBrush));

    QPointF oldBO = painter->brushOrigin();
    QVariant backgroundBrush = d->model->headerData(logicalIndex, d->orientation,
                                                    Qt::BackgroundRole);
    if (backgroundBrush.canConvert()) {
        opt.palette.setBrush(QPalette::Button, qvariant_cast(backgroundBrush));
        opt.palette.setBrush(QPalette::Window, qvariant_cast(backgroundBrush));
        painter->setBrushOrigin(opt.rect.topLeft());

        qDebug() << "backgroundBrush " << backgroundBrush << " logicalIndex " << logicalIndex;
    }

    // the section position
    int visual = visualIndex(logicalIndex);
    Q_ASSERT(visual != -1);
    if (count() == 1)
        opt.position = QStyleOptionHeader::OnlyOneSection;
    else if (visual == 0)
        opt.position = QStyleOptionHeader::Beginning;
    else if (visual == count() - 1)
        opt.position = QStyleOptionHeader::End;
    else
        opt.position = QStyleOptionHeader::Middle;
    opt.orientation = d->orientation;
    // the selected position
    bool previousSelected = d->isSectionSelected(this->logicalIndex(visual - 1));
    bool nextSelected =  d->isSectionSelected(this->logicalIndex(visual   1));
    if (previousSelected && nextSelected)
        opt.selectedPosition = QStyleOptionHeader::NextAndPreviousAreSelected;
    else if (previousSelected)
        opt.selectedPosition = QStyleOptionHeader::PreviousIsSelected;
    else if (nextSelected)
        opt.selectedPosition = QStyleOptionHeader::NextIsSelected;
    else
        opt.selectedPosition = QStyleOptionHeader::NotAdjacent;
    // draw the section
    style()->drawControl(QStyle::CE_Header, &opt, painter, this);

    painter->setBrushOrigin(oldBO);
}

И вот она зацепка: что это за условие if (d->highlightSelected) ? Решение найдено: когда мы переключаем в режим только чтение (для этого чекбокс сверху слева) мы в обработчике для чекбокса выключенного устанавливаем заголовку horizontalHeader()->setHighlightSections( false); и наоборот для включенного устанавливаем true. И это сработало!

фотка 2

фотка 3

Таким образом еще раз убеждаемся, что не все решается стилями. Не все можно решить также наследованием и переопределением. Но иногда проблема решается только пониманием как работают исходники Qt. Можно было догадаться конечно, что надо каждый раз тыкать setHighlightSections, при переключении режима чтения и режима редактирования, но ...