Многие из тех кто занимаются разработкой на C++/Qt знакомы с такой средой как Qt Creator, создатели которой потрудились над дизайном не меньше чем над функциональностью. Но меня, как любителя темных цветовых схем и плоского минимализма, всегда не устраивали светлый фон панелек и градиентные заголовки.
Казалось бы, открытый исходный код — бери да меняй, но неопытность и лень останавливали меня, пока я не узнал про такую вещь как Qt Style Sheets, позволяющюю описать вид виджетов в формате css.
Заранее предупреждаю: Приведенное ниже пускай и не очень грязный, но хак. Конечно он наврятли откроет дыру в безопасности, украдет ваши пароли и отошлет их Пражским хакерам, но возможны разнообразные артефакты в интерфейсе.
Подготовка среды
Для начала берем исходный код среды. Распаковываем и добавляем в конструктор MainWindow::MainWindow()
расположенный примерно в ./src/plugins/coreplugin/mainwindow.cpp:199
свой костыль, снабдив его опознавательными знаками, чтоб в случае чего можно было быстро найти и уничтожить:
//$$MARKER
//HACK: Injecting css to change appearance
//Получаем путь к папке с файлами приложения
//В Linux: /home/shed/.local/share/data/Nokia/QtCreator
//В Windows: C:Document and SettingsuserLocal SettingsApplication Data
QString csspath = QDesktopServices::storageLocation(QDesktopServices::DataLocation)+"/stylesheet.css";
QFile css(csspath);
if (css.open(QIODevice::ReadOnly | QIODevice::Text)){
qDebug() << "NOTE: stylesheet loaded from" << csspath;
QString style = QTextStream(&css).readAll();
qApp->setStyleSheet(style);
} else {
qDebug() << "NOTE: stylesheet not found in " << csspath;
}
//$$MARKEREND
ленивые могут взять измененный файл для последней стабильной версии 2.5.0
затем
qmake && make && ./bin/qtcreator
если все прошло гладко на выходе получаем
NOTE: stylesheet not found in <путь-к-таблице-стилей>/stylesheet.css
теперь создаем этот самый <путь-к-таблице-стилей>/stylesheet.css и пишем туда для проверки
background: blue; color: red;
перезапускаем скомпилированный qtcreator (для экономии нервов следовало бы настроить редактор на запуск qtcreator одним нажатием) и видим такую феерию:
Как и следовало, setStyle покрасил все в синий, но появилось два отщепенца: список классов и методов текущего документа и переключатели панелей вывода. У меня есть два предположения почему так: либо эти элементы не являются потомками QWidget, что врятли, либо они используют свой способ отрисовки обходящий систему стилей Qt, что вполне возможно, учитывая их нестандартный вид.
Перерисовка
Как известно таблица стилей представляет собой набор записей вида:
<Селектор>[, <Селектор>, ...] {
<Параметр>: <Значение>;
<Параметр>: <Значение>;
...
<Параметр>: <Значение>;
}
Если вы никогда раньше не писали ничего подобного, несколько уроков из любого тьюторала дадут вам представление о том что будет происходить ниже. Позже стоит прочесть документацию по Qt Style Sheet и примеры.
Селекторы
В демонологии чтобы вызвать демона нужно знать его имя, в нашем случае для составления селектора нужно знать название класса, objectName или значение любого свойства, заданного с использованием Q_PROPERTY() и setProperty().
Qt потдерживает все СSS2-селекторы. Самые полезные согласно документации:
Универсальный селектор | * | Соответствует всем виджетам. |
Селектор типа | QPushButton | Соответствует экземплярам класса QPushButton и его подклассов. |
Селектор свойства | QPushButton[flat=«false»] | Соответствуют экземплярам класса QPushButton, которые не являются плоскими (flat). Можно использовать этот селектор для проверки любого свойства Qt, заданного с использованием Q_PROPERTY(). Вместо = вы можете также использовать ~= для проверки содержит ли свойство Qt типа QStringList заданную строку QString. |
Селектора класса | .QPushButton | Соответствует экземплярам класса QPushButton, но не его подклассов. Эквивалентно выражению *[class~=«QPushButton»]. |
Селектор идентификатора (ID) | QPushButton#okButton | Соответствует всем экземплярам класса QPushButton, чье objectName равно okButton. |
Селектор потомков | QDialog QPushButton | Соответствует всем экземплярам класса QPushButton, которые являются наследниками класса QDialog (дочерними, внучатыми и т.д.). |
Селектор дочерних элементов | QDialog > QPushButton | Соответствует всем экземплярам класса QPushButton, являющихся непосредственными потомками QDialog. |
Нас пока интересует только селектор типа, более гибким и правильным было бы использование свойств, но это требует вмешательства в код.
Теперь давайте поиграем в дизайнеров.
Напомню, мы хотели сделать темный фон у панелей. Для этого нам нужно выбрать селектор. Что мы обычно видим в этих панельках? Деревья в «Проекты», «Обзор Классов», «Иерархия типов» и «Контур» (в девичестве «Outline»), списки в «Файловая Система», «Открытые Документы» и «Закладки» и таблицы в панелях отладки, т.е. стандартные QListView, QTreeView и QTableVeiw имя которым QAbstractItemView.
Поэтому напишем в наш stylesheet.css следующее:
QAbstractItemView {
color: #EAEAEA;
background: #232323;
font-size: 9pt;
}
Запускаем, уменьшаем размеры окна до минимума чтоб вылезло побольше всяких элементов и видим:
Мы получили что хотели, но наши (пусть и нелюбимые) панельки потеряли свой вид, и причем без нашей команды. Если кто пробегался глазами по MainWindow::MainWindow() возможно заметил неприметную строчку qApp->setStyle(new ManhattanStyle(baseName));
, особо настойчивые могли щелкнуть по ManhattanStyle и заметить что он наследуеться от QProxyStyle т.е. именно он переопределяет рисовку у наших панелек. И именно он уходит за кулисы как только мы задаем стиль.
Предчуствуя кучу мелкой работы по наведению красоты в этих поблекших кнопках я решил не мелочиться, и вбухал помимо QAbstractItemView еще и QMainWindow — отца всех виджетов:
QAbstractItemView, QMainWindow {
color: #EAEAEA;
background: #232323;
font-size: 9pt;
}
Запускаем:
Почти то что надо. Остается омрачить белые пятна табов, хедеров и скролбаров.
Субэлементы и состояния
Как и СCS2, Qt Style Sheet поддерживает субэлементы и состояния, т.е. запись селектора в виде:
<Селектор>::<Субэлемент>:<Состояние>
Например QScrollBar::left-arrow:horizontal
выбирает все левые стрелки гоизонтальных полос прокрутки.
Рассмотрим как это работает на наших белых пятнах.
Оформляем QTreeView и QAbstractItemView
Для начала изменим облик выделенного элемента в QAbstractItemView на более темный с помощью:
QAbstractItemView::item:selected {
color: #EAEAEA;
background-color: #151515;
}
Сравниваем:
Теперь разберемся с QTreeView.
Каждая его строчка состоит из одного субэлемента ::item и одного или нескольких ::branch:
::branch помимо стандартных состояний поддерживает еще 4:
*Синие — элементы с искомым состоянием
:open |
:adjoins-item |
:has-children |
:has-subling |
Подумав, я решил забыть про надоевшие стрелочки и сделать напротив сгрупированных элементов маленькую серенькую точку. Стало быть нам нужны :closed:adjoins-item:has-children
. Подергав параметры получаем чтото вроде:
QTreeView::branch:closed:adjoins-item:has-children {
background: solid #777777;
margin: 6px;
height: 6px;
width: 6px;
border-radius: 3px;
}
Если же вы любите стрелочки, вам должна понравиться конструкция url(filename), которая переданная в image: или border-image: установит в качестве фона или границы изображение, хранящееся на жестком диске либо в системе ресурсов Qt.
Изменяем QScrollBar
В глазах Qt Style Sheets, стандартная полоса прокрутки состоит из следующих субэлементов:
Цинично превращаем это трехмерное великолепие в унылую серо-серую полоску, а кнопки вместе со стрелочками отправляем на награждение премии «Ненужно 2012» следующими строками:
QScrollBar {
border: none;
background: #494949;
height: 6px;
width: 6px;
margin: 0px;
}
QScrollBar::handle {
background: #DBDBDB;
min-width: 20px;
min-height: 20px;
}
QScrollBar::add-line, QScrollBar::sub-line {
background: none;
border: none;
}
QScrollBar::add-page, QScrollBar::sub-page {
background: none;
}
Получаем:
Модифицируем QTabBar
Без лишних слов приступаем к переделке нашей гостинной вкладок. Уж кого, а этот виджет разработчики не обделили:
- Субэлементы: ::tear (разделитель вкладок) и ::scroller (кнопка прокрутки)
- Целая россыпь дополнительных состояний вкладок: :only-one, :first, :last, :middle, :previous--selected, :next-selected, :selected, назначения которых я надеюсь понятны из названий.
- Псевдо-состояния :top, :left, :right, :bottom зависимые от ориентации панели.
- Отрицательные поля которые можно использовать для создания перекрытий
Есть где разгуляться фантазии.
Следует помнить лишь одно, QTabBar лежит на QTabWidget, поэтому фон стоит изменять через виджет, а свойства вкладок уже через панель. Но мы люди неприхотливые, к тому же о фоне у нас позаботился QMainWindow, поэтому мы ляпаем тоненькие неприметные вкладки строками:
QMainWindow,
QAbstractItemView,
QTreeView::branch,
QTabBar::tab
{
...
QTabBar::tab:selected {
font: bold;
border-color: #9B9B9B;
border-bottom-color: #C2C7CB;
}
QTabBar::tab:!selected {
margin-top: 2px;
}
Изменяем QHeaderView
Для тех кто не знал, все эти заголовки таблиц в Qt это и есть QHeaderView. У него один субэлемент ::section который обладает теми же состояниями что и QTabBar::tab.
На этот раз я решил отступиться от традиции и сделать градиентную заливку (да здесь и так можно). Для этого используются конструкции qlineargradient, qradialgradient, qconicalgradient. Градиенты указываются в режиме ограничивающего прямоугольника объекта (Object Bounding Mode). Представьте себе прямоугольник, в котором визуализируется градиент, верхний левый угол которого находится в (0, 0), а нижний правый угол — в (1, 1). Параметры градиента в этом случае указываются как доля между 0 и 1. Эти значения экстраполируются на реальные координаты прямоугольника во время выполнения. Возможно задание значений, которые лежат вне ограничивающего прямоугольника (например, -0.6 или 1.8).
Я использовал следующее оформление:
QHeaderView::section {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #616161, stop: 0.5 #505050,
stop: 0.6 #434343, stop:1 #656565);
color: white;
padding-left: 4px;
border: 1px solid #6c6c6c;
}
То чего мы и добивались.
Уговариваем упрямую панельку
Осталась только одна маленькая проблемка:
Этот отщепеныш остался абсолютно равнодушен к нашим стараниям, более того он оставался равнодушен даже когда мы в качестве селектора указали прадедушку QWidget'а. Несмотря на это, я все же перебирал разные селекторы. Впервые удалось пронять ее селекторами с QComboBox и QLabel:
QComboBox, QComboBox::drop-down {
color: #EAEAEA;
background: #232323;
font-size: 9pt;
border: none;
padding: 1px 18px 1px 3px;
min-width: 6em;
}
QLabel {
border-style: solid;
color: #EAEAEA;
background: #232323;
font-size: 9pt;
}
Минусы на лицо. Мало неумолимой никакими border-style рамки вокруг отщепеныша, так еще и тяжелая наследственнасть просочилась везде куда ненадо.
Тут как кстати, проявились капли здравого смысла. Дети, что такое тоненькое, в верху окна с кнопочками в ряд? Конечно же это QToolBar!
Пробуем:
QToolBar {
border-style: solid;
border-style: outset;
color: #EAEAEA;
background: #333333;
font-size: 9pt;
}
Бинго! Пропали все следы тяжелой болезни и отщепенец послушно влился в общий вид.
Итоги
TODO:
Правильнее было бы не кодировать цвета, а брать их из палитры, которая в лучшем случае могла бы заполняться согласно цветовой схеме редактора. Кроме того можно было бы оформить все это в виде корректного дополнения и добавить свою страницу настройки с выбором стиля, и возможно редактором. Ну самое желанное это конечно же похудеть удобную но толстую боковую панелька. убрав текст и получить доступ к ее оформлению из нашего css файла, но это требует более глубокого вмешательства в код да и вообще совсем другая история.
Автор: shedward