# coding=utf-8
"""
Console
=======
.. versionadded:: 1.9.1
Reboot of the old inspector, designed to be modular and keep concerns
separated. It also has an addons architecture that allow you to add a button,
panel, or more in the Console itself.
.. warning::
This module works, but might fail in some cases. Please contribute!
Usage
-----
For normal module usage, please see the :mod:`~kivy.modules` documentation::
python main.py -m console
Mouse navigation
----------------
When the "Select" button is activated, you can:
- tap once on a widget to select it without leaving inspect mode
- double tap on a widget to select and leave inspect mode (then you can
manipulate the widget again)
Keyboard navigation
-------------------
- "Ctrl + e": toggle console
- "Escape": cancel widget lookup, then hide inspector view
- "Up": select the parent widget
- "Down": select the first child of the currently selected widget
- "Left": select the previous sibling
- "Right": select the next sibling
Additional information
----------------------
Some properties can be edited live. However, due to the delayed usage of
some properties, it might crash if you don't handle the required cases.
Addons
------
Addons must be added to `Console.addons` before the first Clock tick of the
application, or before :attr:`create_console` is called. You currently cannot
add addons on the fly. Addons are quite cheap until the Console is activated.
Panels are even cheaper as nothing is done until the user selects them.
We provide multiple addons activated by default:
- ConsoleAddonFps: display the FPS at the top-right
- ConsoleAddonSelect: activate the selection mode
- ConsoleAddonBreadcrumb: display the hierarchy of the current widget at the
bottom
- ConsoleAddonWidgetTree: panel to display the widget tree of the application
- ConsoleAddonWidgetPanel: panel to display the properties of the selected
widget
If you need to add custom widgets in the Console, please use either
:class:`ConsoleButton`, :class:`ConsoleToggleButton` or :class:`ConsoleLabel`.
An addon must inherit from the :class:`ConsoleAddon` class.
For example, here is a simple addon for displaying the FPS at the top/right
of the Console::
from kivy.modules.console import Console, ConsoleAddon
class ConsoleAddonFps(ConsoleAddon):
def init(self):
self.lbl = ConsoleLabel(text="0 Fps")
self.console.add_toolbar_widget(self.lbl, right=True)
def activate(self):
self.event = Clock.schedule_interval(self.update_fps, 1 / 2.)
def deactivated(self):
self.event.cancel()
def update_fps(self, *args):
fps = Clock.get_fps()
self.lbl.text = "{} Fps".format(int(fps))
Console.register_addon(ConsoleAddonFps)
You can create addons that add panels. Panel activation/deactivation is not
tied to the addon activation/deactivation, but in some cases, you can use the
same callback for deactivating the addon and the panel. Here is a simple
"About" panel addon::
from kivy.modules.console import Console, ConsoleAddon, ConsoleLabel
class ConsoleAddonAbout(ConsoleAddon):
def init(self):
self.console.add_panel("About", self.panel_activate,
self.panel_deactivate)
def panel_activate(self):
self.console.bind(widget=self.update_content)
self.update_content()
def panel_deactivate(self):
self.console.unbind(widget=self.update_content)
def deactivate(self):
self.panel_deactivate()
def update_content(self, *args):
widget = self.console.widget
if not widget:
return
text = "Selected widget is: {!r}".format(widget)
lbl = ConsoleLabel(text=text)
self.console.set_content(lbl)
Console.register_addon(ConsoleAddonAbout)
"""
__all__ = ("start", "stop", "create_console", "Console", "ConsoleAddon",
"ConsoleButton", "ConsoleToggleButton", "ConsoleLabel")
import kivy
kivy.require('1.9.0')
import weakref
from functools import partial
from itertools import chain
from kivy.logger import Logger
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.image import Image
from kivy.uix.treeview import TreeViewNode, TreeView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView
from kivy.graphics import Color, Rectangle, PushMatrix, PopMatrix
from kivy.graphics.context_instructions import Transform
from kivy.graphics.transformation import Matrix
from kivy.properties import (ObjectProperty, BooleanProperty, ListProperty,
NumericProperty, StringProperty, OptionProperty,
ReferenceListProperty, AliasProperty,
VariableListProperty)
from kivy.graphics.texture import Texture
from kivy.clock import Clock
from kivy.lang import Builder
Builder.load_string("""
<Console>:
size_hint: (1, None) if self.mode == "docked" else (None, None)
height: dp(250)
canvas:
Color:
rgb: .185, .18, .18
Rectangle:
size: self.size
Color:
rgb: .3, .3, .3
Rectangle:
pos: 0, self.height - dp(48)
size: self.width, dp(48)
GridLayout:
cols: 1
id: layout
GridLayout:
id: toolbar
rows: 1
height: "48dp"
size_hint_y: None
padding: "4dp"
spacing: "4dp"
RelativeLayout:
id: content
<ConsoleAddonSeparator>:
size_hint_x: None
width: "10dp"
<ConsoleButton,ConsoleToggleButton,ConsoleLabel>:
size_hint_x: None
width: self.texture_size[0] + dp(20)
<ConsoleAddonBreadcrumbView>:
size_hint_y: None
height: "48dp"
canvas:
Color:
rgb: .3, .3, .3
Rectangle:
size: self.size
ScrollView:
id: sv
do_scroll_y: False
GridLayout:
id: stack
rows: 1
size_hint_x: None
width: self.minimum_width
padding: "4dp"
spacing: "4dp"
<TreeViewProperty>:
height: max(dp(48), max(lkey.texture_size[1], ltext.texture_size[1]))
Label:
id: lkey
text: root.key
text_size: (self.width, None)
width: 150
size_hint_x: None
Label:
id: ltext
text: [repr(getattr(root.widget, root.key, '')), root.refresh][0]\
if root.widget else ''
text_size: (self.width, None)
<ConsoleAddonWidgetTreeView>:
ScrollView:
scroll_type: ['bars', 'content']
bar_width: '10dp'
ConsoleAddonWidgetTreeImpl:
id: widgettree
hide_root: True
size_hint: None, None
height: self.minimum_height
width: max(self.parent.width, self.minimum_width)
selected_widget: root.widget
on_select_widget: root.console.highlight_widget(args[1])
<-TreeViewWidget>:
height: self.texture_size[1] + sp(4)
size_hint_x: None
width: self.texture_size[0] + sp(4)
canvas.before:
Color:
rgba: self.color_selected if self.is_selected else (0, 0, 0, 0)
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: 1, 1, 1, int(not self.is_leaf)
Rectangle:
source:
('atlas://data/images/defaulttheme/tree_%s' %
('opened' if self.is_open else 'closed'))
size: 16, 16
pos: self.x - 20, self.center_y - 8
canvas:
Color:
rgba:
(self.disabled_color if self.disabled else
(self.color if not self.markup else (1, 1, 1, 1)))
Rectangle:
texture: self.texture
size: self.texture_size
pos:
(int(self.center_x - self.texture_size[0] / 2.),
int(self.center_y - self.texture_size[1] / 2.))
""")
def ignore_exception(f):
def f2(*args, **kwargs):
try:
return f(*args, **kwargs)
except:
pass
return f2
class TreeViewProperty(BoxLayout, TreeViewNode):
key = ObjectProperty(None, allownone=True)
refresh = BooleanProperty(False)
widget_ref = ObjectProperty(None, allownone=True)
def _get_widget(self):
wr = self.widget_ref
if wr is None:
return None
wr = wr()
if wr is None:
self.widget_ref = None
return None
return wr
widget = AliasProperty(_get_widget, None, bind=('widget_ref', ))
[docs]class ConsoleLabel(Label):
"""LabelButton specialized for the Console"""
pass
class ConsoleAddonSeparator(Widget):
pass
[docs]class ConsoleAddon(object):
"""Base class for implementing addons"""
#: Console instance
console = None
def __init__(self, console):
super(ConsoleAddon, self).__init__()
self.console = console
self.init()
[docs] def init(self):
"""Method called when the addon is instantiated by the Console
"""
pass
[docs] def activate(self):
"""Method called when the addon is activated by the console
(when the console is displayed)"""
pass
[docs] def deactivate(self):
"""Method called when the addon is deactivated by the console
(when the console is hidden)
"""
pass
class ConsoleAddonMode(ConsoleAddon):
def init(self):
btn = ConsoleToggleButton(text=u"Docked")
self.console.add_toolbar_widget(btn)
class ConsoleAddonSelect(ConsoleAddon):
def init(self):
self.btn = ConsoleToggleButton(text=u"Select")
self.btn.bind(state=self.on_button_state)
self.console.add_toolbar_widget(self.btn)
self.console.bind(inspect_enabled=self.on_inspect_enabled)
def on_inspect_enabled(self, instance, value):
self.btn.state = "down" if value else "normal"
def on_button_state(self, instance, value):
self.console.inspect_enabled = (value == "down")
class ConsoleAddonFps(ConsoleAddon):
_update_ev = None
def init(self):
self.lbl = ConsoleLabel(text="0 Fps")
self.console.add_toolbar_widget(self.lbl, right=True)
def activate(self):
ev = self._update_ev
if ev is None:
self._update_ev = Clock.schedule_interval(self.update_fps, 1 / 2.)
else:
ev()
def deactivated(self):
if self._update_ev is not None:
self._update_ev.cancel()
def update_fps(self, *args):
fps = Clock.get_fps()
self.lbl.text = "{} Fps".format(int(fps))
class ConsoleAddonBreadcrumbView(RelativeLayout):
widget = ObjectProperty(None, allownone=True)
parents = []
def on_widget(self, instance, value):
stack = self.ids.stack
# determine if we can just highlight the current one
# or if we need to rebuild the breadcrumb
prefs = [btn.widget_ref() for btn in self.parents]
if value in prefs:
# ok, so just toggle this one instead.
index = prefs.index(value)
for btn in self.parents:
btn.state = "normal"
self.parents[index].state = "down"
return
# we need to rebuild the breadcrumb.
stack.clear_widgets()
if not value:
return
widget = value
parents = []
while True:
btn = ConsoleButton(text=widget.__class__.__name__)
btn.widget_ref = weakref.ref(widget)
btn.bind(on_release=self.highlight_widget)
parents.append(btn)
if widget == widget.parent:
break
widget = widget.parent
for btn in reversed(parents):
stack.add_widget(btn)
self.ids.sv.scroll_x = 1
self.parents = parents
btn.state = "down"
def highlight_widget(self, instance):
self.console.widget = instance.widget_ref()
class ConsoleAddonBreadcrumb(ConsoleAddon):
def init(self):
self.view = ConsoleAddonBreadcrumbView()
self.view.console = self.console
self.console.ids.layout.add_widget(self.view)
def activate(self):
self.console.bind(widget=self.update_content)
self.update_content()
def deactivate(self):
self.console.unbind(widget=self.update_content)
def update_content(self, *args):
self.view.widget = self.console.widget
class ConsoleAddonWidgetPanel(ConsoleAddon):
def init(self):
self.console.add_panel("Properties", self.panel_activate,
self.deactivate)
def panel_activate(self):
self.console.bind(widget=self.update_content)
self.update_content()
def deactivate(self):
self.console.unbind(widget=self.update_content)
def update_content(self, *args):
widget = self.console.widget
if not widget:
return
from kivy.uix.scrollview import ScrollView
self.root = root = BoxLayout()
self.sv = sv = ScrollView(scroll_type=["bars", "content"],
bar_width='10dp')
treeview = TreeView(hide_root=True, size_hint_y=None)
treeview.bind(minimum_height=treeview.setter("height"))
keys = list(widget.properties().keys())
keys.sort()
node = None
wk_widget = weakref.ref(widget)
for key in keys:
node = TreeViewProperty(key=key, widget_ref=wk_widget)
node.bind(is_selected=self.show_property)
try:
widget.bind(**{
key: partial(self.update_node_content, weakref.ref(node))
})
except:
pass
treeview.add_node(node)
root.add_widget(sv)
sv.add_widget(treeview)
self.console.set_content(root)
def show_property(self, instance, value, key=None, index=-1, *l):
# normal call: (tree node, focus, )
# nested call: (widget, prop value, prop key, index in dict/list)
if value is False:
return
console = self.console
content = None
if key is None:
# normal call
nested = False
widget = instance.widget
key = instance.key
prop = widget.property(key)
value = getattr(widget, key)
else:
# nested call, we might edit subvalue
nested = True
widget = instance
prop = None
dtype = None
if isinstance(prop, AliasProperty) or nested:
# trying to resolve type dynamically
if type(value) in (str, str):
dtype = 'string'
elif type(value) in (int, float):
dtype = 'numeric'
elif type(value) in (tuple, list):
dtype = 'list'
if isinstance(prop, NumericProperty) or dtype == 'numeric':
content = TextInput(text=str(value) or '', multiline=False)
content.bind(
text=partial(self.save_property_numeric, widget, key, index))
elif isinstance(prop, StringProperty) or dtype == 'string':
content = TextInput(text=value or '', multiline=True)
content.bind(
text=partial(self.save_property_text, widget, key, index))
elif (isinstance(prop, ListProperty) or
isinstance(prop, ReferenceListProperty) or
isinstance(prop, VariableListProperty) or dtype == 'list'):
content = GridLayout(cols=1, size_hint_y=None)
content.bind(minimum_height=content.setter('height'))
for i, item in enumerate(value):
button = Button(text=repr(item), size_hint_y=None, height=44)
if isinstance(item, Widget):
button.bind(on_release=partial(console.highlight_widget,
item, False))
else:
button.bind(on_release=partial(self.show_property, widget,
item, key, i))
content.add_widget(button)
elif isinstance(prop, OptionProperty):
content = GridLayout(cols=1, size_hint_y=None)
content.bind(minimum_height=content.setter('height'))
for option in prop.options:
button = ToggleButton(
text=option,
state='down' if option == value else 'normal',
group=repr(content.uid),
size_hint_y=None,
height=44)
button.bind(
on_press=partial(self.save_property_option, widget, key))
content.add_widget(button)
elif isinstance(prop, ObjectProperty):
if isinstance(value, Widget):
content = Button(text=repr(value))
content.bind(
on_release=partial(console.highlight_widget, value))
elif isinstance(value, Texture):
content = Image(texture=value)
else:
content = Label(text=repr(value))
elif isinstance(prop, BooleanProperty):
state = 'down' if value else 'normal'
content = ToggleButton(text=key, state=state)
content.bind(on_release=partial(self.save_property_boolean, widget,
key, index))
self.root.clear_widgets()
self.root.add_widget(self.sv)
if content:
self.root.add_widget(content)
@ignore_exception
def save_property_numeric(self, widget, key, index, instance, value):
if index >= 0:
getattr(widget, key)[index] = float(instance.text)
else:
setattr(widget, key, float(instance.text))
@ignore_exception
def save_property_text(self, widget, key, index, instance, value):
if index >= 0:
getattr(widget, key)[index] = instance.text
else:
setattr(widget, key, instance.text)
@ignore_exception
def save_property_boolean(self, widget, key, index, instance, ):
value = instance.state == 'down'
if index >= 0:
getattr(widget, key)[index] = value
else:
setattr(widget, key, value)
@ignore_exception
def save_property_option(self, widget, key, instance, *l):
setattr(widget, key, instance.text)
class TreeViewWidget(Label, TreeViewNode):
widget = ObjectProperty(None)
class ConsoleAddonWidgetTreeImpl(TreeView):
selected_widget = ObjectProperty(None, allownone=True)
__events__ = ('on_select_widget', )
def __init__(self, **kwargs):
super(ConsoleAddonWidgetTreeImpl, self).__init__(**kwargs)
self.update_scroll = Clock.create_trigger(self._update_scroll)
def find_node_by_widget(self, widget):
for node in self.iterate_all_nodes():
if not node.parent_node:
continue
try:
if node.widget == widget:
return node
except ReferenceError:
pass
return None
def update_selected_widget(self, widget):
if widget:
node = self.find_node_by_widget(widget)
if node:
self.select_node(node, False)
while node and isinstance(node, TreeViewWidget):
if not node.is_open:
self.toggle_node(node)
node = node.parent_node
def on_selected_widget(self, inst, widget):
if widget:
self.update_selected_widget(widget)
self.update_scroll()
def select_node(self, node, select_widget=True):
super(ConsoleAddonWidgetTreeImpl, self).select_node(node)
if select_widget:
try:
self.dispatch("on_select_widget", node.widget.__self__)
except ReferenceError:
pass
def on_select_widget(self, widget):
pass
def _update_scroll(self, *args):
node = self._selected_node
if not node:
return
self.parent.scroll_to(node)
class ConsoleAddonWidgetTreeView(RelativeLayout):
widget = ObjectProperty(None, allownone=True)
_window_node = None
def _update_widget_tree_node(self, node, widget, is_open=False):
tree = self.ids.widgettree
update_nodes = []
nodes = {}
for cnode in node.nodes[:]:
try:
nodes[cnode.widget] = cnode
except ReferenceError:
# widget no longer exists, just remove it
pass
tree.remove_node(cnode)
for child in widget.children:
if isinstance(child, Console):
continue
if child in nodes:
cnode = tree.add_node(nodes[child], node)
else:
cnode = tree.add_node(
TreeViewWidget(text=child.__class__.__name__,
widget=child.proxy_ref,
is_open=is_open), node)
update_nodes.append((cnode, child))
return update_nodes
def update_widget_tree(self, *args):
win = self.console.win
if not self._window_node:
self._window_node = self.ids.widgettree.add_node(
TreeViewWidget(text="Window",
widget=win,
is_open=True))
nodes = self._update_widget_tree_node(self._window_node, win,
is_open=True)
while nodes:
ntmp = nodes[:]
nodes = []
for node in ntmp:
nodes += self._update_widget_tree_node(*node)
self.ids.widgettree.update_selected_widget(self.widget)
class ConsoleAddonWidgetTree(ConsoleAddon):
def init(self):
self.content = None
self.console.add_panel("Tree", self.panel_activate, self.deactivate,
self.panel_refresh)
def panel_activate(self):
self.console.bind(widget=self.update_content)
self.update_content()
def deactivate(self):
if self.content:
self.content.widget = None
self.content.console = None
self.console.unbind(widget=self.update_content)
def update_content(self, *args):
widget = self.console.widget
if not self.content:
self.content = ConsoleAddonWidgetTreeView()
self.content.console = self.console
self.content.widget = widget
self.content.update_widget_tree()
self.console.set_content(self.content)
def panel_refresh(self):
if self.content:
self.content.update_widget_tree()
[docs]class Console(RelativeLayout):
"""Console interface
This widget is created by create_console(), when the module is loaded.
During that time, you can add addons on the console to extend the
functionalities, or add your own application stats / debugging module.
"""
#: Array of addons that will be created at Console creation
addons = [ # ConsoleAddonMode,
ConsoleAddonSelect, ConsoleAddonFps, ConsoleAddonWidgetPanel,
ConsoleAddonWidgetTree, ConsoleAddonBreadcrumb]
#: Display mode of the Console, either docked at the bottom, or as a
#: floating window.
mode = OptionProperty("docked", options=["docked", "floated"])
#: Current widget being selected
widget = ObjectProperty(None, allownone=True)
#: Indicate if the inspector inspection is enabled. If yes, the next
#: touch down will select a the widget under the touch
inspect_enabled = BooleanProperty(False)
#: True if the Console is activated (showed)
activated = BooleanProperty(False)
def __init__(self, **kwargs):
self.win = kwargs.pop('win', None)
super(Console, self).__init__(**kwargs)
self.avoid_bring_to_top = False
with self.canvas.before:
self.gcolor = Color(1, 0, 0, .25)
PushMatrix()
self.gtransform = Transform(Matrix())
self.grect = Rectangle(size=(0, 0))
PopMatrix()
Clock.schedule_interval(self.update_widget_graphics, 0)
# instantiate all addons
self._toolbar = {"left": [], "panels": [], "right": []}
self._addons = []
self._panel = None
for addon in self.addons:
instance = addon(self)
self._addons.append(instance)
self._init_toolbar()
# select the first panel
self._panel = self._toolbar["panels"][0]
self._panel.state = "down"
self._panel.cb_activate()
def _init_toolbar(self):
toolbar = self.ids.toolbar
for key in ("left", "panels", "right"):
if key == "right":
toolbar.add_widget(Widget())
for el in self._toolbar[key]:
toolbar.add_widget(el)
if key != "right":
toolbar.add_widget(ConsoleAddonSeparator())
@classmethod
def register_addon(cls, addon):
cls.addons.append(addon)
[docs] def add_panel(self, name, cb_activate, cb_deactivate, cb_refresh=None):
"""Add a new panel in the Console.
- `cb_activate` is a callable that will be called when the panel is
activated by the user.
- `cb_deactivate` is a callable that will be called when the panel is
deactivated or when the console will hide.
- `cb_refresh` is an optional callable that is called if the user
click again on the button for display the panel
When activated, it's up to the panel to display a content in the
Console by using :meth:`set_content`.
"""
btn = ConsoleToggleButton(text=name)
btn.cb_activate = cb_activate
btn.cb_deactivate = cb_deactivate
btn.cb_refresh = cb_refresh
btn.bind(on_press=self._activate_panel)
self._toolbar["panels"].append(btn)
def _activate_panel(self, instance):
if self._panel != instance:
self._panel.cb_deactivate()
self._panel.state = "normal"
self.ids.content.clear_widgets()
self._panel = instance
self._panel.cb_activate()
self._panel.state = "down"
else:
self._panel.state = "down"
if self._panel.cb_refresh:
self._panel.cb_refresh()
[docs] def set_content(self, content):
"""Replace the Console content with a new one.
"""
self.ids.content.clear_widgets()
self.ids.content.add_widget(content)
[docs] def on_touch_down(self, touch):
ret = super(Console, self).on_touch_down(touch)
if (('button' not in touch.profile or touch.button == 'left') and
not ret and self.inspect_enabled):
self.highlight_at(*touch.pos)
if touch.is_double_tap:
self.inspect_enabled = False
ret = True
else:
ret = self.collide_point(*touch.pos)
return ret
[docs] def on_touch_move(self, touch):
ret = super(Console, self).on_touch_move(touch)
if not ret and self.inspect_enabled:
self.highlight_at(*touch.pos)
ret = True
return ret
[docs] def on_touch_up(self, touch):
ret = super(Console, self).on_touch_up(touch)
if not ret and self.inspect_enabled:
ret = True
return ret
def on_window_children(self, win, children):
if self.avoid_bring_to_top or not self.activated:
return
self.avoid_bring_to_top = True
win.remove_widget(self)
win.add_widget(self)
self.avoid_bring_to_top = False
[docs] def highlight_at(self, x, y):
"""Select a widget from a x/y window coordinate.
This is mostly used internally when Select mode is activated
"""
widget = None
# reverse the loop - look at children on top first and
# modalviews before others
win_children = self.win.children
children = chain((c for c in reversed(win_children)
if isinstance(c, ModalView)),
(c for c in reversed(win_children)
if not isinstance(c, ModalView)))
for child in children:
if child is self:
continue
widget = self.pick(child, x, y)
if widget:
break
self.highlight_widget(widget)
def highlight_widget(self, widget, *largs):
# no widget to highlight, reduce rectangle to 0, 0
self.widget = widget
if not widget:
self.grect.size = 0, 0
def update_widget_graphics(self, *l):
if not self.activated:
return
if self.widget is None:
self.grect.size = 0, 0
return
self.grect.size = self.widget.size
matrix = self.widget.get_window_matrix()
if self.gtransform.matrix.get() != matrix.get():
self.gtransform.matrix = matrix
[docs] def pick(self, widget, x, y):
"""Pick a widget at x/y, given a root `widget`
"""
ret = None
# try to filter widgets that are not visible (invalid inspect target)
if (hasattr(widget, 'visible') and not widget.visible):
return ret
if widget.collide_point(x, y):
ret = widget
x2, y2 = widget.to_local(x, y)
# reverse the loop - look at children on top first
for child in reversed(widget.children):
ret = self.pick(child, x2, y2) or ret
return ret
def on_activated(self, instance, activated):
if activated:
self._activate_console()
else:
self._deactivate_console()
def _activate_console(self):
if self not in self.win.children:
self.win.add_widget(self)
self.y = 0
for addon in self._addons:
addon.activate()
Logger.info('Console: console activated')
def _deactivate_console(self):
for addon in self._addons:
addon.deactivate()
self.grect.size = 0, 0
self.y = -self.height
self.widget = None
self.inspect_enabled = False
# self.win.remove_widget(self)
self._window_node = None
Logger.info('Console: console deactivated')
def keyboard_shortcut(self, win, scancode, *largs):
modifiers = largs[-1]
if scancode == 101 and modifiers == ['ctrl']:
self.activated = not self.activated
if self.activated:
self.inspect_enabled = True
return True
elif scancode == 27:
if self.inspect_enabled:
self.inspect_enabled = False
return True
if self.activated:
self.activated = False
return True
if not self.activated or not self.widget:
return
if scancode == 273: # top
self.widget = self.widget.parent
elif scancode == 274: # down
filtered_children = [c for c in self.widget.children
if not isinstance(c, Console)]
if filtered_children:
self.widget = filtered_children[0]
elif scancode == 276: # left
parent = self.widget.parent
filtered_children = [c for c in parent.children
if not isinstance(c, Console)]
index = filtered_children.index(self.widget)
index = max(0, index - 1)
self.widget = filtered_children[index]
elif scancode == 275: # right
parent = self.widget.parent
filtered_children = [c for c in parent.children
if not isinstance(c, Console)]
index = filtered_children.index(self.widget)
index = min(len(filtered_children) - 1, index + 1)
self.widget = filtered_children[index]
def create_console(win, ctx, *l):
ctx.console = Console(win=win)
win.bind(children=ctx.console.on_window_children,
on_keyboard=ctx.console.keyboard_shortcut)
[docs]def start(win, ctx):
"""Create an Console instance attached to the *ctx* and bound to the
Window's :meth:`~kivy.core.window.WindowBase.on_keyboard` event for
capturing the keyboard shortcut.
:Parameters:
`win`: A :class:`Window <kivy.core.window.WindowBase>`
The application Window to bind to.
`ctx`: A :class:`~kivy.uix.widget.Widget` or subclass
The Widget to be inspected.
"""
Clock.schedule_once(partial(create_console, win, ctx))
[docs]def stop(win, ctx):
"""Stop and unload any active Inspectors for the given *ctx*."""
if hasattr(ctx, "console"):
win.unbind(children=ctx.console.on_window_children,
on_keyboard=ctx.console.keyboard_shortcut)
win.remove_widget(ctx.console)
del ctx.console