File: dock_area.py

package info (click to toggle)
python-enaml 0.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,284 kB
  • sloc: python: 31,443; cpp: 4,499; makefile: 140; javascript: 68; lisp: 53; sh: 20
file content (227 lines) | stat: -rw-r--r-- 7,653 bytes parent folder | download
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)