2015-01-09 14:11:21 +03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# This is a "local" version of PyXBMCt to be used in standalone addons.
|
|
|
|
#
|
|
|
|
# PyXBMCt is a mini-framework for creating XBMC Python addons with arbitrary UI
|
|
|
|
# made of controls - decendants of xbmcgui.Control class.
|
|
|
|
# The framework uses image textures from XBMC Confluence skin.
|
|
|
|
#
|
|
|
|
# Licence: GPL v.3 http://www.gnu.org/licenses/gpl.html
|
|
|
|
#
|
|
|
|
## @package addonwindow
|
|
|
|
# PyXBMCt framework module
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
2015-06-23 23:00:27 +03:00
|
|
|
import xbmc
|
|
|
|
import xbmcgui
|
|
|
|
|
|
|
|
|
|
|
|
# _addon = xbmcaddon.Addon()
|
2015-01-09 14:11:21 +03:00
|
|
|
_images = os.path.join(os.path.dirname(__file__), 'textures', 'default')
|
|
|
|
|
|
|
|
|
|
|
|
# Text alighnment constants. Mixed variants are obtained by bit OR (|)
|
|
|
|
ALIGN_LEFT = 0
|
|
|
|
ALIGN_RIGHT = 1
|
|
|
|
ALIGN_CENTER_X = 2
|
|
|
|
ALIGN_CENTER_Y = 4
|
|
|
|
ALIGN_CENTER = 6
|
|
|
|
ALIGN_TRUNCATED = 8
|
|
|
|
ALIGN_JUSTIFY = 10
|
|
|
|
|
|
|
|
# XBMC key action codes.
|
|
|
|
# More codes at https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h
|
|
|
|
## ESC action
|
|
|
|
ACTION_PREVIOUS_MENU = 10
|
|
|
|
## Backspace action
|
|
|
|
ACTION_NAV_BACK = 92
|
|
|
|
## Left arrow key
|
|
|
|
ACTION_MOVE_LEFT = 1
|
|
|
|
## Right arrow key
|
|
|
|
ACTION_MOVE_RIGHT = 2
|
|
|
|
## Up arrow key
|
|
|
|
ACTION_MOVE_UP = 3
|
|
|
|
## Down arrow key
|
|
|
|
ACTION_MOVE_DOWN = 4
|
|
|
|
## Mouse wheel up
|
|
|
|
ACTION_MOUSE_WHEEL_UP = 104
|
|
|
|
## Mouse wheel down
|
|
|
|
ACTION_MOUSE_WHEEL_DOWN = 105
|
|
|
|
## Mouse drag
|
|
|
|
ACTION_MOUSE_DRAG = 106
|
|
|
|
## Mouse move
|
|
|
|
ACTION_MOUSE_MOVE = 107
|
|
|
|
|
|
|
|
|
|
|
|
def _set_textures(textures={}, kwargs={}):
|
|
|
|
"""Set texture arguments for controls."""
|
|
|
|
for texture in textures.keys():
|
|
|
|
try:
|
|
|
|
kwargs[texture]
|
|
|
|
except KeyError:
|
|
|
|
kwargs[texture] = textures[texture]
|
|
|
|
|
|
|
|
|
|
|
|
class AddonWindowError(Exception):
|
|
|
|
"""Custom exception."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Label(xbmcgui.ControlLabel):
|
|
|
|
"""ControlLabel class.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
label: string or unicode - text string.
|
|
|
|
font: string - font used for label text. (e.g. 'font13')
|
|
|
|
textColor: hexstring - color of enabled label's label. (e.g. '0xFFFFFFFF')
|
|
|
|
disabledColor: hexstring - color of disabled label's label. (e.g. '0xFFFF3300')
|
|
|
|
alignment: integer - alignment of label - *Note, see xbfont.h
|
|
|
|
hasPath: bool - True=stores a path / False=no path.
|
|
|
|
angle: integer - angle of control. (+ rotates CCW, - rotates CW)"
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.label = Label('Status', angle=45)
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
return super(Label, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class FadeLabel(xbmcgui.ControlFadeLabel):
|
|
|
|
"""Control that scrolls label text.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
font: string - font used for label text. (e.g. 'font13')
|
|
|
|
textColor: hexstring - color of fadelabel's labels. (e.g. '0xFFFFFFFF')
|
|
|
|
_alignment: integer - alignment of label - *Note, see xbfont.h
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.fadelabel = FadeLabel(textColor='0xFFFFFFFF')
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
return super(FadeLabel, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class TextBox(xbmcgui.ControlTextBox):
|
|
|
|
"""ControlTextBox class.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
font: string - font used for text. (e.g. 'font13')
|
|
|
|
textColor: hexstring - color of textbox's text. (e.g. '0xFFFFFFFF')
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.textbox = TextBox(textColor='0xFFFFFFFF')
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
return super(TextBox, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class Image(xbmcgui.ControlImage):
|
|
|
|
"""ControlImage class.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
filename: string - image filename.
|
|
|
|
colorKey: hexString - (example, '0xFFFF3300')
|
|
|
|
aspectRatio: integer - (values 0 = stretch (default), 1 = scale up (crops), 2 = scale down (black bars)
|
|
|
|
colorDiffuse: hexString - (example, '0xC0FF0000' (red tint)).
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.image = Image('d:\images\picture.jpg', aspectRatio=2)
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
return super(Image, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class Button(xbmcgui.ControlButton):
|
|
|
|
"""ControlButton class.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
label: string or unicode - text string.
|
|
|
|
focusTexture: string - filename for focus texture.
|
|
|
|
noFocusTexture: string - filename for no focus texture.
|
|
|
|
textOffsetX: integer - x offset of label.
|
|
|
|
textOffsetY: integer - y offset of label.
|
|
|
|
alignment: integer - alignment of label - *Note, see xbfont.h
|
|
|
|
font: string - font used for label text. (e.g. 'font13')
|
|
|
|
textColor: hexstring - color of enabled button's label. (e.g. '0xFFFFFFFF')
|
|
|
|
disabledColor: hexstring - color of disabled button's label. (e.g. '0xFFFF3300')
|
|
|
|
angle: integer - angle of control. (+ rotates CCW, - rotates CW)
|
|
|
|
shadowColor: hexstring - color of button's label's shadow. (e.g. '0xFF000000')
|
|
|
|
focusedColor: hexstring - color of focused button's label. (e.g. '0xFF00FFFF')
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.button = Button('Status', font='font14')
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
textures = {'focusTexture': os.path.join(_images, 'Button', 'KeyboardKey.png'),
|
|
|
|
'noFocusTexture': os.path.join(_images, 'Button', 'KeyboardKeyNF.png')}
|
|
|
|
_set_textures(textures, kwargs)
|
|
|
|
try:
|
|
|
|
kwargs['alignment']
|
|
|
|
except KeyError:
|
|
|
|
kwargs['alignment'] = ALIGN_CENTER
|
|
|
|
return super(Button, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class RadioButton(xbmcgui.ControlRadioButton):
|
|
|
|
"""ControlRadioButton class.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
label: string or unicode - text string.
|
|
|
|
focusTexture: string - filename for focus texture.
|
|
|
|
noFocusTexture: string - filename for no focus texture.
|
|
|
|
textOffsetX: integer - x offset of label.
|
|
|
|
textOffsetY: integer - y offset of label.
|
|
|
|
_alignment: integer - alignment of label - *Note, see xbfont.h
|
|
|
|
font: string - font used for label text. (e.g. 'font13')
|
|
|
|
textColor: hexstring - color of enabled radio button's label. (e.g. '0xFFFFFFFF')
|
|
|
|
disabledColor: hexstring - color of disabled radio button's label. (e.g. '0xFFFF3300')
|
|
|
|
angle: integer - angle of control. (+ rotates CCW, - rotates CW)
|
|
|
|
shadowColor: hexstring - color of radio button's label's shadow. (e.g. '0xFF000000')
|
|
|
|
focusedColor: hexstring - color of focused radio button's label. (e.g. '0xFF00FFFF')
|
|
|
|
focusOnTexture: string - filename for radio focused/checked texture.
|
|
|
|
noFocusOnTexture: string - filename for radio not focused/checked texture.
|
|
|
|
focusOffTexture: string - filename for radio focused/unchecked texture.
|
|
|
|
noFocusOffTexture: string - filename for radio not focused/unchecked texture.
|
|
|
|
Note: To customize RadioButton all 4 abovementioned textures need to be provided.
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.radiobutton = RadioButton('Status', font='font14')
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
if int(xbmc.getInfoLabel('System.BuildVersion')[:2]) >= 13:
|
|
|
|
textures = {'focusTexture': os.path.join(_images, 'RadioButton', 'MenuItemFO.png'),
|
|
|
|
'noFocusTexture': os.path.join(_images, 'RadioButton', 'MenuItemNF.png'),
|
|
|
|
'focusOnTexture': os.path.join(_images, 'RadioButton', 'radiobutton-focus.png'),
|
|
|
|
'noFocusOnTexture': os.path.join(_images, 'RadioButton', 'radiobutton-focus.png'),
|
|
|
|
'focusOffTexture': os.path.join(_images, 'RadioButton', 'radiobutton-nofocus.png'),
|
|
|
|
'noFocusOffTexture': os.path.join(_images, 'RadioButton', 'radiobutton-nofocus.png')}
|
2015-06-23 23:00:27 +03:00
|
|
|
else: # This is for compatibility with Frodo and earlier versions.
|
2015-01-09 14:11:21 +03:00
|
|
|
textures = {'focusTexture': os.path.join(_images, 'RadioButton', 'MenuItemFO.png'),
|
|
|
|
'noFocusTexture': os.path.join(_images, 'RadioButton', 'MenuItemNF.png'),
|
|
|
|
'TextureRadioFocus': os.path.join(_images, 'RadioButton', 'radiobutton-focus.png'),
|
|
|
|
'TextureRadioNoFocus': os.path.join(_images, 'RadioButton', 'radiobutton-nofocus.png')}
|
|
|
|
_set_textures(textures, kwargs)
|
|
|
|
return super(RadioButton, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class Edit(xbmcgui.ControlEdit):
|
|
|
|
"""
|
|
|
|
ControlEdit class.
|
|
|
|
|
|
|
|
Edit(label[, font, textColor, disabledColor, alignment, focusTexture, noFocusTexture])
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
label : string or unicode - text string.
|
|
|
|
font : [opt] string - font used for label text. (e.g. 'font13')
|
|
|
|
textColor : [opt] hexstring - color of enabled label's label. (e.g. '0xFFFFFFFF')
|
|
|
|
disabledColor : [opt] hexstring - color of disabled label's label. (e.g. '0xFFFF3300')
|
|
|
|
_alignment : [opt] integer - alignment of label - *Note, see xbfont.h
|
|
|
|
focusTexture : [opt] string - filename for focus texture.
|
|
|
|
noFocusTexture : [opt] string - filename for no focus texture.
|
|
|
|
isPassword : [opt] bool - if true, mask text value.
|
|
|
|
|
|
|
|
*Note, You can use the above as keywords for arguments and skip certain optional arguments.
|
|
|
|
Once you use a keyword, all following arguments require the keyword.
|
|
|
|
After you create the control, you need to add it to the window with palceControl().
|
|
|
|
|
|
|
|
example:
|
|
|
|
- self.edit = Edit('Status')
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
textures = {'focusTexture': os.path.join(_images, 'Edit', 'button-focus.png'),
|
|
|
|
'noFocusTexture': os.path.join(_images, 'Edit', 'black-back2.png')}
|
|
|
|
_set_textures(textures, kwargs)
|
|
|
|
return super(Edit, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class List(xbmcgui.ControlList):
|
|
|
|
"""ControlList class.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
font: string - font used for items label. (e.g. 'font13')
|
|
|
|
textColor: hexstring - color of items label. (e.g. '0xFFFFFFFF')
|
|
|
|
buttonTexture: string - filename for no focus texture.
|
|
|
|
buttonFocusTexture: string - filename for focus texture.
|
|
|
|
selectedColor: integer - x offset of label.
|
|
|
|
_imageWidth: integer - width of items icon or thumbnail.
|
|
|
|
_imageHeight: integer - height of items icon or thumbnail.
|
|
|
|
_itemTextXOffset: integer - x offset of items label.
|
|
|
|
_itemTextYOffset: integer - y offset of items label.
|
|
|
|
_itemHeight: integer - height of items.
|
|
|
|
_space: integer - space between items.
|
|
|
|
_alignmentY: integer - Y-axis alignment of items label - *Note, see xbfont.h
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.cList = List('font14', space=5)
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
textures = {'buttonTexture': os.path.join(_images, 'List', 'MenuItemNF.png'),
|
|
|
|
'buttonFocusTexture': os.path.join(_images, 'List', 'MenuItemFO.png')}
|
|
|
|
_set_textures(textures, kwargs)
|
|
|
|
return super(List, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class Slider(xbmcgui.ControlSlider):
|
|
|
|
"""ControlSlider class.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
textureback: string - image filename.
|
|
|
|
texture: string - image filename.
|
|
|
|
texturefocus: string - image filename.
|
|
|
|
|
|
|
|
Note:
|
|
|
|
After you create the control, you need to add it to the window with placeControl().
|
|
|
|
|
|
|
|
Example:
|
|
|
|
self.slider = Slider()
|
|
|
|
"""
|
2015-06-23 23:00:27 +03:00
|
|
|
|
2015-01-09 14:11:21 +03:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
textures = {'textureback': os.path.join(_images, 'Slider', 'osd_slider_bg.png'),
|
|
|
|
'texture': os.path.join(_images, 'Slider', 'osd_slider_nibNF.png'),
|
|
|
|
'texturefocus': os.path.join(_images, 'Slider', 'osd_slider_nib.png')}
|
|
|
|
_set_textures(textures, kwargs)
|
|
|
|
return super(Slider, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class _AbstractWindow(object):
|
|
|
|
"""
|
|
|
|
Top-level control window.
|
|
|
|
|
|
|
|
The control windows serves as a parent widget for other XBMC UI controls
|
|
|
|
much like Tkinter.Tk or PyQt QWidget class.
|
|
|
|
This is an abstract class which is not supposed to be instantiated directly
|
|
|
|
and will raise exeptions.
|
|
|
|
|
|
|
|
This class is a basic "skeleton" for a control window.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
"""Constructor method."""
|
|
|
|
self.actions_connected = []
|
|
|
|
self.controls_connected = []
|
|
|
|
|
|
|
|
def setGeometry(self, width_, height_, rows_, columns_, pos_x=-1, pos_y=-1):
|
|
|
|
"""
|
|
|
|
Set width, height, Grid layout, and coordinates (optional) for a new control window.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
width_, height_: widgh and height of the created window.
|
|
|
|
rows_, columns_: rows and colums of the Grid layout to place controls on.
|
|
|
|
pos_x, pos_y (optional): coordinates of the top left corner of the window.
|
|
|
|
If pos_x and pos_y are not privided, the window will be placed
|
|
|
|
at the center of the screen.
|
|
|
|
Example:
|
|
|
|
self.setGeometry(400, 500, 5, 4)
|
|
|
|
"""
|
|
|
|
self.width = width_
|
|
|
|
self.height = height_
|
|
|
|
self.rows = rows_
|
|
|
|
self.columns = columns_
|
|
|
|
if pos_x > 0 and pos_y > 0:
|
|
|
|
self.x = pos_x
|
|
|
|
self.y = pos_y
|
|
|
|
else:
|
2015-06-23 23:00:27 +03:00
|
|
|
self.x = 640 - self.width / 2
|
|
|
|
self.y = 360 - self.height / 2
|
2015-01-09 14:11:21 +03:00
|
|
|
self.setGrid()
|
|
|
|
|
|
|
|
def setGrid(self):
|
|
|
|
"""
|
|
|
|
Set window grid layout of rows * columns.
|
|
|
|
This is a helper method not to be called directly.
|
|
|
|
"""
|
|
|
|
self.grid_x = self.x
|
|
|
|
self.grid_y = self.y
|
2015-06-23 23:00:27 +03:00
|
|
|
self.tile_width = self.width / self.columns
|
|
|
|
self.tile_height = self.height / self.rows
|
2015-01-09 14:11:21 +03:00
|
|
|
|
|
|
|
def placeControl(self, control, row, column, rowspan=1, columnspan=1, pad_x=5, pad_y=5):
|
|
|
|
"""
|
|
|
|
Place a control within the window grid layout.
|
|
|
|
|
|
|
|
pad_x, pad_y: horisontal and vertical padding for control's
|
|
|
|
size and aspect adjustments. Negative values can be used
|
|
|
|
to make a control overlap with grid cells next to it, if necessary.
|
|
|
|
Raises AddonWindowError if a grid has not yet been set.
|
|
|
|
Example:
|
|
|
|
self.placeControl(self.label, 0, 1)
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
control_x = (self.grid_x + self.tile_width * column) + pad_x
|
|
|
|
control_y = (self.grid_y + self.tile_height * row) + pad_y
|
|
|
|
control_width = self.tile_width * columnspan - 2 * pad_x
|
|
|
|
control_height = self.tile_height * rowspan - 2 * pad_y
|
|
|
|
except AttributeError:
|
|
|
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
|
|
|
control.setPosition(control_x, control_y)
|
|
|
|
control.setWidth(control_width)
|
|
|
|
control.setHeight(control_height)
|
|
|
|
self.addControl(control)
|
|
|
|
self.setAnimation(control)
|
|
|
|
|
|
|
|
def getX(self):
|
|
|
|
"""Get X coordinate of the top-left corner of the window."""
|
|
|
|
try:
|
|
|
|
return self.x
|
|
|
|
except AttributeError:
|
|
|
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
|
|
|
|
|
|
|
def getY(self):
|
|
|
|
"""Get Y coordinate of the top-left corner of the window."""
|
|
|
|
try:
|
|
|
|
return self.y
|
|
|
|
except AttributeError:
|
|
|
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
|
|
|
|
|
|
|
def getWindowWidth(self):
|
|
|
|
"""Get window width."""
|
|
|
|
try:
|
|
|
|
return self.width
|
|
|
|
except AttributeError:
|
|
|
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
|
|
|
|
|
|
|
def getWindowHeight(self):
|
|
|
|
"""Get window height."""
|
|
|
|
try:
|
|
|
|
return self.height
|
|
|
|
except AttributeError:
|
|
|
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
|
|
|
|
|
|
|
def getRows(self):
|
|
|
|
"""
|
|
|
|
Get grid rows count.
|
|
|
|
Raises AddonWindowError if a grid has not yet been set.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return self.rows
|
|
|
|
except AttributeError:
|
|
|
|
raise AddonWindowError('Grid layot is not set! Call setGeometry first.')
|
|
|
|
|
|
|
|
def getColumns(self):
|
|
|
|
"""
|
|
|
|
Get grid columns count.
|
|
|
|
Raises AddonWindowError if a grid has not yet been set.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return self.columns
|
|
|
|
except AttributeError:
|
|
|
|
raise AddonWindowError('Grid layout is not set! Call setGeometry first.')
|
|
|
|
|
|
|
|
def connect(self, event, function):
|
|
|
|
"""
|
|
|
|
Connect an event to a function.
|
|
|
|
|
|
|
|
An event can be an inctance of a Control object or an integer key action code.
|
|
|
|
Several basic key action codes are provided by PyXBMCT. More action codes can be found at
|
|
|
|
https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h
|
|
|
|
|
|
|
|
You can connect the following Controls: Button, RadioButton and List. Other Controls do not
|
|
|
|
generate any control events when activated so their connections won't work.
|
|
|
|
To catch Slider events you need to connect the following key actions:
|
|
|
|
ACTION_MOVE_LEFT, ACTION_MOVE_RIGHT and ACTION_MOUSE_DRAG, and do a check
|
|
|
|
whether the Slider is focused.
|
|
|
|
|
|
|
|
"function" parameter is a function or a method to be executed. Note that you must provide
|
|
|
|
a function object [without brackets ()], not a function call!
|
|
|
|
lambda can be used as a function to call another function or method with parameters.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
self.connect(self.exit_button, self.close)
|
|
|
|
or
|
|
|
|
self.connect(ACTION_NAV_BACK, self.close)
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
self.disconnect(event)
|
|
|
|
except AddonWindowError:
|
|
|
|
if type(event) == int:
|
|
|
|
self.actions_connected.append([event, function])
|
|
|
|
else:
|
|
|
|
self.controls_connected.append([event, function])
|
|
|
|
|
|
|
|
def connectEventList(self, events, function):
|
|
|
|
"""
|
|
|
|
Connect a list of controls/action codes to a function.
|
|
|
|
See connect docstring for more info.
|
|
|
|
"""
|
|
|
|
[self.connect(event, function) for event in events]
|
|
|
|
|
|
|
|
def disconnect(self, event):
|
|
|
|
"""
|
|
|
|
Disconnect an event from a function.
|
|
|
|
|
|
|
|
An event can be an inctance of a Control object or an integer key action code
|
|
|
|
which has previously been connected to a function or a method.
|
|
|
|
Raises AddonWindowError if an event is not connected to any function.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
self.disconnect(self.exit_button)
|
|
|
|
or
|
|
|
|
self.disconnect(ACTION_NAV_BACK)
|
|
|
|
"""
|
|
|
|
if type(event) == int:
|
2015-06-23 23:00:27 +03:00
|
|
|
event_list = self.actions_connected
|
2015-01-09 14:11:21 +03:00
|
|
|
else:
|
2015-06-23 23:00:27 +03:00
|
|
|
event_list = self.controls_connected
|
2015-01-09 14:11:21 +03:00
|
|
|
for index in range(len(event_list)):
|
|
|
|
if event == event_list[index][0]:
|
|
|
|
event_list.pop(index)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise AddonWindowError('The action or control %s is not connected!' % event)
|
|
|
|
|
|
|
|
def disconnectEventList(self, events):
|
|
|
|
"""
|
|
|
|
Disconnect a list of controls/action codes from functions.
|
|
|
|
See disconnect docstring for more info.
|
|
|
|
Raises AddonWindowError if at least one event in the list
|
|
|
|
is not connected to any function.
|
|
|
|
"""
|
|
|
|
[self.disconnect(event) for event in events]
|
|
|
|
|
|
|
|
def executeConnected(self, event, connected_list):
|
|
|
|
"""
|
|
|
|
Execute a connected event (an action or a control).
|
|
|
|
This is a helper method not to be called directly.
|
|
|
|
"""
|
|
|
|
for item in connected_list:
|
|
|
|
if event == item[0]:
|
|
|
|
item[1]()
|
|
|
|
break
|
|
|
|
|
|
|
|
def setAnimation(self, control):
|
|
|
|
"""
|
|
|
|
This method is called to set animation properties for all controls
|
|
|
|
added to the current addon window instance - both built-in controls
|
|
|
|
(window background, title bar etc.) and controls added with placeControl().
|
|
|
|
It receives a control instance as the 2nd positional argument (besides self).
|
|
|
|
By default the method does nothing, i.e. no animation is set for controls.
|
|
|
|
To add animation you need to re-implement this menthod in your child class.
|
|
|
|
|
|
|
|
E.g:
|
|
|
|
def setAnimation(self, control):
|
|
|
|
control.setAnimations([('WindowOpen', 'effect=fade start=0 end=100 time=1000',),
|
|
|
|
('WindowClose', 'effect=fade start=100 end=0 time=1000',)])
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class _AddonWindow(_AbstractWindow):
|
|
|
|
"""
|
|
|
|
Top-level control window.
|
|
|
|
|
|
|
|
The control windows serves as a parent widget for other XBMC UI controls
|
|
|
|
much like Tkinter.Tk or PyQt QWidget class.
|
|
|
|
This is an abstract class which is not supposed to be instantiated directly
|
|
|
|
and will raise exeptions. It is designed to be implemented in a grand-child class
|
|
|
|
with the second inheritance from xbmcgui.Window or xbmcgui.WindowDialog
|
|
|
|
in a direct child class.
|
|
|
|
|
|
|
|
This class provides a control window with a background and a header
|
|
|
|
similar to top-level widgets of desktop UI frameworks.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, title=''):
|
|
|
|
"""Constructor method."""
|
|
|
|
super(_AddonWindow, self).__init__()
|
|
|
|
self.setFrame(title)
|
|
|
|
|
|
|
|
def setFrame(self, title):
|
|
|
|
"""
|
|
|
|
Define paths to images for window background and title background textures,
|
|
|
|
and set control position adjustment constants used in setGrid.
|
|
|
|
This is a helper method not to be called directly.
|
|
|
|
"""
|
|
|
|
# Window background image
|
|
|
|
self.background_img = os.path.join(_images, 'AddonWindow', 'ContentPanel.png')
|
|
|
|
# Background for a window header
|
|
|
|
self.title_background_img = os.path.join(_images, 'AddonWindow', 'dialogheader.png')
|
|
|
|
# Horisontal adjustment for a header background if the main background has transparent edges.
|
|
|
|
self.X_MARGIN = 5
|
|
|
|
# Vertical adjustment for a header background if the main background has transparent edges
|
|
|
|
self.Y_MARGIN = 5
|
|
|
|
# Header position adjustment if the main backround has visible borders.
|
|
|
|
self.Y_SHIFT = 4
|
|
|
|
# The height of a window header (for the title background and the title label).
|
|
|
|
self.HEADER_HEIGHT = 35
|
|
|
|
self.background = xbmcgui.ControlImage(-10, -10, 1, 1, self.background_img)
|
|
|
|
self.addControl(self.background)
|
|
|
|
self.setAnimation(self.background)
|
|
|
|
self.title_background = xbmcgui.ControlImage(-10, -10, 1, 1, self.title_background_img)
|
|
|
|
self.addControl(self.title_background)
|
|
|
|
self.setAnimation(self.title_background)
|
|
|
|
self.title_bar = xbmcgui.ControlLabel(-10, -10, 1, 1, title, alignment=ALIGN_CENTER, textColor='0xFFFFA500',
|
2015-06-23 23:00:27 +03:00
|
|
|
font='font13_title')
|
2015-01-09 14:11:21 +03:00
|
|
|
self.addControl(self.title_bar)
|
|
|
|
self.setAnimation(self.title_bar)
|
|
|
|
self.window_close_button = xbmcgui.ControlButton(-100, -100, 60, 30, '',
|
2015-06-23 23:00:27 +03:00
|
|
|
focusTexture=os.path.join(_images, 'AddonWindow',
|
|
|
|
'DialogCloseButton-focus.png'),
|
|
|
|
noFocusTexture=os.path.join(_images, 'AddonWindow',
|
|
|
|
'DialogCloseButton.png'))
|
2015-01-09 14:11:21 +03:00
|
|
|
self.addControl(self.window_close_button)
|
|
|
|
self.setAnimation(self.window_close_button)
|
|
|
|
|
|
|
|
def setGeometry(self, width_, height_, rows_, columns_, pos_x=-1, pos_y=-1, padding=5):
|
|
|
|
"""
|
|
|
|
Set width, height, Grid layout, and coordinates (optional) for a new control window.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
width_, height_: widgh and height of the created window.
|
|
|
|
rows_, columns_: rows and colums of the Grid layout to place controls on.
|
|
|
|
pos_x, pos_y (optional): coordinates of the top left corner of the window.
|
|
|
|
If pos_x and pos_y are not privided, the window will be placed
|
|
|
|
at the center of the screen.
|
|
|
|
padding (optional): padding between outer edges of the window and
|
|
|
|
controls placed on it.
|
|
|
|
Example:
|
|
|
|
self.setGeometry(400, 500, 5, 4)
|
|
|
|
"""
|
|
|
|
self.win_padding = padding
|
|
|
|
super(_AddonWindow, self).setGeometry(width_, height_, rows_, columns_, pos_x, pos_y)
|
|
|
|
self.background.setPosition(self.x, self.y)
|
|
|
|
self.background.setWidth(self.width)
|
|
|
|
self.background.setHeight(self.height)
|
|
|
|
self.title_background.setPosition(self.x + self.X_MARGIN, self.y + self.Y_MARGIN + self.Y_SHIFT)
|
|
|
|
self.title_background.setWidth(self.width - 2 * self.X_MARGIN)
|
|
|
|
self.title_background.setHeight(self.HEADER_HEIGHT)
|
|
|
|
self.title_bar.setPosition(self.x + self.X_MARGIN, self.y + self.Y_MARGIN + self.Y_SHIFT)
|
|
|
|
self.title_bar.setWidth(self.width - 2 * self.X_MARGIN)
|
|
|
|
self.title_bar.setHeight(self.HEADER_HEIGHT)
|
|
|
|
self.window_close_button.setPosition(self.x + self.width - 70, self.y + self.Y_MARGIN + self.Y_SHIFT)
|
|
|
|
|
|
|
|
def setGrid(self):
|
|
|
|
"""
|
|
|
|
Set window grid layout of rows * columns.
|
|
|
|
This is a helper method not to be called directly.
|
|
|
|
"""
|
|
|
|
self.grid_x = self.x + self.X_MARGIN + self.win_padding
|
|
|
|
self.grid_y = self.y + self.Y_MARGIN + self.Y_SHIFT + self.HEADER_HEIGHT + self.win_padding
|
2015-06-23 23:00:27 +03:00
|
|
|
self.tile_width = (self.width - 2 * (self.X_MARGIN + self.win_padding)) / self.columns
|
2015-01-09 14:11:21 +03:00
|
|
|
self.tile_height = (
|
2015-06-23 23:00:27 +03:00
|
|
|
self.height - self.HEADER_HEIGHT - self.Y_SHIFT - 2 * (
|
|
|
|
self.Y_MARGIN + self.win_padding)) / self.rows
|
2015-01-09 14:11:21 +03:00
|
|
|
|
|
|
|
def setWindowTitle(self, title=''):
|
|
|
|
"""
|
|
|
|
Set window title.
|
|
|
|
This method must be called AFTER (!!!) setGeometry(),
|
|
|
|
otherwise there is some werid bug with all skin text labels set to the 'title' text.
|
|
|
|
Example:
|
|
|
|
self.setWindowTitle('My Cool Addon')
|
|
|
|
"""
|
|
|
|
self.title_bar.setLabel(title)
|
|
|
|
|
|
|
|
def getWindowTitle(self):
|
|
|
|
"""Get window title."""
|
|
|
|
return self.title_bar.getLabel()
|
|
|
|
|
|
|
|
|
2015-06-23 23:00:27 +03:00
|
|
|
class _FullWindow(xbmcgui.Window):
|
2015-01-09 14:11:21 +03:00
|
|
|
"""An abstract class to define window event processing."""
|
|
|
|
|
|
|
|
def onAction(self, action):
|
|
|
|
"""
|
|
|
|
Catch button actions.
|
|
|
|
Note that, despite being compared to an integer,
|
|
|
|
action is an instance of xbmcgui.Action class.
|
|
|
|
"""
|
|
|
|
if action == ACTION_PREVIOUS_MENU:
|
|
|
|
self.close()
|
|
|
|
else:
|
|
|
|
self.executeConnected(action, self.actions_connected)
|
|
|
|
|
|
|
|
def onControl(self, control):
|
|
|
|
"""
|
|
|
|
Catch activated controls.
|
|
|
|
Control is an instance of xbmcgui.Control class.
|
|
|
|
"""
|
|
|
|
if control == self.window_close_button:
|
|
|
|
self.close()
|
|
|
|
else:
|
|
|
|
self.executeConnected(control, self.controls_connected)
|
|
|
|
|
|
|
|
|
|
|
|
class _DialogWindow(xbmcgui.WindowDialog):
|
|
|
|
"""An abstract class to define window event processing."""
|
|
|
|
|
|
|
|
def onAction(self, action):
|
|
|
|
"""
|
|
|
|
Catch button actions.
|
|
|
|
Note that, despite being compared to an integer,
|
|
|
|
action is an instance of xbmcgui.Action class.
|
|
|
|
"""
|
|
|
|
if action == ACTION_PREVIOUS_MENU:
|
|
|
|
self.close()
|
|
|
|
else:
|
|
|
|
self.executeConnected(action, self.actions_connected)
|
|
|
|
|
|
|
|
def onControl(self, control):
|
|
|
|
"""
|
|
|
|
Catch activated controls.
|
|
|
|
Control is an instance of xbmcgui.Control class.
|
|
|
|
"""
|
|
|
|
if control == self.window_close_button:
|
|
|
|
self.close()
|
|
|
|
else:
|
|
|
|
self.executeConnected(control, self.controls_connected)
|
|
|
|
|
|
|
|
|
|
|
|
class BlankFullWindow(_FullWindow, _AbstractWindow):
|
|
|
|
"""
|
|
|
|
Addon UI container with a solid background.
|
|
|
|
This is a blank window with a black background and without any elements whatsoever.
|
|
|
|
The decoration and layout are completely up to an addon developer.
|
|
|
|
The window controls can hide under video or music visualization.
|
|
|
|
Window ID can be passed on class instantiation an agrument
|
|
|
|
but __init__ must have the 2nd fake argument, e.g:
|
|
|
|
|
|
|
|
def __init__(self, *args)
|
|
|
|
|
|
|
|
Minimal example:
|
|
|
|
|
|
|
|
addon = MyAddon('My Cool Addon')
|
|
|
|
addon.setGeometry(400, 300, 4, 3)
|
|
|
|
addon.doModal()
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BlankDialogWindow(_DialogWindow, _AbstractWindow):
|
|
|
|
"""
|
|
|
|
Addon UI container with a transparent background.
|
|
|
|
This is a blank window with a transparent background and without any elements whatsoever.
|
|
|
|
The decoration and layout are completely up to an addon developer.
|
|
|
|
The window controls are always displayed over video or music visualization.
|
|
|
|
Minimal example:
|
|
|
|
|
|
|
|
addon = MyAddon('My Cool Addon')
|
|
|
|
addon.setGeometry(400, 300, 4, 3)
|
|
|
|
addon.doModal()
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-06-23 23:00:27 +03:00
|
|
|
class AddonFullWindow(_FullWindow, _AddonWindow):
|
2015-01-09 14:11:21 +03:00
|
|
|
"""
|
|
|
|
Addon UI container with a solid background.
|
|
|
|
Control window is displayed on top of the main background image - self.main_bg.
|
|
|
|
Video and music visualization are displayed unhindered.
|
|
|
|
Window ID can be passed on class instantiation as the 2nd positional agrument
|
|
|
|
but __init__ must have the 3rd fake argument, e.g:
|
|
|
|
|
|
|
|
def __init__(self, title='', *args)
|
|
|
|
|
|
|
|
Minimal example:
|
|
|
|
|
|
|
|
addon = MyAddon('My Cool Addon')
|
|
|
|
addon.setGeometry(400, 300, 4, 3)
|
|
|
|
addon.doModal()
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __new__(cls, title='', *args, **kwargs):
|
|
|
|
return super(AddonFullWindow, cls).__new__(cls, *args, **kwargs)
|
|
|
|
|
|
|
|
def setFrame(self, title):
|
|
|
|
"""
|
|
|
|
Set the image for for the fullscreen background.
|
|
|
|
"""
|
|
|
|
# Image for the fullscreen background.
|
|
|
|
self.main_bg_img = os.path.join(_images, 'AddonWindow', 'SKINDEFAULT.jpg')
|
|
|
|
# Fullscreen background image control.
|
|
|
|
self.main_bg = xbmcgui.ControlImage(1, 1, 1280, 720, self.main_bg_img)
|
|
|
|
self.addControl(self.main_bg)
|
|
|
|
super(AddonFullWindow, self).setFrame(title)
|
|
|
|
|
|
|
|
def setBackground(self, image=''):
|
|
|
|
"""
|
|
|
|
Set the main bacground to an image file.
|
|
|
|
image: path to an image file as str.
|
|
|
|
Example:
|
|
|
|
self.setBackground('d:\images\bacground.png')
|
|
|
|
"""
|
|
|
|
self.main_bg.setImage(image)
|
|
|
|
|
|
|
|
|
|
|
|
class AddonDialogWindow(_DialogWindow, _AddonWindow):
|
|
|
|
"""
|
|
|
|
Addon UI container with a transparent background.
|
|
|
|
Control window is displayed on top of XBMC UI,
|
|
|
|
including video an music visualization!
|
|
|
|
Minimal example:
|
|
|
|
|
|
|
|
addon = MyAddon('My Cool Addon')
|
|
|
|
addon.setGeometry(400, 300, 4, 3)
|
|
|
|
addon.doModal()
|
|
|
|
"""
|
|
|
|
pass
|