Вступление
Это II часть цикла статей, посвященных написанию плагинов для XBMC с собственным интерфейсом. В I части рассказывается общая информация о создании плагинов для XBMC с собственным интерфейсом и приведен простейший пример. В этой части я дам еще немного общей информации — расскажу о диалогах, а также рассмотрю немного более сложные примеры, в которых будет показано использование картинок для украшения интерфейса, а также создание простейших интерактивных элементов.
Итак, перейдем к диалогам.
Диалоги
В отличие от контролов, диалоги не требуют родительского класса-контейнера и могут быть вызваны непосредственно — путем создания экземпляра соответствующего класса с последующим обращением к нужному методу. Второе отличие заключается в том, что диалоги автоматически используют необходимые текстуры из текущего скина, т. е. нам не надо заботиться об их визуальном оформлении. Функционально диалоги XBMC UI напоминают модальные диалоги tkFileDialog/tkMessageBox в Tkinter или диалоги-наследники QDialog в PyQt.
Большинство диалогов, кроме двух, представляют собой методы класса xbmcgui.Dialog. Методы статические, т. е. их можно вызывать без создания экземпляра соответствующего класса.
Диалоги xbmcgui.Dialog
Далее будут рассмотрены диалоги, реализуемые посредством методов класса xbmcgui.Dialog.
browse
Диалог выбора файлов и папок. Имеет много параметров, позволяющих настроить режим выбора: файл, папка, множественный выбор и т. п.
Возвращает строку, содержащую путь к файлу или папке, или кортеж строк с путями в зависимости от режима выбора.
Не совсем очевидный момент: 3-й параметр вызова (s_shares) представляет собой один из тэгов файла \userdata\sources.xml: video, music, pictures или files. Первые 3 варианта используются для доступа к источникам видео, музыки и фото соответственно, а параметр files открывает доступ к дискам/папкам, добавленным в менеджер файлов. Если менеджер файлов пустой, открывается доступ ко всей файловой системе.
Пример:
dialog = xbmcgui.Dialog()
path = dialog.browse(1, 'Select file', 'video', '.avi|.mkv|.mp4')
browseMultiple
Подвид предыдущего метода. Реализует множественный выбор. Возвращает кортеж строк с путями.
browseSingle
Тоже подвид browse для выбора одного файла/папки. Возвращает путь в виде строки.
input
Диалог ввода с виртуальной клавиатурой. Имеет разные режимы: ввод текста, чисел, даты, времени и т. п. Возвращает введенные данные в виде строки.
numeric
Подвид предыдущего диалога, позволяющий вводить числовую информацию. Также возвращает введенное значение в виде строки.
notification
Выводит всплывающее уведомление в интерфейсе XBMC. Положение уведомления зависит от текущего скина. В Confluence это правый нижний угол.
Внимание: данный метод добавлен в 13.0 Gotham. В предыдущих версиях используйте альтернативные способы вывода всплывающих сообщений, благо этих способов несколько.
yesno
Диалог с кнопками «Да» и «Нет». Возвращает True при выборе «Да» и False в остальных случаях.
ok
Диалог с кнопкой «ОК». Возвращает True при нажатии «ОК» и False, если диалог был закрыт любым другим способом (например, нажатием ESC).
select
Список текстовых строк для выбора одного элемента из некоего набора. При большом количестве строк список прокручивается. Возвращает номер выбранной строки, начиная с 0, либо -1, если диалог был закрыт нажатием ESC (или другой кнопки, реализующей отмену действия).
Далее два диалога, не являющиеся методами Dialog.
DialogProgress
Класс DialogProgress реализует диалоговое окно с прогресс-баром, показывающим ход выполнения некоего процесса.
DialogProgressBG
«Фоновое» окно с прогресс-баром. Обычно отображается в правом верхнем углу интерфейса.
Как мы видим, диалоги дополняют возможности контролов. Комбинируя разные контролы и диалоги, можно реализовать достаточно сложное взаимодействие с пользователем.
Наиболее актуальную краткую справку по диалогам и другим элементам интерфейса плагинов XBMC можно найти здесь.
Добавляем украшательства и интерактивные элементы
Далее рассмотрим немного более сложные примеры, чем простой «Привет, мир!» в предыдущей части. Для украшения интерфейса нам понадобятся картинки и текстуры. Как упоминалось в I части, в исходниках скинов можно найти достаточно широкий набор картинок и текстур. Текстуры для приведенных ниже примеров взяты из исходников скина Confluence, а картинку с танцующим бананом я нашел на просторах Интернета.
Пример на базе класса Window
Начнем, конечно же, с файла addon.xml.
Содержимое addon.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.test.buttons"
name="Buttons 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">Buttons test script</summary>
<description lang="en">My buttons test script.</description>
</extension>
</addon>
Теперь переходим непосредственно к коду плагина.
default.py
# -*- coding: utf-8 -*-
# Licence: GPL v.3 http://www.gnu.org/licenses/gpl.html
# Имортируем модули стандартной библиотеки.
import os, sys
# Импортируем модули XBMC.
import xbmcgui, xbmcaddon
# Создаем экземпляр класса Addon для доступа к параметрам плагина.
# Если имя плагина в xbmcaddon.Addon() явно не указано,
# мы получаем параметры текущего плагина.
_addon = xbmcaddon.Addon()
# Получаем путь к плагину.
_addon_path = _addon.getAddonInfo('path').decode(sys.getfilesystemencoding())
# Коды клавиатурных команд.
ACTION_PREVIOUS_MENU = 10 # Esc
ACTION_NAV_BACK = 92 # Backspace
# Параметр выравнивания текста.
ALIGN_CENTER = 6
# Указываем пути к картинкам и текстурам:
# фон;
background_img = os.path.join(_addon_path, 'images', 'SKINDEFAULT.jpg')
# текстура кнопки без фокуса;
button_nf_img = os.path.join(_addon_path, 'images', 'KeyboardKeyNF.png')
# текстура кнопки в фокусе;
button_fo_img = os.path.join(_addon_path, 'images', 'KeyboardKey.png')
# танцующий банан чисто для прикола :-).
banana_img = os.path.join(_addon_path, 'images', 'banana.gif')
class MyAddon(xbmcgui.Window):
def __init__(self):
# Устанавливаем фоновую картинку.
background = xbmcgui.ControlImage(1, 1, 1280, 720, background_img)
self.addControl(background)
# Размещаем картинку с бананом.
banana_picture = xbmcgui.ControlImage(500, 200, 256, 256, banana_img)
self.addControl(banana_picture)
# Создаем интерактивные контролы (кнопки).
self.set_controls()
# Настраиваем навигацию между контролами.
self.set_navigation()
def set_controls(self):
# Кнопка "Привет".
self.privet_btn = xbmcgui.ControlButton(500, 500, 110, 40, u'Привет…', focusTexture=button_fo_img,
noFocusTexture=button_nf_img, alignment=ALIGN_CENTER)
self.addControl(self.privet_btn)
# Кнопка "Выход".
self.exit_btn = xbmcgui.ControlButton(650, 500, 110, 40, u'Выход', focusTexture=button_fo_img,
noFocusTexture=button_nf_img, alignment=ALIGN_CENTER)
self.addControl(self.exit_btn)
def set_navigation(self):
# Назначаем соседние контролы для кнопки "Привет".
self.privet_btn.controlRight(self.exit_btn)
self.privet_btn.controlLeft(self.exit_btn)
# Назначаем соседние контролы для кнопки "Выход".
self.exit_btn.controlRight(self.privet_btn)
self.exit_btn.controlLeft(self.privet_btn)
# Устанавливаем первоначальный фокус на кнопку "Привет".
self.setFocus(self.privet_btn)
def onAction(self, action):
# Обрабатываем нажатия кнопок для выхода из плагина.
if action == ACTION_NAV_BACK or action == ACTION_PREVIOUS_MENU:
self.close()
def onControl(self, control):
# Обрабатываем активированные контролы.
# Если активирована кнопка "Привет"...
if control == self.privet_btn:
# ...выводим диалоговое окно с надписью и кнопкой ОК при помощи статического метода .Dialog().ok().
xbmcgui.Dialog().ok(u'Привет, мир!', u'Я рад тебя видеть :-)')
# Если активирована кнопка "Выход", выходим из плагина.
elif control == self.exit_btn:
self.close()
if __name__ == '__main__':
addon = MyAddon()
addon.doModal()
del addon
Далее построчный разбор. Для отображение номеров строк используйте редактор с такой функцией, например Notepad++.
Очевидные или описанные ранее моменты пропускаю.
12, 14: создаем экземпляр класса Addon для доступа к параметрам плагина. В данном случае нам нужен путь к плагину для доступа к файлам картинок.
21: числовая константа, определяющая выравнивание текста в некоторых контролах, включая кнопку.
25—31: определяем пути к файлам картинок. Все картинки хранятся в подпапке \images папки с плагином.
Примечание: если указать имя файла без полного пути, XBMC будет искать картинку в ресурсах текущего скина. Однако злоупотреблять этим не стоит, поскольку имена файлов картинок в разных скинах чаще всего не совпадают.
39—43: создаем два контрола с картинками и выводим их на экран.
49—55: создаем 2 кнопки. Файлы текстур и выравнивание текста надписи на кнопке указываем явно.
57—65: создаем правила навигации. В данном случае назначаем каждой из кнопок соседа справа и слева. Поскольку кнопок всего 2, каждая из кнопок является соседом друг для друга, т. е. при нажатии на клавиши со стрелками вправо или влево фокус будет поочередно перемещаться между нашими двумя кнопками.
67: чтобы навигация работала, устанавливаем фокус на одну из кнопок. Если этого не сделать, контролы можно будет активировать только мышью.
67—70: метод onAction такой же, как и в предыдущем примере. Повторюсь, если вы решили переопределить этот метод в своем классе, обязательно прописывайте команду (команды) выхода из плагина!
72—80: обрабатываем контролы. Метод onControl получает в качестве второго параметра экземпляр активированного контрола, т. е. контрола, который мы кликнули мышью или на котором нажали ENTER, когда он был в фокусе.
77: при нажатии кнопки «Привет…» выводится простое диалоговое окно с кнопкой «ОК».
79, 80: кнопка «Выход» является альтернативным способом выхода из плагина, помимо клавиш ESC и BACKSPACE.
Если всё сделано правильно, мы должны увидеть
вот такую картинку
Обратите внимание на 2 момента:
— во-первых, в файле gif (как и в png) поддерживается анимация;
— во-вторых, контролы, созданные позднее, (картинка с бананом, кнопки) отображаются поверх фона, который создан первым.
Если выбрать/кликнуть кнопку «Привет», откроется
диалоговое окно с текстом.
Полностью плагин с примером можно скачать отсюда.
Пример на базе класса WindowDialog
В предыдущем примере мы наследовали от класса Window. Как было сказано в I части (и как вы могли убедиться в самом первом примере), класс-контейнер Window имеет черный непрозрачный фон, поэтому в предыдущем примере мы поместили на заднем плане текстуру с фоном, поверх которой разместили другие контролы. Теперь попробуем несколько видоизменить наш предыдущий пример. Во-первых, мы будем наследовать от класса WindowDialog, и, во-вторых, в качестве фона выберем текстуру с частичной прозрачностью, которая будет занимать только часть экрана. Напомню, что класс WindowDialog имеет прозрачный фон, т. е. в результате мы должны получить окно нашего плагина поверх интерфейса XBMC. Проверим это на практике.
Как всегда, addon.xml — главный файл плагина.
Содержимое addon.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.test.buttons.alt"
name="Buttons test script - Dialog"
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">Buttons test script</summary>
<description lang="en">My buttons test script.</description>
</extension>
</addon>
Далее сам код.
default.py
# -*- coding: utf-8 -*-
# Licence: GPL v.3 http://www.gnu.org/licenses/gpl.html
# Имортируем модули стандартной библиотеки.
import os, sys
# Импортируем модули XBMC.
import xbmcgui, xbmcaddon
# Создаем экземпляр класса Addon для доступа к параметрам плагина.
# Если имя плагина в xbmcaddon.Addon() явно не указано,
# мы получаем параметры текущего плагина.
_addon = xbmcaddon.Addon()
# Получаем путь к плагину.
_addon_path = _addon.getAddonInfo('path').decode(sys.getfilesystemencoding())
# Коды клавиатурных комманд.
ACTION_PREVIOUS_MENU = 10 # Esc
ACTION_NAV_BACK = 92 # Backspace
# Параметр выравнивания текста.
ALIGN_CENTER = 6
# Указываем пути к картинкам и текстурам:
# фон;
background_img = os.path.join(_addon_path, 'images', 'ContentPanel.png')
# текстура кнопки без фокуса;
button_nf_img = os.path.join(_addon_path, 'images', 'KeyboardKeyNF.png')
# текстура кнопки в фокусе;
button_fo_img = os.path.join(_addon_path, 'images', 'KeyboardKey.png')
# танцующий банан чисто для прикола :-).
banana_img = os.path.join(_addon_path, 'images', 'banana.gif')
class MyAddon(xbmcgui.WindowDialog):
def __init__(self):
# Устанавливаем фоновую картинку.
background = xbmcgui.ControlImage(370, 100, 500, 500, background_img)
self.addControl(background)
# Размещаем картинку с бананом.
banana_picture = xbmcgui.ControlImage(500, 200, 256, 256, banana_img)
self.addControl(banana_picture)
# Создаем интерактивные контролы (кнопки).
self.set_controls()
# Настраиваем навигацию между контролами.
self.set_navigation()
def set_controls(self):
# Кнопка "Привет".
self.privet_btn = xbmcgui.ControlButton(500, 500, 110, 40, u'Привет…', focusTexture=button_fo_img,
noFocusTexture=button_nf_img, alignment=ALIGN_CENTER)
self.addControl(self.privet_btn)
# Кнопка "Выход".
self.exit_btn = xbmcgui.ControlButton(650, 500, 110, 40, u'Выход', focusTexture=button_fo_img,
noFocusTexture=button_nf_img, alignment=ALIGN_CENTER)
self.addControl(self.exit_btn)
def set_navigation(self):
# Назначаем соседние контролы для кнопки "Привет".
self.privet_btn.controlRight(self.exit_btn)
self.privet_btn.controlLeft(self.exit_btn)
# Назначаем соседние контролы для кнопки "Выход".
self.exit_btn.controlRight(self.privet_btn)
self.exit_btn.controlLeft(self.privet_btn)
# Устанавливаем первоначальный фокус на кнопку "Привет".
self.setFocus(self.privet_btn)
def onAction(self, action):
# Обрабатываем нажатия кнопок для выхода из плагина.
if action == ACTION_NAV_BACK or action == ACTION_PREVIOUS_MENU:
self.close()
def onControl(self, control):
# Обрабатываем активированные контролы.
# Если активирована кнопка "Привет"...
if control == self.privet_btn:
# ...выводим диалоговое окно с надписью и кнопкой ОК при помощи статического метода xbmcgui.Dialog().ok().
xbmcgui.Dialog().ok(u'Привет, мир!', u'Я рад тебя видеть :-)')
# Если активирована кнопка "Выход", выходим из плагина.
elif control == self.exit_btn:
self.close()
if __name__ == '__main__':
addon = MyAddon()
addon.doModal()
del addon
По сравнению с предыдущим примером изменились всего 3 строчки.
25: другая текстура фона.
33: наследуем от WindowDialog.
37: другие координаты и размеры фоновой картинки.
Проверяем результат.
Теперь наш плагин не занимает весь экран, а скромно помещается в окне. Подобный вариант подойдет для плагинов с небольшим числом контролов, которые не будут воспроизводить видео или музыку, поскольку, напомню, при наследовании от WindowDialog все контролы всегда остаются поверх видео или музыкальной визуализации.
Полностью плагин из данного примера можно скачать отсюда.
Заключение
Во II части цикла статей о создании плагинов для XBMC с собственным интерфейсом я рассказал о диалогах, а также привел два относительно простых примера плагинов, использующих текстуры для оформления интерфейса и имеющих простые интерактивные элементы.
В III части я планирую немного затронуть вопросы взаимодействия плагинов с XBMC посредством разных API, а также продемонстрирую написанный мною микро-фреймоворк, упрощающий создание интерфейса плагинов XBMC.
Источники информации
Краткая документация XBMC Python API.
HOW-TO:Write Python Scripts for XBMC.
Предыдущие статьи
Подробная анатомия простого плагина для XBMC.
Пишем плагин для XBMC с собственным интерфейсом: часть I — теория и простейший пример.