2018-09-11 04:53:46 +10:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import absolute_import, division, unicode_literals
|
2020-12-19 03:10:20 +11:00
|
|
|
from builtins import zip
|
|
|
|
from builtins import str
|
|
|
|
from builtins import range
|
|
|
|
from builtins import object
|
2018-09-11 04:53:46 +10:00
|
|
|
import xbmc
|
|
|
|
import xbmcgui
|
|
|
|
import time
|
|
|
|
import threading
|
|
|
|
import traceback
|
|
|
|
|
2018-11-21 02:58:25 +11:00
|
|
|
from .. import app
|
|
|
|
|
2018-09-11 04:53:46 +10:00
|
|
|
MONITOR = None
|
|
|
|
|
|
|
|
|
2020-12-19 03:10:20 +11:00
|
|
|
class BaseFunctions(object):
|
2018-09-11 04:53:46 +10:00
|
|
|
xmlFile = ''
|
|
|
|
path = ''
|
|
|
|
theme = ''
|
|
|
|
res = '720p'
|
|
|
|
width = 1280
|
|
|
|
height = 720
|
|
|
|
|
|
|
|
usesGenerate = False
|
|
|
|
lastWinID = None
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.isOpen = True
|
|
|
|
|
|
|
|
def onWindowFocus(self):
|
|
|
|
# Not automatically called. Can be used by an external window manager
|
|
|
|
pass
|
|
|
|
|
|
|
|
def onClosed(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def open(cls, **kwargs):
|
|
|
|
window = cls(cls.xmlFile, cls.path, cls.theme, cls.res, **kwargs)
|
|
|
|
window.modal()
|
|
|
|
return window
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def create(cls, show=True, **kwargs):
|
|
|
|
window = cls(cls.xmlFile, cls.path, cls.theme, cls.res, **kwargs)
|
|
|
|
if show:
|
|
|
|
window.show()
|
|
|
|
window.isOpen = True
|
|
|
|
return window
|
|
|
|
|
|
|
|
def modal(self):
|
|
|
|
self.isOpen = True
|
|
|
|
self.doModal()
|
|
|
|
self.onClosed()
|
|
|
|
self.isOpen = False
|
|
|
|
|
|
|
|
def activate(self):
|
|
|
|
if not self._winID:
|
|
|
|
self._winID = xbmcgui.getCurrentWindowId()
|
|
|
|
xbmc.executebuiltin('ReplaceWindow({0})'.format(self._winID))
|
|
|
|
|
|
|
|
def mouseXTrans(self, val):
|
|
|
|
return int((val / self.getWidth()) * self.width)
|
|
|
|
|
|
|
|
def mouseYTrans(self, val):
|
|
|
|
return int((val / self.getHeight()) * self.height)
|
|
|
|
|
|
|
|
def closing(self):
|
|
|
|
return self._closing
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def generate(self):
|
|
|
|
return None
|
|
|
|
|
|
|
|
def setProperties(self, prop_list, val_list_or_val):
|
|
|
|
if isinstance(val_list_or_val, list) or isinstance(val_list_or_val, tuple):
|
|
|
|
val_list = val_list_or_val
|
|
|
|
else:
|
|
|
|
val_list = [val_list_or_val] * len(prop_list)
|
|
|
|
|
|
|
|
for prop, val in zip(prop_list, val_list):
|
|
|
|
self.setProperty(prop, val)
|
|
|
|
|
|
|
|
def propertyContext(self, prop, val='1'):
|
|
|
|
return WindowProperty(self, prop, val)
|
|
|
|
|
|
|
|
def setBoolProperty(self, key, boolean):
|
|
|
|
self.setProperty(key, boolean and '1' or '')
|
|
|
|
|
|
|
|
|
|
|
|
class BaseWindow(xbmcgui.WindowXML, BaseFunctions):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
BaseFunctions.__init__(self)
|
|
|
|
self._closing = False
|
|
|
|
self._winID = None
|
|
|
|
self.started = False
|
|
|
|
self.finishedInit = False
|
|
|
|
|
|
|
|
def onInit(self):
|
|
|
|
self._winID = xbmcgui.getCurrentWindowId()
|
|
|
|
BaseFunctions.lastWinID = self._winID
|
|
|
|
if self.started:
|
|
|
|
self.onReInit()
|
|
|
|
else:
|
|
|
|
self.started = True
|
|
|
|
self.onFirstInit()
|
|
|
|
self.finishedInit = True
|
|
|
|
|
|
|
|
def onFirstInit(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def onReInit(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def setProperty(self, key, value):
|
|
|
|
if self._closing:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self._winID:
|
|
|
|
self._winID = xbmcgui.getCurrentWindowId()
|
|
|
|
|
|
|
|
try:
|
|
|
|
xbmcgui.Window(self._winID).setProperty(key, value)
|
|
|
|
xbmcgui.WindowXML.setProperty(self, key, value)
|
|
|
|
except RuntimeError:
|
|
|
|
xbmc.log('kodigui.BaseWindow.setProperty: Missing window', xbmc.LOGDEBUG)
|
|
|
|
|
|
|
|
def doClose(self):
|
|
|
|
if not self.isOpen:
|
|
|
|
return
|
|
|
|
self._closing = True
|
|
|
|
self.isOpen = False
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
def show(self):
|
|
|
|
self._closing = False
|
|
|
|
self.isOpen = True
|
|
|
|
xbmcgui.WindowXML.show(self)
|
|
|
|
|
|
|
|
def onClosed(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BaseDialog(xbmcgui.WindowXMLDialog, BaseFunctions):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
BaseFunctions.__init__(self)
|
|
|
|
self._closing = False
|
|
|
|
self._winID = ''
|
|
|
|
self.started = False
|
|
|
|
|
|
|
|
def onInit(self):
|
|
|
|
self._winID = xbmcgui.getCurrentWindowDialogId()
|
|
|
|
BaseFunctions.lastWinID = self._winID
|
|
|
|
if self.started:
|
|
|
|
self.onReInit()
|
|
|
|
else:
|
|
|
|
self.started = True
|
|
|
|
self.onFirstInit()
|
|
|
|
|
|
|
|
def onFirstInit(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def onReInit(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def setProperty(self, key, value):
|
|
|
|
if self._closing:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self._winID:
|
|
|
|
self._winID = xbmcgui.getCurrentWindowId()
|
|
|
|
|
|
|
|
try:
|
|
|
|
xbmcgui.Window(self._winID).setProperty(key, value)
|
|
|
|
xbmcgui.WindowXMLDialog.setProperty(self, key, value)
|
|
|
|
except RuntimeError:
|
|
|
|
xbmc.log('kodigui.BaseDialog.setProperty: Missing window', xbmc.LOGDEBUG)
|
|
|
|
|
|
|
|
def doClose(self):
|
|
|
|
self._closing = True
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
def show(self):
|
|
|
|
self._closing = False
|
|
|
|
xbmcgui.WindowXMLDialog.show(self)
|
|
|
|
|
|
|
|
def onClosed(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2020-12-19 03:10:20 +11:00
|
|
|
class ControlledBase(object):
|
2018-09-11 04:53:46 +10:00
|
|
|
def doModal(self):
|
|
|
|
self.show()
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def wait(self):
|
|
|
|
while not self._closing and not MONITOR.waitForAbort(0.1):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self._closing = True
|
|
|
|
|
|
|
|
|
|
|
|
class ControlledWindow(ControlledBase, BaseWindow):
|
|
|
|
def onAction(self, action):
|
|
|
|
try:
|
|
|
|
if action in (xbmcgui.ACTION_PREVIOUS_MENU, xbmcgui.ACTION_NAV_BACK):
|
|
|
|
self.doClose()
|
|
|
|
return
|
2019-02-08 23:52:33 +11:00
|
|
|
except Exception:
|
2018-09-11 04:53:46 +10:00
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
BaseWindow.onAction(self, action)
|
|
|
|
|
|
|
|
|
|
|
|
class ControlledDialog(ControlledBase, BaseDialog):
|
|
|
|
def onAction(self, action):
|
|
|
|
try:
|
|
|
|
if action in (xbmcgui.ACTION_PREVIOUS_MENU, xbmcgui.ACTION_NAV_BACK):
|
|
|
|
self.doClose()
|
|
|
|
return
|
2019-02-08 23:52:33 +11:00
|
|
|
except Exception:
|
2018-09-11 04:53:46 +10:00
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
BaseDialog.onAction(self, action)
|
|
|
|
|
|
|
|
|
|
|
|
DUMMY_LIST_ITEM = xbmcgui.ListItem()
|
|
|
|
|
|
|
|
|
|
|
|
class ManagedListItem(object):
|
2019-02-08 23:52:33 +11:00
|
|
|
def __init__(self, label='', label2='', iconImage='', thumbnailImage='',
|
|
|
|
path='', data_source=None, properties=None):
|
2018-09-11 04:53:46 +10:00
|
|
|
self._listItem = xbmcgui.ListItem(label, label2, iconImage, thumbnailImage, path)
|
|
|
|
self.dataSource = data_source
|
|
|
|
self.properties = {}
|
|
|
|
self.label = label
|
|
|
|
self.label2 = label2
|
|
|
|
self.iconImage = iconImage
|
|
|
|
self.thumbnailImage = thumbnailImage
|
|
|
|
self.path = path
|
|
|
|
self._ID = None
|
|
|
|
self._manager = None
|
|
|
|
self._valid = True
|
|
|
|
if properties:
|
2020-12-19 03:10:20 +11:00
|
|
|
for k, v in list(properties.items()):
|
2018-09-11 04:53:46 +10:00
|
|
|
self.setProperty(k, v)
|
|
|
|
|
2020-12-19 03:10:20 +11:00
|
|
|
def __bool__(self):
|
2018-09-11 04:53:46 +10:00
|
|
|
return self._valid
|
|
|
|
|
|
|
|
@property
|
|
|
|
def listItem(self):
|
|
|
|
if not self._listItem:
|
|
|
|
if not self._manager:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
self._listItem = self._manager.getListItemFromManagedItem(self)
|
|
|
|
except RuntimeError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self._listItem
|
|
|
|
|
|
|
|
def invalidate(self):
|
|
|
|
self._valid = False
|
|
|
|
self._listItem = DUMMY_LIST_ITEM
|
|
|
|
|
|
|
|
def _takeListItem(self, manager, lid):
|
|
|
|
self._manager = manager
|
|
|
|
self._ID = lid
|
|
|
|
self._listItem.setProperty('__ID__', lid)
|
|
|
|
li = self._listItem
|
|
|
|
self._listItem = None
|
|
|
|
self._manager._properties.update(self.properties)
|
|
|
|
return li
|
|
|
|
|
|
|
|
def _updateListItem(self):
|
|
|
|
self.listItem.setProperty('__ID__', self._ID)
|
|
|
|
self.listItem.setLabel(self.label)
|
|
|
|
self.listItem.setLabel2(self.label2)
|
|
|
|
self.listItem.setIconImage(self.iconImage)
|
|
|
|
self.listItem.setThumbnailImage(self.thumbnailImage)
|
|
|
|
self.listItem.setPath(self.path)
|
2020-12-19 03:10:20 +11:00
|
|
|
for k in list(self._manager._properties.keys()):
|
2018-09-11 04:53:46 +10:00
|
|
|
self.listItem.setProperty(k, self.properties.get(k) or '')
|
|
|
|
|
|
|
|
def clear(self):
|
|
|
|
self.label = ''
|
|
|
|
self.label2 = ''
|
|
|
|
self.iconImage = ''
|
|
|
|
self.thumbnailImage = ''
|
|
|
|
self.path = ''
|
|
|
|
for k in self.properties:
|
|
|
|
self.properties[k] = ''
|
|
|
|
self._updateListItem()
|
|
|
|
|
|
|
|
def pos(self):
|
|
|
|
if not self._manager:
|
|
|
|
return None
|
|
|
|
return self._manager.getManagedItemPosition(self)
|
|
|
|
|
|
|
|
def addContextMenuItems(self, items, replaceItems=False):
|
|
|
|
self.listItem.addContextMenuItems(items, replaceItems)
|
|
|
|
|
|
|
|
def addStreamInfo(self, stype, values):
|
|
|
|
self.listItem.addStreamInfo(stype, values)
|
|
|
|
|
|
|
|
def getLabel(self):
|
|
|
|
return self.label
|
|
|
|
|
|
|
|
def getLabel2(self):
|
|
|
|
return self.label2
|
|
|
|
|
|
|
|
def getProperty(self, key):
|
|
|
|
return self.properties.get(key, '')
|
|
|
|
|
|
|
|
def getdescription(self):
|
|
|
|
return self.listItem.getdescription()
|
|
|
|
|
|
|
|
def getduration(self):
|
|
|
|
return self.listItem.getduration()
|
|
|
|
|
|
|
|
def getfilename(self):
|
|
|
|
return self.listItem.getfilename()
|
|
|
|
|
|
|
|
def isSelected(self):
|
|
|
|
return self.listItem.isSelected()
|
|
|
|
|
|
|
|
def select(self, selected):
|
|
|
|
return self.listItem.select(selected)
|
|
|
|
|
|
|
|
def setArt(self, values):
|
|
|
|
return self.listItem.setArt(values)
|
|
|
|
|
|
|
|
def setIconImage(self, icon):
|
|
|
|
self.iconImage = icon
|
|
|
|
return self.listItem.setIconImage(icon)
|
|
|
|
|
|
|
|
def setInfo(self, itype, infoLabels):
|
|
|
|
return self.listItem.setInfo(itype, infoLabels)
|
|
|
|
|
|
|
|
def setLabel(self, label):
|
|
|
|
self.label = label
|
|
|
|
return self.listItem.setLabel(label)
|
|
|
|
|
|
|
|
def setLabel2(self, label):
|
|
|
|
self.label2 = label
|
|
|
|
return self.listItem.setLabel2(label)
|
|
|
|
|
|
|
|
def setMimeType(self, mimetype):
|
|
|
|
return self.listItem.setMimeType(mimetype)
|
|
|
|
|
|
|
|
def setPath(self, path):
|
|
|
|
self.path = path
|
|
|
|
return self.listItem.setPath(path)
|
|
|
|
|
|
|
|
def setProperty(self, key, value):
|
|
|
|
if self._manager:
|
|
|
|
self._manager._properties[key] = 1
|
|
|
|
self.properties[key] = value
|
|
|
|
self.listItem.setProperty(key, value)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def setBoolProperty(self, key, boolean):
|
|
|
|
return self.setProperty(key, boolean and '1' or '')
|
|
|
|
|
|
|
|
def setSubtitles(self, subtitles):
|
|
|
|
return self.listItem.setSubtitles(subtitles) # List of strings - HELIX
|
|
|
|
|
|
|
|
def setThumbnailImage(self, thumb):
|
|
|
|
self.thumbnailImage = thumb
|
|
|
|
return self.listItem.setThumbnailImage(thumb)
|
|
|
|
|
|
|
|
def onDestroy(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ManagedControlList(object):
|
|
|
|
def __init__(self, window, control_id, max_view_index, data_source=None):
|
|
|
|
self.controlID = control_id
|
|
|
|
self.control = window.getControl(control_id)
|
|
|
|
self.items = []
|
|
|
|
self._sortKey = None
|
|
|
|
self._idCounter = 0
|
|
|
|
self._maxViewIndex = max_view_index
|
|
|
|
self._properties = {}
|
|
|
|
self.dataSource = data_source
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return getattr(self.control, name)
|
|
|
|
|
|
|
|
def __getitem__(self, idx):
|
|
|
|
if isinstance(idx, slice):
|
|
|
|
return self.items[idx]
|
|
|
|
else:
|
|
|
|
return self.getListItem(idx)
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
for i in self.items:
|
|
|
|
yield i
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return self.size()
|
|
|
|
|
|
|
|
def _updateItems(self, bottom=None, top=None):
|
|
|
|
if bottom is None:
|
|
|
|
bottom = 0
|
|
|
|
top = self.size()
|
|
|
|
|
|
|
|
try:
|
|
|
|
for idx in range(bottom, top):
|
|
|
|
li = self.control.getListItem(idx)
|
|
|
|
mli = self.items[idx]
|
|
|
|
self._properties.update(mli.properties)
|
|
|
|
mli._manager = self
|
|
|
|
mli._listItem = li
|
|
|
|
mli._updateListItem()
|
|
|
|
except RuntimeError:
|
|
|
|
xbmc.log('kodigui.ManagedControlList._updateItems: Runtime error', xbmc.LOGNOTICE)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _nextID(self):
|
|
|
|
self._idCounter += 1
|
|
|
|
return str(self._idCounter)
|
|
|
|
|
|
|
|
def reInit(self, window, control_id):
|
|
|
|
self.controlID = control_id
|
|
|
|
self.control = window.getControl(control_id)
|
|
|
|
self.control.addItems([i._takeListItem(self, self._nextID()) for i in self.items])
|
|
|
|
|
|
|
|
def setSort(self, sort):
|
|
|
|
self._sortKey = sort
|
|
|
|
|
|
|
|
def addItem(self, managed_item):
|
|
|
|
self.items.append(managed_item)
|
|
|
|
self.control.addItem(managed_item._takeListItem(self, self._nextID()))
|
|
|
|
|
|
|
|
def addItems(self, managed_items):
|
|
|
|
self.items += managed_items
|
|
|
|
self.control.addItems([i._takeListItem(self, self._nextID()) for i in managed_items])
|
|
|
|
|
|
|
|
def replaceItem(self, pos, mli):
|
|
|
|
self[pos].onDestroy()
|
|
|
|
self[pos].invalidate()
|
|
|
|
self.items[pos] = mli
|
|
|
|
li = self.control.getListItem(pos)
|
|
|
|
mli._manager = self
|
|
|
|
mli._listItem = li
|
|
|
|
mli._updateListItem()
|
|
|
|
|
|
|
|
def replaceItems(self, managed_items):
|
|
|
|
if not self.items:
|
|
|
|
self.addItems(managed_items)
|
|
|
|
return True
|
|
|
|
|
|
|
|
oldSize = self.size()
|
|
|
|
|
|
|
|
for i in self.items:
|
|
|
|
i.onDestroy()
|
|
|
|
i.invalidate()
|
|
|
|
|
|
|
|
self.items = managed_items
|
|
|
|
size = self.size()
|
|
|
|
if size != oldSize:
|
|
|
|
pos = self.getSelectedPosition()
|
|
|
|
|
|
|
|
if size > oldSize:
|
|
|
|
for i in range(0, size - oldSize):
|
|
|
|
self.control.addItem(xbmcgui.ListItem())
|
|
|
|
elif size < oldSize:
|
|
|
|
diff = oldSize - size
|
|
|
|
idx = oldSize - 1
|
|
|
|
while diff:
|
|
|
|
self.control.removeItem(idx)
|
|
|
|
idx -= 1
|
|
|
|
diff -= 1
|
|
|
|
|
|
|
|
if self.positionIsValid(pos):
|
|
|
|
self.selectItem(pos)
|
|
|
|
elif pos >= size:
|
|
|
|
self.selectItem(size - 1)
|
|
|
|
|
|
|
|
self._updateItems(0, self.size())
|
|
|
|
|
|
|
|
def getListItem(self, pos):
|
|
|
|
li = self.control.getListItem(pos)
|
|
|
|
mli = self.items[pos]
|
|
|
|
mli._listItem = li
|
|
|
|
return mli
|
|
|
|
|
|
|
|
def getListItemByDataSource(self, data_source):
|
|
|
|
for mli in self:
|
|
|
|
if data_source == mli.dataSource:
|
|
|
|
return mli
|
|
|
|
return None
|
|
|
|
|
|
|
|
def getSelectedItem(self):
|
|
|
|
pos = self.control.getSelectedPosition()
|
|
|
|
if not self.positionIsValid(pos):
|
|
|
|
pos = self.size() - 1
|
|
|
|
|
|
|
|
if pos < 0:
|
|
|
|
return None
|
|
|
|
return self.getListItem(pos)
|
|
|
|
|
|
|
|
def removeItem(self, index):
|
|
|
|
old = self.items.pop(index)
|
|
|
|
old.onDestroy()
|
|
|
|
old.invalidate()
|
|
|
|
|
|
|
|
self.control.removeItem(index)
|
|
|
|
top = self.control.size() - 1
|
|
|
|
if top < 0:
|
|
|
|
return
|
|
|
|
if top < index:
|
|
|
|
index = top
|
|
|
|
self.control.selectItem(index)
|
|
|
|
|
|
|
|
def removeManagedItem(self, mli):
|
|
|
|
self.removeItem(mli.pos())
|
|
|
|
|
|
|
|
def insertItem(self, index, managed_item):
|
|
|
|
pos = self.getSelectedPosition() + 1
|
|
|
|
|
|
|
|
if index >= self.size() or index < 0:
|
|
|
|
self.addItem(managed_item)
|
|
|
|
else:
|
|
|
|
self.items.insert(index, managed_item)
|
|
|
|
self.control.addItem(managed_item._takeListItem(self, self._nextID()))
|
|
|
|
self._updateItems(index, self.size())
|
|
|
|
|
|
|
|
if self.positionIsValid(pos):
|
|
|
|
self.selectItem(pos)
|
|
|
|
|
|
|
|
def moveItem(self, mli, dest_idx):
|
|
|
|
source_idx = mli.pos()
|
|
|
|
if source_idx < dest_idx:
|
|
|
|
rstart = source_idx
|
|
|
|
rend = dest_idx + 1
|
|
|
|
# dest_idx-=1
|
|
|
|
else:
|
|
|
|
rstart = dest_idx
|
|
|
|
rend = source_idx + 1
|
|
|
|
mli = self.items.pop(source_idx)
|
|
|
|
self.items.insert(dest_idx, mli)
|
|
|
|
|
|
|
|
self._updateItems(rstart, rend)
|
|
|
|
|
|
|
|
def swapItems(self, pos1, pos2):
|
|
|
|
if not self.positionIsValid(pos1) or not self.positionIsValid(pos2):
|
|
|
|
return False
|
|
|
|
|
|
|
|
item1 = self.items[pos1]
|
|
|
|
item2 = self.items[pos2]
|
|
|
|
li1 = item1._listItem
|
|
|
|
li2 = item2._listItem
|
|
|
|
item1._listItem = li2
|
|
|
|
item2._listItem = li1
|
|
|
|
|
|
|
|
item1._updateListItem()
|
|
|
|
item2._updateListItem()
|
|
|
|
self.items[pos1] = item2
|
|
|
|
self.items[pos2] = item1
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def shiftView(self, shift, hold_selected=False):
|
|
|
|
if not self._maxViewIndex:
|
|
|
|
return
|
|
|
|
selected = self.getSelectedItem()
|
|
|
|
selectedPos = selected.pos()
|
|
|
|
viewPos = self.getViewPosition()
|
|
|
|
|
|
|
|
if shift > 0:
|
|
|
|
pushPos = selectedPos + (self._maxViewIndex - viewPos) + shift
|
|
|
|
if pushPos >= self.size():
|
|
|
|
pushPos = self.size() - 1
|
|
|
|
self.selectItem(pushPos)
|
|
|
|
newViewPos = self._maxViewIndex
|
|
|
|
elif shift < 0:
|
|
|
|
pushPos = (selectedPos - viewPos) + shift
|
|
|
|
if pushPos < 0:
|
|
|
|
pushPos = 0
|
|
|
|
self.selectItem(pushPos)
|
|
|
|
newViewPos = 0
|
|
|
|
|
|
|
|
if hold_selected:
|
|
|
|
self.selectItem(selected.pos())
|
|
|
|
else:
|
|
|
|
diff = newViewPos - viewPos
|
|
|
|
fix = pushPos - diff
|
|
|
|
# print '{0} {1} {2}'.format(newViewPos, viewPos, fix)
|
|
|
|
if self.positionIsValid(fix):
|
|
|
|
self.selectItem(fix)
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
self.dataSource = None
|
|
|
|
for i in self.items:
|
|
|
|
i.onDestroy()
|
|
|
|
i.invalidate()
|
|
|
|
self.items = []
|
|
|
|
self.control.reset()
|
|
|
|
|
|
|
|
def size(self):
|
|
|
|
return len(self.items)
|
|
|
|
|
|
|
|
def getViewPosition(self):
|
|
|
|
try:
|
|
|
|
return int(xbmc.getInfoLabel('Container({0}).Position'.format(self.controlID)))
|
2019-02-08 23:52:33 +11:00
|
|
|
except Exception:
|
2018-09-11 04:53:46 +10:00
|
|
|
return 0
|
|
|
|
|
|
|
|
def getViewRange(self):
|
|
|
|
viewPosition = self.getViewPosition()
|
|
|
|
selected = self.getSelectedPosition()
|
2020-12-19 03:10:20 +11:00
|
|
|
return list(range(max(selected - viewPosition, 0),
|
2019-02-08 23:52:33 +11:00
|
|
|
min(selected + (self._maxViewIndex - viewPosition) + 1,
|
2020-12-19 03:10:20 +11:00
|
|
|
self.size() - 1)))
|
2018-09-11 04:53:46 +10:00
|
|
|
|
|
|
|
def positionIsValid(self, pos):
|
|
|
|
return 0 <= pos < self.size()
|
|
|
|
|
|
|
|
def sort(self, sort=None, reverse=False):
|
|
|
|
sort = sort or self._sortKey
|
|
|
|
|
|
|
|
self.items.sort(key=sort, reverse=reverse)
|
|
|
|
|
|
|
|
self._updateItems(0, self.size())
|
|
|
|
|
|
|
|
def reverse(self):
|
|
|
|
self.items.reverse()
|
|
|
|
self._updateItems(0, self.size())
|
|
|
|
|
|
|
|
def getManagedItemPosition(self, mli):
|
|
|
|
return self.items.index(mli)
|
|
|
|
|
|
|
|
def getListItemFromManagedItem(self, mli):
|
|
|
|
pos = self.items.index(mli)
|
|
|
|
return self.control.getListItem(pos)
|
|
|
|
|
|
|
|
def topHasFocus(self):
|
|
|
|
return self.getSelectedPosition() == 0
|
|
|
|
|
|
|
|
def bottomHasFocus(self):
|
|
|
|
return self.getSelectedPosition() == self.size() - 1
|
|
|
|
|
|
|
|
def invalidate(self):
|
|
|
|
for item in self.items:
|
|
|
|
item._listItem = DUMMY_LIST_ITEM
|
|
|
|
|
|
|
|
def newControl(self, window=None, control_id=None):
|
|
|
|
self.controlID = control_id or self.controlID
|
|
|
|
self.control = window.getControl(self.controlID)
|
|
|
|
self.control.addItems([xbmcgui.ListItem() for i in range(self.size())])
|
|
|
|
self._updateItems()
|
|
|
|
|
|
|
|
|
|
|
|
class _MWBackground(ControlledWindow):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self._multiWindow = kwargs.get('multi_window')
|
|
|
|
self.started = False
|
|
|
|
BaseWindow.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def onInit(self):
|
|
|
|
if self.started:
|
|
|
|
return
|
|
|
|
self.started = True
|
|
|
|
self._multiWindow._open()
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
|
|
|
|
class MultiWindow(object):
|
|
|
|
def __init__(self, windows=None, default_window=None, **kwargs):
|
|
|
|
self._windows = windows
|
|
|
|
self._next = default_window or self._windows[0]
|
|
|
|
self._properties = {}
|
|
|
|
self._current = None
|
|
|
|
self._allClosed = False
|
|
|
|
self.exitCommand = None
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return getattr(self._current, name)
|
|
|
|
|
|
|
|
def setWindows(self, windows):
|
|
|
|
self._windows = windows
|
|
|
|
|
|
|
|
def setDefault(self, default):
|
|
|
|
self._next = default or self._windows[0]
|
|
|
|
|
|
|
|
def windowIndex(self, window):
|
|
|
|
if hasattr(window, 'MULTI_WINDOW_ID'):
|
|
|
|
for i, w in enumerate(self._windows):
|
|
|
|
if window.MULTI_WINDOW_ID == w.MULTI_WINDOW_ID:
|
|
|
|
return i
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return self._windows.index(window.__class__)
|
|
|
|
|
|
|
|
def nextWindow(self, window=None):
|
|
|
|
if window is False:
|
|
|
|
window = self._windows[self.windowIndex(self._current)]
|
|
|
|
|
|
|
|
if window:
|
|
|
|
if window.__class__ == self._current.__class__:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
idx = self.windowIndex(self._current)
|
|
|
|
idx += 1
|
|
|
|
if idx >= len(self._windows):
|
|
|
|
idx = 0
|
|
|
|
window = self._windows[idx]
|
|
|
|
|
|
|
|
self._next = window
|
|
|
|
self._current.doClose()
|
|
|
|
return self._next
|
|
|
|
|
|
|
|
def _setupCurrent(self, cls):
|
|
|
|
self._current = cls(cls.xmlFile, cls.path, cls.theme, cls.res)
|
|
|
|
self._current.onFirstInit = self._onFirstInit
|
|
|
|
self._current.onReInit = self.onReInit
|
|
|
|
self._current.onClick = self.onClick
|
|
|
|
self._current.onFocus = self.onFocus
|
|
|
|
|
|
|
|
self._currentOnAction = self._current.onAction
|
|
|
|
self._current.onAction = self.onAction
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def open(cls, **kwargs):
|
|
|
|
mw = cls(**kwargs)
|
|
|
|
b = _MWBackground(mw.bgXML, mw.path, mw.theme, mw.res, multi_window=mw)
|
|
|
|
b.modal()
|
|
|
|
del b
|
|
|
|
import gc
|
|
|
|
gc.collect(2)
|
|
|
|
return mw
|
|
|
|
|
|
|
|
def _open(self):
|
2020-05-06 02:18:34 +10:00
|
|
|
while not xbmc.Monitor().abortRequested() and not self._allClosed:
|
2018-09-11 04:53:46 +10:00
|
|
|
self._setupCurrent(self._next)
|
|
|
|
self._current.modal()
|
|
|
|
|
|
|
|
self._current.doClose()
|
|
|
|
del self._current
|
|
|
|
del self._next
|
|
|
|
del self._currentOnAction
|
|
|
|
|
|
|
|
def setProperty(self, key, value):
|
|
|
|
self._properties[key] = value
|
|
|
|
self._current.setProperty(key, value)
|
|
|
|
|
|
|
|
def _onFirstInit(self):
|
2020-12-19 03:10:20 +11:00
|
|
|
for k, v in list(self._properties.items()):
|
2018-09-11 04:53:46 +10:00
|
|
|
self._current.setProperty(k, v)
|
|
|
|
self.onFirstInit()
|
|
|
|
|
|
|
|
def doClose(self):
|
|
|
|
self._allClosed = True
|
|
|
|
self._current.doClose()
|
|
|
|
|
|
|
|
def onFirstInit(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def onReInit(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def onAction(self, action):
|
|
|
|
if action == xbmcgui.ACTION_PREVIOUS_MENU or action == xbmcgui.ACTION_NAV_BACK:
|
|
|
|
self.doClose()
|
|
|
|
self._currentOnAction(action)
|
|
|
|
|
|
|
|
def onClick(self, controlID):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def onFocus(self, controlID):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class SafeControlEdit(object):
|
|
|
|
CHARS_LOWER = 'abcdefghijklmnopqrstuvwxyz'
|
|
|
|
CHARS_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
|
|
CHARS_NUMBERS = '0123456789'
|
|
|
|
CURSOR = '[COLOR FFCC7B19]|[/COLOR]'
|
|
|
|
|
|
|
|
def __init__(self, control_id, label_id, window, key_callback=None, grab_focus=False):
|
|
|
|
self.controlID = control_id
|
|
|
|
self.labelID = label_id
|
|
|
|
self._win = window
|
|
|
|
self._keyCallback = key_callback
|
|
|
|
self.grabFocus = grab_focus
|
|
|
|
self._text = ''
|
|
|
|
self._compatibleMode = False
|
|
|
|
self.setup()
|
|
|
|
|
|
|
|
def setup(self):
|
|
|
|
self._labelControl = self._win.getControl(self.labelID)
|
|
|
|
self._winOnAction = self._win.onAction
|
|
|
|
self._win.onAction = self.onAction
|
|
|
|
self.updateLabel()
|
|
|
|
|
|
|
|
def setCompatibleMode(self, on):
|
|
|
|
self._compatibleMode = on
|
|
|
|
|
|
|
|
def onAction(self, action):
|
|
|
|
try:
|
|
|
|
controlID = self._win.getFocusId()
|
|
|
|
if controlID == self.controlID:
|
|
|
|
if self.processAction(action.getId()):
|
|
|
|
return
|
|
|
|
elif self.grabFocus:
|
|
|
|
if self.processOffControlAction(action.getButtonCode()):
|
|
|
|
self._win.setFocusId(self.controlID)
|
|
|
|
return
|
2019-02-08 23:52:33 +11:00
|
|
|
except Exception:
|
2018-09-11 04:53:46 +10:00
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
self._winOnAction(action)
|
|
|
|
|
|
|
|
def processAction(self, action_id):
|
|
|
|
if not self._compatibleMode:
|
|
|
|
self._text = self._win.getControl(self.controlID).getText()
|
|
|
|
|
|
|
|
if self._keyCallback:
|
|
|
|
self._keyCallback()
|
|
|
|
|
|
|
|
self. updateLabel()
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
if 61793 <= action_id <= 61818: # Lowercase
|
|
|
|
self.processChar(self.CHARS_LOWER[action_id - 61793])
|
|
|
|
elif 61761 <= action_id <= 61786: # Uppercase
|
|
|
|
self.processChar(self.CHARS_UPPER[action_id - 61761])
|
|
|
|
elif 61744 <= action_id <= 61753:
|
|
|
|
self.processChar(self.CHARS_NUMBERS[action_id - 61744])
|
|
|
|
elif action_id == 61728: # Space
|
|
|
|
self.processChar(' ')
|
|
|
|
elif action_id == 61448:
|
|
|
|
self.delete()
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self._keyCallback:
|
|
|
|
self._keyCallback()
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def processOffControlAction(self, action_id):
|
|
|
|
if 61505 <= action_id <= 61530: # Lowercase
|
|
|
|
self.processChar(self.CHARS_LOWER[action_id - 61505])
|
|
|
|
elif 192577 <= action_id <= 192602: # Uppercase
|
|
|
|
self.processChar(self.CHARS_UPPER[action_id - 192577])
|
|
|
|
elif 61488 <= action_id <= 61497:
|
|
|
|
self.processChar(self.CHARS_NUMBERS[action_id - 61488])
|
|
|
|
elif 61552 <= action_id <= 61561:
|
|
|
|
self.processChar(self.CHARS_NUMBERS[action_id - 61552])
|
|
|
|
elif action_id == 61472: # Space
|
|
|
|
self.processChar(' ')
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self._keyCallback:
|
|
|
|
self._keyCallback()
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _setText(self, text):
|
|
|
|
self._text = text
|
|
|
|
|
|
|
|
if not self._compatibleMode:
|
|
|
|
self._win.getControl(self.controlID).setText(text)
|
|
|
|
self.updateLabel()
|
|
|
|
|
|
|
|
def _getText(self):
|
|
|
|
if not self._compatibleMode and self._win.getFocusId() == self.controlID:
|
|
|
|
return self._win.getControl(self.controlID).getText()
|
|
|
|
else:
|
|
|
|
return self._text
|
|
|
|
|
|
|
|
def updateLabel(self):
|
|
|
|
self._labelControl.setLabel(self._getText() + self.CURSOR)
|
|
|
|
|
|
|
|
def processChar(self, char):
|
|
|
|
self._setText(self.getText() + char)
|
|
|
|
|
|
|
|
def setText(self, text):
|
|
|
|
self._setText(text)
|
|
|
|
|
|
|
|
def getText(self):
|
|
|
|
return self._getText()
|
|
|
|
|
|
|
|
def append(self, text):
|
|
|
|
self._setText(self.getText() + text)
|
|
|
|
|
|
|
|
def delete(self):
|
|
|
|
self._setText(self.getText()[:-1])
|
|
|
|
|
|
|
|
|
2020-12-19 03:10:20 +11:00
|
|
|
class PropertyTimer(object):
|
2018-09-11 04:53:46 +10:00
|
|
|
def __init__(self, window_id, timeout, property_, value='', init_value='1', addon_id=None, callback=None):
|
|
|
|
self._winID = window_id
|
|
|
|
self._timeout = timeout
|
|
|
|
self._property = property_
|
|
|
|
self._value = value
|
|
|
|
self._initValue = init_value
|
|
|
|
self._endTime = 0
|
|
|
|
self._thread = None
|
|
|
|
self._addonID = addon_id
|
|
|
|
self._closeWin = None
|
|
|
|
self._closed = False
|
|
|
|
self._callback = callback
|
|
|
|
|
|
|
|
def _onTimeout(self):
|
|
|
|
self._endTime = 0
|
|
|
|
xbmcgui.Window(self._winID).setProperty(self._property, self._value)
|
|
|
|
if self._addonID:
|
|
|
|
xbmcgui.Window(10000).setProperty('{0}.{1}'.format(self._addonID, self._property), self._value)
|
|
|
|
if self._closeWin:
|
|
|
|
self._closeWin.doClose()
|
|
|
|
if self._callback:
|
|
|
|
self._callback()
|
|
|
|
|
|
|
|
def _wait(self):
|
2020-05-06 02:18:34 +10:00
|
|
|
while not xbmc.Monitor().abortRequested() and time.time() < self._endTime:
|
2018-11-21 02:58:25 +11:00
|
|
|
app.APP.monitor.waitForAbort(0.1)
|
2020-05-06 02:18:34 +10:00
|
|
|
if xbmc.Monitor().abortRequested():
|
2018-09-11 04:53:46 +10:00
|
|
|
return
|
|
|
|
if self._endTime == 0:
|
|
|
|
return
|
|
|
|
self._onTimeout()
|
|
|
|
|
|
|
|
def _stopped(self):
|
|
|
|
return not self._thread or not self._thread.isAlive()
|
|
|
|
|
|
|
|
def _reset(self):
|
|
|
|
self._endTime = time.time() + self._timeout
|
|
|
|
|
|
|
|
def _start(self):
|
|
|
|
self.init(self._initValue)
|
|
|
|
self._thread = threading.Thread(target=self._wait)
|
|
|
|
self._thread.start()
|
|
|
|
|
|
|
|
def stop(self, trigger=False):
|
|
|
|
self._endTime = trigger and 1 or 0
|
|
|
|
if not self._stopped():
|
|
|
|
self._thread.join()
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self._closed = True
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
def init(self, val):
|
|
|
|
if val is False:
|
|
|
|
return
|
|
|
|
elif val is None:
|
|
|
|
val = self._initValue
|
|
|
|
|
|
|
|
xbmcgui.Window(self._winID).setProperty(self._property, val)
|
|
|
|
if self._addonID:
|
|
|
|
xbmcgui.Window(10000).setProperty('{0}.{1}'.format(self._addonID, self._property), val)
|
|
|
|
|
|
|
|
def reset(self, close_win=None, init=None):
|
|
|
|
self.init(init)
|
|
|
|
|
|
|
|
if self._closed:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self._timeout:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._closeWin = close_win
|
|
|
|
self._reset()
|
|
|
|
|
|
|
|
if self._stopped:
|
|
|
|
self._start()
|
|
|
|
|
|
|
|
|
2020-12-19 03:10:20 +11:00
|
|
|
class WindowProperty(object):
|
2018-09-11 04:53:46 +10:00
|
|
|
def __init__(self, win, prop, val='1', end=None):
|
|
|
|
self.win = win
|
|
|
|
self.prop = prop
|
|
|
|
self.val = val
|
|
|
|
self.end = end
|
|
|
|
self.old = self.win.getProperty(self.prop)
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
self.win.setProperty(self.prop, self.val)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
2018-11-21 04:57:54 +11:00
|
|
|
if exc_type:
|
|
|
|
# re-raise any exception
|
|
|
|
return False
|
2018-09-11 04:53:46 +10:00
|
|
|
self.win.setProperty(self.prop, self.end or self.old)
|
|
|
|
|
|
|
|
|
2020-12-19 03:10:20 +11:00
|
|
|
class GlobalProperty(object):
|
2018-09-11 04:53:46 +10:00
|
|
|
def __init__(self, prop, val='1', end=None):
|
|
|
|
import xbmcaddon
|
|
|
|
self._addonID = xbmcaddon.Addon().getAddonInfo('id')
|
|
|
|
self.prop = prop
|
|
|
|
self.val = val
|
|
|
|
self.end = end
|
|
|
|
self.old = xbmc.getInfoLabel('Window(10000).Property({0}}.{1})'.format(self._addonID, prop))
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
xbmcgui.Window(10000).setProperty('{0}.{1}'.format(self._addonID, self.prop), self.val)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
2018-11-21 04:57:54 +11:00
|
|
|
if exc_type:
|
|
|
|
# re-raise any exception
|
|
|
|
return False
|
2018-09-11 04:53:46 +10:00
|
|
|
xbmcgui.Window(10000).setProperty('{0}.{1}'.format(self._addonID, self.prop), self.end or self.old)
|