Pull to refresh

Пишем плагин для XBMC с собственным интерфейсом: часть I — теория и простейший пример

Reading time11 min
Views21K

Вступление


Это I часть цикла статей, посвященных написанию плагинов для XBMC с собственным интерфейсом. В ней будет рассмотрена основная информация о создании интерфейса плагина и приведен простейший пример.
Во II части я планирую дать еще немного теории и чуть более сложный пример.
В III части я продемонстрирую написанный мною микро-фреймоворк, упрощающий компоновку интерфейса.

В своей первой статье «Подробная анатомия простого плагина для XBMC» я попытался максимально подробно описать структуру плагина для XBMC. Кроме того, я постарался рассказать, чем отличаются плагина-источники контента и плагины скрипты. Для тех, кто забыл, напомню, что плагины-источники контента формируют многоуровневые списки (каталоги) контента при помощи функции addDirectoryItem(). Т. е. мы последовательно «скармливаем» функции элементы списка, а всем остальным — отображением списка, обработкой событий при выборе элементов списка и т. п. — занимается XBMC. В свою очередь, плагины-скрипты лишены такой привилегии, и в них все упомянутые задачи ложатся на разработчика. Также напомню, что, как и плагины-источники, скрипты могут отображаться в разделах «Видео», «Музыка» и/или «Фото», если они выполняют некие задачи по работе с указанным контентом, либо же в разделе «Программы», если это плагины-скрипты общего назначения. Кроме того, плагины-скрипты также могут скачивать субтитры, работать как фоновые службы и т. п. Функция плагина определяется тэгами в файле Addon.xml, содержащем всю информацию о нашем плагине.

К сожалению, никаких гениальных идей меня не посетило, поэтому примеры в этих статьях носят несколько абстрактно-теоретический характер. Применение этой информации для написания чего-нибудь полезного я оставляю вам.

Для понимания этих статей весьма желательно хорошее знание принципов ООП и написания GUI-приложений на языке Python (далее — Питон).

Общая информация


За работу с интерфейсом отвечает модуль xbmcgui, входящий в XBMC Python API. Этот модуль содержит общие классы-контейнеры, а также классы, отвечающие за разные элементы интерфейса. К сожалению, часть API, отвечающая за интерфейс менее удобна, чем традиционные питоновские библиотеки GUI, и, к тому же, документация изобилует неточностями. Я рекомендую пользоваться модулями-заглушками, о которых я писал в своей предыдущей статье. Кроме удобства разработки, они также содержат более актуальную информацию: как по набору классов, методов и функций, так и по их использованию (в докстрингах).

Компоненты интерфейса плагина


Для создания интерфейса плагина модуль xbmcgui предоставляет нам различные компоненты:
— классы-контейнеры (класс Window и его наследники);
— виджеты, или в терминологии XBMC контролы (controls), отвечающие за разные элементы интерфейса (классы-наследники Control);
— диалоги, служащие для вывода информации и взаимодействия с пользователем (методы класса Dialog и другие).

Рассмотрим первые 2 пункта подробнее.

Классы WindowXML и WindowXMLDialog


Как следует из названий, эти классы-контейнеры используют скелет интерфейса, написанный на XML и имеющий структуру, аналогичную структуре скинов XBMC. Т. е. по сути интерфейс на базе этих классов имеет свой собственный мини-скин. Углубляться в дебри скинописательства (в котором я, признаться, разбираюсь слабо) я не хочу, поэтому останавливаться подробно на этих классах я не буду.

Классы Window и WindowDialog


Классы Window и WindowDialog представляют собой родительские контейнеры, в которых размещаются другие элементы интерфейса — контролы. Для размещения контролов эти классы предоставляют координатную сетку, начинающуюся от левого верхнего угла экрана. По умолчанию разрешение видимой области координатной сетки равно 1280 х 720 пикселей. Это разрешение можно менять, но лично я не вижу в этом особого смысла, поэтому в примерах будет использоваться стандартное разрешение видимой области. Я не зря употребил термин «видимая область». Координаты контролов могут иметь любые значения, в т. ч. и отрицательные, но на экране будет видно только то, что попадает в видимую область. Это можно использовать, к примеру, чтобы временно скрыть какой-то контрол, задав для него координаты, заведомо выходящие за видимую область (например, -5000, -5000).
Внимание: не путайте разрешение видимой области координатной сетки и разрешение экрана. Видимая область имеет одинаковое разрешение при любом фактическом разрешении экрана, чтобы элементы интерфейса всегда имели одинаковый масштаб независимо от фактического разрешения.

Отличие между классами Window и WindowDialog заключается в том, что класс Window имеет черный непрозрачный фон и может прятаться под окном воспроизведения видео или музыкальной визуализации. В свою очередь, WindowDialog имеет прозрачный фон, и его контролы всегда отображаются поверх других элементов интерфейса XBMC. Таким образом, если ваш плагин будет воспроизводить видео и проигрывать музыку, то для него лучше использовать класс Window, а в остальных случаях выбор класса зависит от личных предпочтений и задач плагина.

Для обработки событий интерфейса оба класса имеют методы onAction() и onControl(). Первый перехватывает клавиатурные команды, а второй — активированные контролы. Таким образом, чтобы обеспечить взаимодействие с пользователем, эти методы необходимо определить в дочернем классе, реализующем интерфейс вашего плагина, прописав в них соответствующие обработчики событий.
Внимание! По умолчанию метод onAction «ловит» клавиатурные команды, соответствующие клавишам BACKSPACE и ESC, которые вызывают метод close (выход из плагина). Поэтому если в дочернем классе этот метод переопределен, в этом классе нужно обязательно прописать как минимум команду (команды) выхода из плагина. Иначе выйти из плагина можно будет, только принудительно остановив («убив») XBMC, что может представлять собой проблему, скажем, во встроенных системах (OpenELEC и др.).

Контролы


Теперь познакомимся подробнее с контролами. Я расскажу не о всех контролах, а только о тех, которые изучил лично, но, думаю, их хватит для большинства задач. Контролы представляют собой элементы интерфейса (виджеты) плагина. Однако в отличие от виджетов в GUI-фреймворках общего назначения (Tkinter, PyQt), контролы представляют собой своеобразные «скелеты» элементов интерфейса. Для визуального оформления контролов нужны графические файлы с картинками (текстурами).

Те, кто хорошо владеет фотожабой графическими программами, могут нарисовать текстуры самостоятельно, а остальные плагинописатели могут взять нужные файлы текстур в ресурсах скинов XBMC. К сожалению, в готовых скинах текстуры запакованы в специальном формате, поэтому нам понадобятся исходники скина. Исходники стандартного скина XBMC, Confluence, включая картинки, можно найти здесь.
Примечание: некоторые контролы (например, кнопка) могут автоматически использовать нужные текстуры текущего скина, но полагаться на такое поведение нельзя. Во-первых, это умеют не все контролы, и, во-вторых, при переключении на другой скин может нарушиться внешний вид плагина. Поэтому файлы текстур для контролов лучше задавать явно.

Как можно догадаться из написанного выше, при создании плагинов с собственным интерфейсом можно использовать 2 подхода: простой, при котором внешний вид плагина будет иметь одинаковый вид в любом скине, и сложный, при котором файлы текстур для виджетов будут выбираться в зависимости от текущего скина, чтобы интерфейс плагина был оформлен в том же стиле, что и скин. Понятное дело, что нельзя объять необъятное, поэтому при втором подходе придется ограничиться несколькими популярными скинами, а для остальных использовать оформление по умолчанию.

Далее краткое описание основных контролов интерфейса.

ControlLabel

Простая надпись с прозрачным фоном. Текстуры не использует. Это полный аналог классов Label в Tkinter или QLabel в PyQt. Выравнивание текста задается одной из следующих числовых констант:
ALIGN_LEFT = 0
ALIGN_RIGHT = 1
ALIGN_CENTER_X = 2
ALIGN_CENTER_Y = 4
ALIGN_CENTER = 6
ALIGN_TRUNCATED = 8
ALIGN_JUSTIFY = 10


На всякий случай напомню, что в реальном плагине текстовые строки для этого и других контролов, отображающих текстовую информацию, лучше получать из языковых файлов при помощи метода Addon.getLocalizedString(), чтобы интерфейс вашего плагина отвечал текущим языковым настройкам XBMC.

ControlFadeLabel

Аналог ControlLabel с той лишь разницей, что слишком длинная строка автоматически прокручивается.

ControlTextBox

Тестовое окно, которое может содержать длинный текст с автоматическим переносом строк. Прокрутки нет, и текст, который не поместился в текстовое окно, обрезается.

ControlImage

Картинка. Поддерживаются основные форматы файлов изображений (jpg, png, gif). В файлах png и gif поддерживается прозрачность и анимация (если есть). По умолчанию картинка вписывается в отведенное место с искажением пропорций, но способ вписывания можно задавать. К сожалению, параметр вписывания не работает при последующем изменении картинки.

ControlButton

Кнопка. Обработчик нажатия на кнопку прописывается в методе onControl() класса-контейнера.

ControlRadioButton и ControlCheckMark

Радиокнопка и флажок («галочка»). Функционально полностью аналогичны и отличаются только внешним видом. Используются в качестве переключателя с двумя состояниями. Требуют текстуры.

ControlEdit

Поле для ввода текста. При выборе открывается экранная клавиатура, позволяющая ввести нужный текст. Если указать параметр isPassword=True, вводимый текст будет заменяться звездочками.

ControlList

Список. В список можно добавлять простые текстовые строки, но на самом деле каждый элемент списка представляет собой экземпляр класса ListItem. Напомню, что из таких же элементов состоят списки в плагинах-источниках контента. Однако, в отличие от плагинов-источников, в ControlList функциональность элементов списка ограничена. Фактически поддерживаются только текстовые подписи и маленькие иконки (thumbnailImage). Всю остальную логику, например действие при выборе элемента списка, нужно реализовывать самому. Контрол требует текстуры для оформления списка.

Если все элементы списка не помещаются в отведенное место, список прокручивается.

ControlSlider

Ползунок со шкалой. Служит для плавной регулировки какого-либо параметра. Требует явного указания текстур.

Примеры контролов, оформленных с использованием текстур скина Confluence, можно увидеть на скриншоте ниже.

Основные контролы интерфейса плагина



ControlButton, ControlRadioButton, ControlEdit, ControlList и ControlSlider представляют собой интерактивные элементы, которые меняют свой вид при их выборе. Теоретически выбрать можно любой контрол, например, ControlLabel, но при этом не будет никакой визуальной обратной связи.

Для навигации по интерфейсу плагина при помощи клавиш со стрелками на клавиатуре или пульте ДУ каждому контролу нужно назначить соседей, на которых будет перемещаться фокус при нажатии соответствующей клавиши со стрелкой. Для этого служат методы setNavigation, а также controlUp, controlDown, controlLeft и controlRight, унаследованные от Control.
Кроме того, чтобы навигация работала, при начальном отображении интерфейса не забываем установить фокус на один из контролов.

Контролы добавляются при помощи метода addControl класса-контейнера. При этом контролы, добавленные позже, отображаются поверх тех, что добавлены ранее. Например, мы можем сначала вывести на экран картинку, которая будет служить в качестве фона, а все остальные контролы размещать поверх нее.

Внимание: практически все контролы имеют методы, которые позволяют менять их свойства (текст, картинку и т. п.) в ходе выполнения плагина. Так вот, всегда меняйте свойство контрола (например, надпись ControlLabel при помощи setLabel('Some Text') после того, как этот контрол был добавлен в класс-контейнер методом addControl. При попытке изменить свойства «несвязанного» контрола в лучшем случае ничего не произойдет, а в худшем возможны всякие интересные глюки интерфейса XBMC.

Теперь перейдем к практическим примерам. Начнем, конечно же, с классического «Hello, World!», или в русском варианте «Привет, мир!».

Плагин «Привет, мир!»


Как я уже говорил в своей первой статье, для каждого плагина нужен обязательный служебный файл addon.xml.
Содержимое addon.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.test"
   name="Test script"
   version="0.0.1"
   provider-name="Roman_V_M">
  <requires>
    <import addon="xbmc.python" version="2.0"/>
  </requires>
  <extension point="xbmc.python.script" library="default.py">
    <provides>executable</provides>
  </extension>
  <extension point="xbmc.addon.metadata">
    <platform>all</platform>
    <summary lang="en">Test script</summary>
    <description lang="en">My test script.</description>
  </extension>
</addon>



Строки
<extension point="xbmc.python.script" library="default.py">
    <provides>executable</provides>
  </extension>

говорят нам (и XBMC) о том, что это программный плагин-скрипт, который будет доступен в разделе «Программы».

Теперь непосредственно код нашего плагина:
Содержимое default.py
# -*- coding: utf-8 -*-
# Licence: GPL v.3 http://www.gnu.org/licenses/gpl.html

# Импортируем нужный модуль
import xbmcgui

# Коды клавиатурных действий
ACTION_PREVIOUS_MENU = 10 # По умолчанию - ESC
ACTION_NAV_BACK = 92 # По умолчанию - Backspace


# Главный класс-контейнер
class MyAddon(xbmcgui.Window):

    def __init__(self):
        # Создаем текстовую надпись.
        label = xbmcgui.ControlLabel(550, 300, 200, 50, u'Привет, мир!')
        # Добавляем наддпись в контейнер
        self.addControl(label)

    def onAction(self, action):
        # Если нажали ESC или Backspace...
        if action == ACTION_NAV_BACK or action == ACTION_PREVIOUS_MENU:
            # ...закрываем плагин.
            self.close()


if __name__ == '__main__':
    # Создаем экземпляр класса-контейнера.
    addon = MyAddon()
    # Выводим контейнер на экран.
    addon.doModal()
    # По завершении удаляем экземпляр.
    del addon




Далее краткий разбор, поскольку основные моменты уже указаны в комментариях к коду. Для отображения номеров строк используйте текстовый редактор с соответствующей функцией, например Notepad++.

Строки 8, 9: здесь мы задаем константы, соответствующие числовым кодам клавиатурных событий, используемых XBMC. Полные перечень клавиатурных событий можно найти в исходниках XBMC. Соответствие между клавиатурными событиями и клавишами клавиатуры, пульта ДУ или другого органа управления задается в конфигурационном файле keyboard.xml

13—25: описываем класс, реализующий наш интерфейс. В данном случае класс наследует от Window. Наш интерфейс содержит единственный контрол — ControlLabel с соответствующей надписью. Обратите внимание, что первые 4 параметра контрола — его координаты и размеры по ширине и высоте в пикселях координатной сетки. Эти параметры являются обязательными. Координаты и размеры можно впоследствии менять при необходимости. Если мы хотим создать контрол, но отложить его вывод на экран, то можно указать фиктивные координаты и/или размеры, а реальные параметры задавать после отображения контрола.
За добавление контролов в контейнер отвечает метод addControl() родительского класса.

В методе onAction мы перехватываем клавиатурные события, соответствующие клавишам ESC и Backspace, чтобы иметь возможность выйти из нашего плагина. Еще раз повторюсь, если метод onAction переопределен в дочернем классе, плагин должен обязательно иметь код, позволяющий выйти из него, не прибегая к радикальным мерам, вроде диспетчера задач.
Примечание: несмотря на сравнение с целыми числами, параметр action на самом деле представляет собой экземпляр служебного класса Action.

32: метод doModal() выводит созданный нами интерфейс на экран. Этот метод аналогичен методам mainloop() в Tkinter.Tk или exec_() в QDialog. Интерфейс будет отображаться до тех пор, пока не будет вызван метод close().
Кроме doModal(), можно использовать show(). При этом интерфейс будет отображаться, пока работает плагин или пока не вызван close(). Если использовать show(), организация event loop целиком ложится на программиста.

34: зачем удалять экземпляр класса по завершении работы я, честно говоря, не знаю. Это я увидел в примере в официальной WiKi.

Если всё сделано правильно, мы должны увидеть
вот такую картинку.



Согласитесь, не очень привлекательное зрелище. Как сказано выше, класс Window имеет черный непрозрачный фон. Поэтому в следующем примере мы попытаемся немного украсить наш интерфейс, а также добавить некоторые интерактивные элементы.

Ради любопытства попробуйте наследовать от WindowDialog. При таком варианте надпись появится поверх интерфейса XBMC.

Готовый плагин «Привет, мир!» можно скачать отсюда. Напомню, что установить плагин можно из меню «Система» > «Дополнения» > «Установить из файла ZIP». Все новые плагины устанавливаются в папку \addons папки с пользовательскими настройками. В Windows это, как правило, %AppData%\XBMC, в Linux — $HOME/.xbmc.

Заключение


В I части статьи, посвященной написанию плагинов для XBMC с собственным интерфейсом, были рассмотрены основные элементы интерфейса плагина, а также дан простейший пример.
В следующей части я планирую рассказать о диалогах, а также привести чуть более сложный пример, демонстрирующий использование картинок для украшения интерфейса, а также создание интерактивных элементов.

Те, кто хочет забежать немного вперед, могут скачать этот плагин, демонстрирующий описанные выше контролы. Именно с него сделан скриншот с примерами контролов выше. Плагин использует написанный мною микро-фрейморк, о котором я планирую рассказать в III части, но опытные питонщики, думаю, без труда во всем разберутся.

P. S. Сделал несколько мелких исправлений и добавлений.
P.P.S. Исправил информацию о методе onClose.

Продолжение


Пишем плагин для XBMC с собственным интерфейсом: часть II — диалоги и украшателства.
Tags:
Hubs:
+17
Comments6

Articles