diff options
Diffstat (limited to 'src/python/app/debugpanel.py')
-rw-r--r-- | src/python/app/debugpanel.py | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/src/python/app/debugpanel.py b/src/python/app/debugpanel.py new file mode 100644 index 0000000..c786785 --- /dev/null +++ b/src/python/app/debugpanel.py @@ -0,0 +1,697 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# libavg - Media Playback Engine. +# Copyright (C) 2003-2014 Ulrich von Zadow +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Current versions can be found at www.libavg.de +# +# Original authors of this file are +# OXullo Interecans <x at brainrapers dot org> +# Richard Klemm <richy at coding-reality.de> + +from collections import defaultdict +from collections import deque +import math + +import libavg +from libavg import avg +from touchvisualization import DebugTouchVisualization +from touchvisualization import TouchVisualizationOverlay as TouchVisOverlay + + +import keyboardmanager as kbmgr + +g_fontsize = 10 + +PANGO_ENTITIES_MAP = { + "&": "&", + '"': """, + "'": "'", + ">": ">", + "<": "<", +} + +def subscribe(publisher, msgID, callable_): + publisher.subscribe(msgID, callable_) + return lambda: publisher.unsubscribe(msgID, callable_) + + +class DebugWidgetFrame(avg.DivNode): + + BORDER = 7 + FRAME_HEIGHT_CHANGED = avg.Publisher.genMessageID() + + def __init__(self, size, widgetCls, *args, **kwargs): + super(DebugWidgetFrame, self).__init__(size=size, *args, **kwargs) + self.registerInstance(self, None) + self.setup(widgetCls) + self.subscribe(self.SIZE_CHANGED, self._onSizeChanged) + self.size = size + self._onSizeChanged(size) + + def setup(self, widgetCls): + self.__background = avg.RectNode(parent=self, opacity=0.8, + fillcolor='000000', fillopacity=0.8) + self.__widget = widgetCls(parent=self, + size=(max(0, self.width - self.BORDER * 2), 0), + pos=(self.BORDER, self.BORDER)) + self.__selectHighlight = avg.RectNode(parent=self, color="35C0CD", + strokewidth=self.BORDER, opacity=0.8, + pos=(self.BORDER / 2, self.BORDER / 2), active=False, sensitive=False) + self.__boundary = avg.RectNode(parent=self, sensitive=False) + + self.publish(DebugWidgetFrame.FRAME_HEIGHT_CHANGED) + + self.__widget.subscribe(self.__widget.WIDGET_HEIGHT_CHANGED, + self.adjustWidgetHeight) + self.__widget.update() + + def _onSizeChanged(self, size): + self.__boundary.size = size + self.__background.size = size + childSize = (max(0, size[0] - self.BORDER * 2), max(0, size[1] - self.BORDER * 2)) + self.__selectHighlight.size = (max(0, size[0] - self.BORDER), + max(0, size[1] - self.BORDER)) + self.__widget.size = childSize + self.__widget.syncSize(childSize) + + def adjustWidgetHeight(self, height): + self.size = (max(0, self.width), height + 2 * self.BORDER) + self.notifySubscribers(DebugWidgetFrame.FRAME_HEIGHT_CHANGED, []) + + def toggleSelect(self, event=None): + self.__selectHighlight.active = not(self.__selectHighlight.active) + + def isSelected(self): + return self.__selectHighlight.active + + def select(self): + self.__selectHighlight.active = True + + def unselect(self): + self.__selectHighlight.active = False + + def show(self): + self.active = True + self.__widget.onShow() + self.__widget.update() + + def hide(self): + self.active = False + self.__widget.onHide() + + @property + def widget(self): + return self.__widget + + +class DebugWidget(avg.DivNode): + SLOT_HEIGHT = 200 + CAPTION = '' + + WIDGET_HEIGHT_CHANGED = avg.Publisher.genMessageID() + + def __init__(self, parent=None, **kwargs): + super(DebugWidget, self).__init__(**kwargs) + self.registerInstance(self, parent) + self.publish(DebugWidget.WIDGET_HEIGHT_CHANGED) + if self.CAPTION: + self._caption = avg.WordsNode(text=self.CAPTION, pivot=(0, 0), + opacity=0.5, fontsize=14, parent=self) + self._caption.angle = math.pi / 2 + self._caption.pos = (self.width, 0) + + def syncSize(self, size): + self._caption.width = size[1] + + def update(self): + pass + + def onShow(self): + pass + + def onHide(self): + pass + + def kill(self): + pass + + +NUM_COLS = 10 +COL_WIDTH = 60 +ROW_HEIGHT = g_fontsize + 2 + + +class TableRow(avg.DivNode): + COL_POS_X = 0 + ROW_ID = 0 + + def __init__(self, parent=None, **kwargs): + super(TableRow, self).__init__(**kwargs) + self.registerInstance(self, parent) + global NUM_COLS + NUM_COLS = int((self.parent.width - COL_WIDTH * 4) / COL_WIDTH) + self._initRow() + TableRow.ROW_ID += 1 + + def _initRow(self): + self.columnBackground = avg.RectNode(parent=self, fillcolor="222222", + fillopacity=0.6, opacity=0) + self.columnContainer = avg.DivNode(parent=self) + if TableRow.ROW_ID % 2 != 0: + self.columnBackground.fillopacity = 0 + self.cols = [0] * NUM_COLS + self.liveColumn = avg.WordsNode(parent=self.columnContainer, fontsize=g_fontsize, + text="N/A - SPECIAL", size=(COL_WIDTH, ROW_HEIGHT), variant="bold") + for i in xrange(0, NUM_COLS): + self.cols[i] = (avg.WordsNode(parent=self.columnContainer, + fontsize=g_fontsize, + text="0", size=(COL_WIDTH / 2.0, ROW_HEIGHT), + pos=((i+1) * COL_WIDTH, 0)), + avg.WordsNode(parent=self.columnContainer, + fontsize=g_fontsize, + text="(0)", size=(COL_WIDTH / 2.0, ROW_HEIGHT), + pos=((i+1) * COL_WIDTH + COL_WIDTH / 2, 0), + color="000000")) + + self.rowData = deque([(0, 0)] * (NUM_COLS + 1), maxlen=NUM_COLS + 1) + self.label = avg.WordsNode(parent=self, fontsize=g_fontsize, variant="bold") + self.setLabel("NONE") + + @property + def height(self): + return self.label.height + + def setLabel(self, label): + if self.label.text == label + ":": + return + self.label.text = label + ":" + TableRow.COL_POS_X = max(TableRow.COL_POS_X, self.label.width) + if self.label.width < TableRow.COL_POS_X: + self.parent.labelColumnSizeChanged() + + def resizeLabelColumn(self): + self.columnContainer.pos = (TableRow.COL_POS_X + 10, 0) + self.columnBackground.size = (self.columnContainer.x + self.liveColumn.x + + self.liveColumn.width, g_fontsize) + + def insertValue(self, data): + prevValue = self.rowData[0][0] + self.rowData.appendleft([data, data-prevValue]) + for i in xrange(0, len(self.rowData)-1): + val, diff = self.rowData[i] + column = self.cols[i] + column[0].text = str(val) + column[1].text = "({diff})".format(diff=diff) + column[1].pos = (column[0].x + column[0].getLineExtents(0)[0] + 2, + column[0].y) + if diff == 0: + column[1].color = "000000" + elif diff < 0: + column[1].color = "00FF00" + else: + column[1].color = "FF0000" + + def updateLiveColumn(self, value): + self.liveColumn.text = str(value) + + +class Table(avg.DivNode): + def __init__(self, parent=None, **kwargs): + super(Table, self).__init__(**kwargs) + self.registerInstance(self, parent) + + def labelColumnSizeChanged(self): + for childID in xrange(0, self.getNumChildren()): + child = self.getChild(childID) + child.resizeLabelColumn() + + +class ObjectDumpWidget(DebugWidget): + CAPTION = 'Objects count' + + def __init__(self, parent=None, **kwargs): + super(ObjectDumpWidget, self).__init__(**kwargs) + self.registerInstance(self, parent) + self.tableContainer = Table(parent=self, size=(self.width, self.SLOT_HEIGHT)) + self.tableDivs = defaultdict(lambda: TableRow(parent=self.tableContainer)) + + def update(self): + objDump = libavg.player.getTestHelper().getObjectCount() + pos = (0, 0) + for key in sorted(objDump.iterkeys()): + val = objDump[key] + self.tableDivs[key].updateLiveColumn(val) + self.tableDivs[key].setLabel(key) + self.tableDivs[key].pos = pos + pos = (0, pos[1] + self.tableDivs[key].height) + height = len(objDump) * self.tableDivs[key].height + if self.height != height: + self.notifySubscribers(DebugWidget.WIDGET_HEIGHT_CHANGED, [height]) + + def persistColumn(self): + objDump = libavg.player.getTestHelper().getObjectCount() + for key, val in objDump.iteritems(): + self.tableDivs[key].insertValue(val) + + def syncSize(self, size): + self.tableContainer.size = (size[0], size[1] - (g_fontsize + 2)) + + def onShow(self): + self.intervalID = libavg.player.setInterval(1000, self.update) + kbmgr.bindKeyDown(keystring='i', + handler=self.persistColumn, + help="Object count snapshot", + modifiers=libavg.KEYMOD_CTRL) + + def onHide(self): + if self.intervalID: + libavg.player.clearInterval(self.intervalID) + self.intervalID = None + kbmgr.unbindKeyDown(keystring='i', modifiers=libavg.KEYMOD_CTRL) + + def kill(self): + self.onHide() + self.tableDivs = None + + +class GraphWidget(DebugWidget): + def __init__(self, **kwargs): + super(GraphWidget, self).__init__(**kwargs) + self.registerInstance(self, None) + self.__graph = None + + def onShow(self): + if self.__graph: + self.__graph.active = True + else: + self.__graph = self._createGraph() + + def onHide(self): + if self.__graph: + self.__graph.active = False + + def kill(self): + self.__graph.unlink(True) + + def _createGraph(self): + pass + + +class MemoryGraphWidget(GraphWidget): + CAPTION = 'Memory usage' + + def _createGraph(self): + return libavg.graph.AveragingGraph(parent=self, size=self.size, + getValue=avg.getMemoryUsage) + + +class FrametimeGraphWidget(GraphWidget): + CAPTION = 'Time per frame' + + def _createGraph(self): + return libavg.graph.SlidingBinnedGraph(parent=self, + getValue=libavg.player.getFrameTime, + binsThresholds=[0.0, 20.0, 40.0, 80.0, 160.0], + size=self.size) + + +class GPUMemoryGraphWidget(GraphWidget): + CAPTION = 'GPU Memory usage' + + def _createGraph(self): + try: + libavg.player.getVideoMemUsed() + except RuntimeError: + return avg.WordsNode(parent=self, + text='GPU memory graph is not supported on this hardware', + color='ff5555') + else: + return libavg.graph.AveragingGraph(parent=self, size=self.size, + getValue=libavg.player.getVideoMemUsed) + + +class KeyboardManagerBindingsShower(DebugWidget): + CAPTION = 'Keyboard bindings' + + def __init__(self, *args, **kwargs): + super(KeyboardManagerBindingsShower, self).__init__(**kwargs) + self.registerInstance(self, None) + self.keybindingWordNodes = [] + kbmgr.publisher.subscribe(kbmgr.publisher.BINDINGS_UPDATED, self.update) + + def clear(self): + for node in self.keybindingWordNodes: + node.unlink(True) + self.keybindingWordNodes = [] + + def update(self): + self.clear() + for binding in kbmgr.getCurrentBindings(): + keystring = binding.keystring.decode('utf8') + modifiersStr = self.__modifiersToString(binding.modifiers) + + if modifiersStr is not None: + key = '%s-%s' % (modifiersStr, keystring) + else: + key = keystring + + if binding.type == libavg.avg.KEYDOWN: + key = '%s %s' % (unichr(8595), key) + else: + key = '%s %s' % (unichr(8593), key) + + node = avg.WordsNode( + text='<span size="large"><b>%s</b></span>: %s' % + (key, binding.help), + fontsize=g_fontsize, parent=self) + self.keybindingWordNodes.append(node) + + self._placeNodes() + + def _placeNodes(self): + if not self.keybindingWordNodes: + return + + maxWidth = max([node.width for node in self.keybindingWordNodes]) + columns = int(self.parent.width / maxWidth) + rows = len(self.keybindingWordNodes) / columns + remainder = len(self.keybindingWordNodes) % columns + + if remainder != 0: + rows += 1 + + colSize = self.parent.width / columns + + currentColumn = 0 + currentRow = 0 + heights = [0] * columns + for node in self.keybindingWordNodes: + if currentRow == rows and currentColumn < columns - 1: + currentRow = 0 + currentColumn += 1 + + node.pos = (currentColumn * colSize, heights[currentColumn]) + heights[currentColumn] += node.height + currentRow += 1 + + finalHeight = max(heights) + if self.height != finalHeight: + self.notifySubscribers(self.WIDGET_HEIGHT_CHANGED, [finalHeight]) + + def __modifiersToString(self, modifiers): + def isSingleBit(number): + bitsSet = 0 + for i in xrange(8): + if (1 << i) & number: + bitsSet += 1 + + return bitsSet == 1 + + if modifiers in (0, kbmgr.KEYMOD_ANY): + return None + + allModifiers = [] + for mod in dir(avg): + if 'KEYMOD_' in mod: + maskVal = int(getattr(avg, mod)) + if isSingleBit(maskVal): + allModifiers.append((maskVal, mod)) + + modifiersStringsList = [] + for modval, modstr in allModifiers: + if modifiers & modval: + modifiersStringsList.append(modstr.replace('KEYMOD_', '')) + + for doubleMod in ['CTRL', 'META', 'SHIFT']: + left = 'L' + doubleMod + right = 'R' + doubleMod + if left in modifiersStringsList and right in modifiersStringsList: + modifiersStringsList.remove(left) + modifiersStringsList.remove(right) + modifiersStringsList.append(doubleMod) + + return '/'.join(modifiersStringsList).lower() + + +class DebugPanel(avg.DivNode): + def __init__(self, parent=None, fontsize=10, **kwargs): + super(DebugPanel, self).__init__(**kwargs) + self.registerInstance(self, parent) + + avg.RectNode(size=self.size, opacity=0, fillopacity=0.3, fillcolor='ff0000', + parent=self) + avg.WordsNode(text='Debug panel', fontsize=fontsize, + pos=(0, self.height - fontsize - fontsize / 3), + parent=self) + + self.sensitive = False + self.active = False + self.__panel = None + self.__callables = [] + self.__fontsize = fontsize + self.__touchVisOverlay = None + + def setupKeys(self): + kbmgr.bindKeyDown(keystring='g', + handler=lambda: self.toggleWidget(GPUMemoryGraphWidget), + help="GPU memory graph", + modifiers=libavg.avg.KEYMOD_CTRL) + + kbmgr.bindKeyDown(keystring='m', + handler=lambda: self.toggleWidget(MemoryGraphWidget), + help="Memory graph", + modifiers=libavg.avg.KEYMOD_CTRL) + + kbmgr.bindKeyDown(keystring='f', + handler=lambda: self.toggleWidget(FrametimeGraphWidget), + help="Frametime graph", + modifiers=libavg.avg.KEYMOD_CTRL) + + kbmgr.bindKeyDown(keystring='?', + handler=lambda: self.toggleWidget(KeyboardManagerBindingsShower), + help="Show keyboard bindings", + modifiers=kbmgr.KEYMOD_ANY) + + kbmgr.bindKeyDown(keystring='o', + handler=lambda: self.toggleWidget(ObjectDumpWidget), + help="Object count table", + modifiers=libavg.avg.KEYMOD_CTRL) + + kbmgr.bindKeyDown(keystring='v', handler=self.toggleTouchVisualization, + help="Cursor visualization", + modifiers=libavg.avg.KEYMOD_CTRL) + + def addWidget(self, widgetCls, *args, **kwargs): + callable_ = lambda: self.__panel.addWidget(widgetCls, *args, **kwargs) + if self.__panel: + callable_() + else: + self.__callables.append(callable_) + + def toggleWidget(self, *args, **kwargs): + if not self.active: + self.show() + self.__panel.ensureWidgetWisible(*args, **kwargs) + else: + self.__panel.toggleWidget(*args, **kwargs) + + if not self.__panel.activeWidgetClasses: + self.hide() + + def hide(self): + if self.__panel and self.active: + self.__panel.hide() + self.active = False + + def show(self): + if self.__panel: + if not self.active: + self.__panel.show() + else: + self.forceLoadPanel() + + self.active = True + + def toggleVisibility(self): + if self.active: + self.hide() + else: + self.show() + + def toggleTouchVisualization(self): + if self.__touchVisOverlay is None: + self.__touchVisOverlay = TouchVisOverlay( + isDebug=True, + visClass=DebugTouchVisualization, + size=self.parent.size, + parent=self.parent) + else: + self.__touchVisOverlay.unlink(True) + self.__touchVisOverlay = None + + def forceLoadPanel(self): + if self.__panel is None: + self.__panel = _DebugPanel(parent=self, size=self.size, + fontsize=self.__fontsize) + for callable_ in self.__callables: + callable_() + + +class _DebugPanel(avg.DivNode): + + def __init__(self, parent=None, fontsize=10, **kwargs): + super(_DebugPanel, self).__init__(**kwargs) + self.registerInstance(self, parent) + + self.__slots = [] + + self.maxSize = self.size + self.size = (self.size[0], 0) + self.activeWidgetClasses = [] + self.__selectedWidget = None + + global g_fontsize + g_fontsize = fontsize + + self.show() + + def show(self): + for widgetFrame in self.__slots: + if widgetFrame: + widgetFrame.show() + self.updateWidgets() + + def hide(self): + for widget in self.__slots: + if widget: + widget.hide() + + def ensureWidgetWisible(self, widgetClass, *args, **kwargs): + if not widgetClass in self.activeWidgetClasses: + self.toggleWidget(widgetClass, *args, **kwargs) + + def toggleWidget(self, widgetClass, *args, **kwargs): + if widgetClass in self.activeWidgetClasses: + self._removeWidgetByClass(widgetClass) + else: + self.addWidget(widgetClass, *args, **kwargs) + + def addWidget(self, widgetClass, *args, **kwargs): + if widgetClass in self.activeWidgetClasses: + libavg.logger.warning("You can't add the same widget twice") + return + + widgetFrame = DebugWidgetFrame((max(0, self.width), DebugWidget.SLOT_HEIGHT), + widgetClass) + height = 0 + for frame in self.__slots: + if frame: + height += frame.height + height += widgetFrame.height + + if height > self.maxSize[1]: + libavg.logger.warning("No vertical space left. " + "Delete a widget and try again") + return False + + self.appendChild(widgetFrame) + + widgetPlaced = False + for idx, slot in enumerate(self.__slots): + if slot is None: + self.__slots[idx] = widgetFrame + widgetPlaced = True + break + if not widgetPlaced: + self.__slots.append(widgetFrame) + widgetFrame.subscribe(widgetFrame.FRAME_HEIGHT_CHANGED, self._heightChanged) + + self.reorderWidgets() + widgetFrame.show() + self.updateWidgets() + self.activeWidgetClasses.append(widgetClass) + + def _removeWidgetByClass(self, widgetClass): + for frame in self.__slots: + if frame and frame.widget.__class__ == widgetClass: + self.removeWidgetFrame(frame) + return + + def _heightChanged(self): + height = 0 + for childID in xrange(0, self.getNumChildren()): + child = self.getChild(childID) + height += child.height + self.height = height + self.reorderWidgets() + + def updateWidgets(self): + for childID in xrange(0, self.getNumChildren()): + self.getChild(childID).widget.update() + + def selectWidget(self, id): + id = id % self.getNumChildren() + for childID in xrange(0, self.getNumChildren()): + self.getChild(childID).unselect() + self.getChild(id).select() + self.__selectedWidget = id + + def selectPreviousWidget(self): + if self.__selectedWidget is None: + self.selectWidget(-1) + else: + self.selectWidget(self.__selectedWidget - 1) + + def selectNextWidget(self): + if self.__selectedWidget is None: + self.selectWidget(0) + else: + self.selectWidget(self.__selectedWidget + 1) + + def removeWidgetFrame(self, widgetFrame): + self.activeWidgetClasses.remove(widgetFrame.widget.__class__) + for idx, slot in enumerate(self.__slots): + if slot == widgetFrame: + self.__slots[idx] = None + break + widgetFrame.widget.kill() + widgetFrame.unlink(True) + self.reorderWidgets() + self.updateWidgets() + + def removeSelectedWidgetFrames(self): + candidates = [] + for childID in xrange(0, self.getNumChildren()): + child = self.getChild(childID) + if child.isSelected(): + candidates.append(child) + for widgetFrame in candidates: + self.removeWidgetFrame(widgetFrame) + self.__selectedWidget = None + + def reorderWidgets(self): + #TODO: This is no layout management, yet + count = 0 + height = 0 + for idx, widgetFrame in enumerate(self.__slots): + if widgetFrame: + widgetFrame.pos = (0, height) + count += 1 + height += widgetFrame.height + self.size = (self.maxSize[0], height) |