На хабре еще не было ни одного поста про создание плазмоида на чистом QML с использованием JavaScript. Данный пост призван исправить данный недостаток.
Немного теории и истории
Плазмоид — это виджет оболочки Plasma, появившейся в KDE 4. На этапе появления первых версий KDE 4 все плазмоиды писались исключительно на C++, затем появилась поддержка python. На C++ это было, скажем, не очень легко (ввиду трудоемкости, знаний всех основ языка и пр.), на python уже было гораздо легче. Так как KDE написано на C++ с использованием Qt, то предпочтительнее было бы использовать этот самый Qt (по крайней мере в KDE так считают). Когда появился QML — KDE сразу сделали полную поддержку создания плазмоидов на QML + JS + C++, причем теперь они настаивают на создании плазмоидов исключительно на QML.
На хабре уже были посты о создании плазмоидов на Python и C++, теперь пришла очередь и для QML. Но эта серия постов (Я рассчитываю написать не один пост) не просто о создании плазмоида используя связку QML + JS + C++, но и обо всех подводных камнях, с которым может столкнуться начинающий разработчик виджета. Дело в том, что о QML-плазмоидах еще слишком мало информации на techbase.kde.org, ввиду этого я постоянно вел дискуссии с разработчиками на #kde и #plasma @ irc.freenode.net. Кстати, все они помогают абсолютно всегда и во всех случаях без исключения, за этом им большой и жирный плюс в карму.
Инструментарий
Вообще говоря, для написания плазмоида можно использовать любой текстовый редактор, но относительно недавно на свет появился замечательный plasmate. Plasmate — это часть Plasma SDK, это мини-IDE (мини, потому что еще очень много багов, очень много нереализованных фич и вообще это еще альфа версия, тем не менее, для написания плазмоидов она уже вполне годна) для создания всего, что связано с Plasma: плазмоиды, переключатели окон (alt-tab), эффекты kwin, темы plasma и т.д.
На примере Ubuntu все очень ставится просто:
sudo apt-get install kde-sdk
или еще проще:
sudo apt-get install plasmate
Но для правильной установки нужно установить kubuntu-ppa backports, почитать об этом можно здесь.
После установки запускаем plasmate, щелкаем Plasma Widget. Обзываем будущий плазмоид — задаем значение Addon name, щелкаем по кнопке Create и попадаем в окно редактора. По умолчанию включен режим "Preview", это особенно удобно, если у вас два монитора — на одном можно расположить окно с виджетом, на другом оставить редактор кода.
Итак, далее самое интересное.
QML-компоненты Plasma
На данный момент компонентов плазмы уже достаточно много для того, чтобы описать любой визуальный интерфейс, и не только визуальный, но и интерфейсы для работы с данными (например DataSource).
Все компоненты можно найти на http://api.kde.org, а именно здесь. Для построения визуального интерфейса обычно используют PlasmaExtraComponents и PlasmaComponents, но чаще всего хватает и одного последнего. Их подробное описание можно найти там же.
При создании плазмоида в plasmate заготовка будущего виджета уже готова к установке на рабочий стол. Сразу хочу обратить ваше внимание на заготовку — в ней уже подключены нужные импорты и даже используется i18n, что очень полезно для локализации. К слову, на данный момент в заготовке указан QtQuick 1.1, хотя у Вас в системе уже может стоять Qt5 с QtQuick 2.0. Если попытаться его подключить — plasmate будет ругаться т.к. плазма в последнем KDE (и до версии 5.0) не имеет поддержки QtQuick 2.
Итак, простенький «Hello world» мы уже видим в заготовке. Но чтобы сделать что-то посложнее необходимо кое-что осознать, а именно: любая информация о системе, компьютере, и обо всем остальном в плазмоиде недоступна, т.к. он просто не имеет доступ ко всему вышеперечисленному. А так, как QML — технология, чаще всего привязанная к C++, то и с плазмоидами так же — все данные предоставляются так называемыми DataSource и DataEngine.
DataSource — что-то вроде «поставщика» данных в QML из C++. Например, текущее время мы узнать в QML не можем, а используя C++ и соответствующие заголовочные файлы — вполне. В таком случае нам нужно написать DataEngine, который будет поставлять эту информацию(время) всем плазмоидам, которые его используют как источник (DataSource), кстати, плазмоиды в этом случае могут быть написано на любом языке.
DataEngine — тот самый C++-код, отправляющий данные в QML. Разумеется, его нужно писать по особым правилам KDE, но в данном посте я вникать в это не стану, может быть в следующем, если кому-то интересно. И кстати, DateEngine тоже может быть написан не только на C++.
Далее, я буду пояснять на примере своего плазмоида — KFilePlaces (https://bugs.kde.org/show_bug.cgi?id=180139). На самом деле он еще не готов, но я выложу alpha-версию, чтобы посмотреть, из чего вообще лепятся плазмоиды и пощупать данное тесто.
Создание плазмоида «Places»
Обзор существующих DataEngine
Для создания плазмоида, показывающего «Точки входа», нам необходимо сначала понять, как все работает. Здесь нужно знать две вещи: если данный функционал уже есть — скорее всего в исходниках (в данном случае в исходниках файлового менеджера Dolphin) можно узнать, каким способам берутся нужные нам данные. Если его нет — то можно поискать соответствующий DataEngine в системе и там уже разобраться (поверьте, это очень просто). Для этого можно вызвать Plasma Engine Explorer:
plasmaengineexplorer
Вылезет небольшое окно, в котором можно будет, как ясно из названия, посмотреть все установленные в системе DataEngine. В моем случае, необходимый DataEngine — «places». Сразу после того, как мы выберем его из списка — появится древовидный список объектов, которые данный «двигатель» поставляет наружу. У меня это выглядит так:
Здесь от 0 до 11 — источники данных, т.к. DataSources. Данный массив и есть все точки входа. Чтобы использовать этот двигатель в QML нужно написать нечто подобное:
PlasmaCore.DataSource {
id: placesDataSource
engine: "places"
interval: 1000
connectedSources: sources
}
Здесь sources — это все эти DataSource от 0 до 11, т.е. массив. Можно было бы указать и явно:
PlasmaCore.DataSource {
id: placesDataSource
engine: "places"
interval: 1000
connectedSources: ["0", "2", "11"]
}
Но в моем случае нужно обрабатывать все точки входа, поэтому оставляем «sources».
После создания источника данных пользоваться им сразу нельзя, необходимо создать модель. Для этого существует объект "DataModel", который просто берет данные из DataSource и из которого уже можно выводить данные. Создается DataModel тоже очень просто:
PlasmaCore.DataModel {
id: placesDataModel
dataSource: placesDataSource
}
Сортировка и фильтрация элементов источника данных
Итак, данные мы получили. Но, как можно заметить, они идут не по порядку, да и вполне возможно, что нам их нужно отсортировать по какому-либо признаку(переменной). В моем случае, мне нужно показывать все не скрытые элементы(параметр hidden: false), и отсортировать по параметру isDevice (устройство или место). Для этого воспользуемся средствами PlasmaCore.SortFilterModel:
PlasmaCore.SortFilterModel {
id: sortedEntriesDataModel
filterRole: "isDevice"
filterRegExp: "false"
sourceModel: PlasmaCore.SortFilterModel {
sourceModel: placesDataModel
filterRole: "hidden"
filterRegExp: "false"
sortRole: "isDevice"
sortOrder: "AscendingOrder"
}
}
Здесь sortRole — переменная, по которой будет проводиться сортировка, sortOrder — порядок; filterRole — переменная, по которой будет проводиться фильтрация (т.е. отсекутся все неподходящие элементы), и filterRegExp — выражение, проверкой которого будет проводиться отбор. Здесь есть одна загвоздка: если вы захотите отфильтровать еще по какому-то признаку кроме «isDevice» — вам придется обернуть данный элемент SortFilterModel так, как сделано в данном примере в случае с дополнительной фильтрацией по переменной «hidden».
PlasmaCore.SortFilterModel является расширением PlasmaCore.DataModel, поэтому они совместимы.
Создание визуального списка
Для создания списка сперва советую использовать компонент ScrollArea из PlasmaExtras, он нужен практически для всего, где неизвестны размеры дочернего элемента. Он подходит по таким причинам, как:
- Flickable-контейнер. Содержимое может скроллиться не только скроллбаром сбоку, который можно отключить, но и жестами мыши или тач-скрина.
- Его настоятельно рекомендуют использовать вместо самостоятельного создания
велосипедаоберток из других компонентов Plasma runtime, например таких, как создание Item/Page и т.д. с PlasmaComponents.ScrollBar.
Создаем:
PlasmaExtras.ScrollArea {
id: entriesScrollArea
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.horizontalCenter: plasmoidItem.horizontalCenter
height: 40 * (entriesListView.count + 1)
flickableItem: ListView {
Здесь я специально остановился на ListView, чтобы пояснить: элемент flickableItem представляет собой контейнер для любого элемента, который можно скроллить/прокручивать, если он не вмещается в ширину и/или высоту родительского(нашего ScrollArea) элемента. Это полезно для списков, так как их удобно прокручивать и они могут расширяться.
Чтобы не загромождать экран, я специально вынес создание списка в спойлер:
PlasmaExtras.ScrollArea {
id: entriesScrollArea
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.horizontalCenter: plasmoidItem.horizontalCenter
height: 40 * (entriesListView.count + 1)
flickableItem: ListView {
id: entriesListView
highlightRangeMode: ListView.NoHighlightRange
orientation: ListView.Vertical
focus: true
clip: true
model: sortedEntriesDataModel
delegate: listViewItemTemplate
header: PlasmaComponents.Switch {
id: entriesHeaderSwitch
anchors.top: parent.top
anchors.left: parent.left
anchors.right: devicesHeaderLabel.left
text: i18n("Places")
checked: true
}
highlight: Rectangle {
id: highlightListViewItem
anchors.left: parent.left
color: "lightgrey";
radius: 6;
opacity: 0.6
}
}
}
Здесь пояснять особо не вижу смысла, т.к. список — обычный ListView-элемент QML, начиная с QtQuick 1.0.
Идем дальше.
Создание делегата для элементов списка
Казалось бы, зачем вообще уделять такому простому шагу внимание, но я с ним провозился довольно долго. Дело в том, что элементом-контейнером для делагата обязательно должен быть PlasmaComponents.ListItem. Если просто заключить элементы в Item или просто контейнер(не ListItem) — все элементы перемешаются в кучу. Увы, нигде про этот ньюанс написано не было.
Пробуем!
Если вы все сделали правильно, у нас будет что-то вроде этого:
import QtQuick 1.1
import org.kde.locale 0.1
import org.kde.plasma.components 0.1 as PlasmaComponents
import org.kde.plasma.core 0.1 as PlasmaCore
import org.kde.plasma.extras 0.1 as PlasmaExtras
Item {
width: 200
height: 300
PlasmaCore.DataSource {
id: placesDataSource
engine: "places"
interval: 1000
connectedSources: sources
}
PlasmaCore.DataModel {
id: placesDataModel
dataSource: placesDataSource
}
PlasmaCore.SortFilterModel {
id: sortedEntriesDataModel
filterRole: "isDevice"
filterRegExp: "false"
sourceModel: PlasmaCore.SortFilterModel {
sourceModel: placesDataModel
filterRole: "hidden"
filterRegExp: "false"
sortRole: "isDevice"
sortOrder: "AscendingOrder"
}
}
Component {
id: listViewItemTemplate
PlasmaComponents.ListItem {
id: placeListItem
anchors.left: plasmoidItem.left
anchors.right: plasmoidItem.right
x: 20
height: 40
Item {
id: listItemObject
anchors.left: plasmoidItem.left
anchors.right: plasmoidItem.right
PlasmaCore.IconItem {
id: placeIconItem
anchors.left: parent.left
anchors.leftMargin: 5
source: icon
}
PlasmaComponents.Label {
id: placeNameLabel
anchors.left: placeIconItem.right
anchors.leftMargin: 10
text: name
font.pointSize: 12
}
PlasmaComponents.ProgressBar {
id: placeFreeSizeProgressBar
anchors.top: placeNameLabel.bottom
anchors.left: placeNameLabel.left
width: placeNameLabel.width
height: 10
value: kBUsed / kBSize
opacity: 0
}
}
}
}
PlasmaExtras.ScrollArea {
id: entriesScrollArea
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.horizontalCenter: plasmoidItem.horizontalCenter
height: 40 * (entriesListView.count + 1)
flickableItem: ListView {
id: entriesListView
highlightRangeMode: ListView.NoHighlightRange
orientation: ListView.Vertical
focus: true
clip: true
model: sortedEntriesDataModel
delegate: listViewItemTemplate
header: PlasmaComponents.Switch {
id: entriesHeaderSwitch
anchors.top: parent.top
anchors.left: parent.left
anchors.right: devicesHeaderLabel.left
text: i18n("Places")
checked: true
}
highlight: Rectangle {
id: highlightListViewItem
anchors.left: parent.left
color: "lightgrey";
radius: 6;
opacity: 0.6
}
}
}
}
Или внешне вот так (в моем случае):
Код можно скопипастить и вставить в plasmate.
К сожалению, пост получился слишком обширный, поэтому продолжение следует!
P.S. Не могу понять баг с атрибутом «align: 'center'» в — после него текст тоже центрируется и не сбивается обратно после <br clear=«left» />, в результате чего у меня пост выглядит кривовато.
Автор: broken
Пишу сейчас свой первый плазмоид (мне нужно спарсить страницу и показать всего одно число в плазмоиде). Ваша статья для меня была весьма информативна и полезна. Где же продолжение?