1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
#------------------------------------------------------------------------------
# Copyright (c) 2013-2025, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
#------------------------------------------------------------------------------
from contextlib import contextmanager
from atom.api import (
Bool, Coerced, Enum, Typed, ForwardTyped, Str, Event, set_default
)
from enaml.core.declarative import d_, observe
from enaml.layout.dock_layout import DockLayout, DockLayoutOp
from enaml.styling import StyleSheet
from .constraints_widget import ConstraintsWidget, ProxyConstraintsWidget
from .dock_events import DockEvent
from .dock_item import DockItem
_dock_area_styles = None
def get_registered_styles(name):
# lazy import the stdlib module in case it's never needed.
global _dock_area_styles
if _dock_area_styles is None:
import enaml
with enaml.imports():
from enaml.stdlib import dock_area_styles
_dock_area_styles = dock_area_styles
return _dock_area_styles.get_registered_styles(name)
class ProxyDockArea(ProxyConstraintsWidget):
""" The abstract definition of a proxy DockArea object.
"""
#: A reference to the Stack declaration.
declaration = ForwardTyped(lambda: DockArea)
def set_tab_position(self, position):
raise NotImplementedError
def set_live_drag(self, live_drag):
raise NotImplementedError
def set_style(self, style):
raise NotImplementedError
def set_dock_events_enabled(self, enabled):
raise NotImplementedError
def save_layout(self):
raise NotImplementedError
def apply_layout(self, layout):
raise NotImplementedError
def update_layout(self, ops):
raise NotImplementedError
class DockArea(ConstraintsWidget):
""" A component which arranges dock item children.
"""
#: The layout of dock items for the area. This attribute is *not*
#: kept in sync with the layout state of the widget at runtime. The
#: 'save_layout' method should be called to retrieve the current
#: layout state.
layout = d_(Coerced(DockLayout, ()))
#: The default tab position for newly created dock tabs.
tab_position = d_(Enum('top', 'bottom', 'left', 'right'))
#: Whether the dock items resize as a dock splitter is being dragged
#: (True), or if a simple indicator is drawn until the drag handle
#: is released (False). The default is True.
live_drag = d_(Bool(True))
#: The name of the registered style to apply to the dock area. The
#: list of available styles can be retrieved by calling the function
#: `available_styles` in the `enaml.stdlib.dock_area_styles` module.
#: The default is a style inspired by Visual Studio 2010
#:
#: Users can also define and use their own custom style sheets with
#: the dock area. Simply set this attribute to an empty string so
#: the default styling is disabled, and proceed to use style sheets
#: as with any other widget (see the stdlib styles for inspiration).
#:
#: Only one mode of styling should be used for the dock area at a
#: time. Using both modes simultaneously is undefined.
style = d_(Str('vs-2024'))
#: Whether or not dock events are enabled for the area.
dock_events_enabled = d_(Bool(False))
#: An event emitted when a dock event occurs in the dock area.
#: `dock_events_enabled` must be True in order to recieve events.
dock_event = d_(Event(DockEvent), writable=False)
#: A Stack expands freely in height and width by default
hug_width = set_default('ignore')
hug_height = set_default('ignore')
#: A reference to the ProxyStack widget.
proxy = Typed(ProxyDockArea)
#: The style sheet created from the 'style' attribute.
_internal_style = Typed(StyleSheet)
def initialize(self):
""" A reimplemented initializer method.
This method ensures the internal style sheet is created.
"""
super(DockArea, self).initialize()
self._refresh_internal_style()
def dock_items(self):
""" Get the dock items defined on the stack
"""
return [c for c in self.children if isinstance(c, DockItem)]
def save_layout(self):
""" Save the current layout state of the dock area.
Returns
-------
result : docklayout
The current layout state of the dock area.
"""
if self.proxy_is_active:
return self.proxy.save_layout()
return self.layout
def apply_layout(self, layout):
""" Apply a new layout to the dock area.
Parameters
----------
layout : DockLayout
The dock layout to apply to the dock area.
"""
assert isinstance(layout, DockLayout), 'layout must be a DockLayout'
if self.proxy_is_active:
return self.proxy.apply_layout(layout)
def update_layout(self, ops):
""" Update the layout configuration using layout operations.
Parameters
----------
ops : DockLayoutOp or iterable
A single DockLayoutOp instance or an iterable of the same.
The operations will be executed in order. If a given op is
not valid for the current layout state, it will be skipped.
"""
if isinstance(ops, DockLayoutOp):
ops = [ops]
for op in ops:
assert isinstance(op, DockLayoutOp)
if self.proxy_is_active:
self.proxy.update_layout(ops)
@contextmanager
def suppress_dock_events(self):
""" A context manager which supresses dock events.
This manager will disable dock events for the duration of the
context, and restore the old value upon exit.
"""
enabled = self.dock_events_enabled
self.dock_events_enabled = False
yield
self.dock_events_enabled = enabled
#--------------------------------------------------------------------------
# Observers
#--------------------------------------------------------------------------
@observe('style')
def _update_style(self, change):
""" An observer which updates the internal style sheet.
"""
change_t = change['type']
if change_t == 'update' or change_t == 'delete':
self._refresh_internal_style()
@observe('layout')
def _update_layout(self, change):
""" An observer which updates the layout when it changes.
"""
if change['type'] == 'update':
self.apply_layout(change['value'])
@observe('tab_position', 'live_drag', 'style', 'dock_events_enabled')
def _update_proxy(self, change):
""" Update the proxy when the area state changes.
"""
# The superclass implementation is sufficient.
super(DockArea, self)._update_proxy(change)
#--------------------------------------------------------------------------
# Private API
#--------------------------------------------------------------------------
def _refresh_internal_style(self):
old = self._internal_style
if old is not None:
old.destroy()
self._internal_style = None
if self.style:
style_t = get_registered_styles(self.style)
if style_t is not None:
sheet = StyleSheet()
style_t()(sheet)
self._internal_style = sheet
sheet.set_parent(self)
|