1"""
2Wrapper for the layout.
3"""
4from __future__ import unicode_literals
5
6import six
7
8from prompt_toolkit.buffer import Buffer
9
10from .containers import ConditionalContainer, Container, Window, to_container
11from .controls import BufferControl, UIControl
12
13__all__ = [
14    'Layout',
15    'InvalidLayoutError',
16    'walk',
17]
18
19
20class Layout(object):
21    """
22    The layout for a prompt_toolkit
23    :class:`~prompt_toolkit.application.Application`.
24    This also keeps track of which user control is focused.
25
26    :param container: The "root" container for the layout.
27    :param focused_element: element to be focused initially. (Can be anything
28        the `focus` function accepts.)
29    """
30    def __init__(self, container, focused_element=None):
31        self.container = to_container(container)
32        self._stack = []
33
34        # Map search BufferControl back to the original BufferControl.
35        # This is used to keep track of when exactly we are searching, and for
36        # applying the search.
37        # When a link exists in this dictionary, that means the search is
38        # currently active.
39        self.search_links = {}  # search_buffer_control -> original buffer control.
40
41        # Mapping that maps the children in the layout to their parent.
42        # This relationship is calculated dynamically, each time when the UI
43        # is rendered.  (UI elements have only references to their children.)
44        self._child_to_parent = {}
45
46        if focused_element is None:
47            try:
48                self._stack.append(next(self.find_all_windows()))
49            except StopIteration:
50                raise InvalidLayoutError('Invalid layout. The layout does not contain any Window object.')
51        else:
52            self.focus(focused_element)
53
54        # List of visible windows.
55        self.visible_windows = []  # List of `Window` objects.
56
57    def __repr__(self):
58        return 'Layout(%r, current_window=%r)' % (
59            self.container, self.current_window)
60
61    def find_all_windows(self):
62        """
63        Find all the :class:`.UIControl` objects in this layout.
64        """
65        for item in self.walk():
66            if isinstance(item, Window):
67                yield item
68
69    def find_all_controls(self):
70        for container in self.find_all_windows():
71            yield container.content
72
73    def focus(self, value):
74        """
75        Focus the given UI element.
76
77        `value` can be either:
78
79        - a :class:`.UIControl`
80        - a :class:`.Buffer` instance or the name of a :class:`.Buffer`
81        - a :class:`.Window`
82        - Any container object. In this case we will focus the :class:`.Window`
83          from this container that was focused most recent, or the very first
84          focusable :class:`.Window` of the container.
85        """
86        # BufferControl by buffer name.
87        if isinstance(value, six.text_type):
88            for control in self.find_all_controls():
89                if isinstance(control, BufferControl) and control.buffer.name == value:
90                    self.focus(control)
91                    return
92            raise ValueError("Couldn't find Buffer in the current layout: %r." % (value, ))
93
94        # BufferControl by buffer object.
95        elif isinstance(value, Buffer):
96            for control in self.find_all_controls():
97                if isinstance(control, BufferControl) and control.buffer == value:
98                    self.focus(control)
99                    return
100            raise ValueError("Couldn't find Buffer in the current layout: %r." % (value, ))
101
102        # Focus UIControl.
103        elif isinstance(value, UIControl):
104            if value not in self.find_all_controls():
105                raise ValueError('Invalid value. Container does not appear in the layout.')
106            if not value.is_focusable():
107                raise ValueError('Invalid value. UIControl is not focusable.')
108
109            self.current_control = value
110
111        # Otherwise, expecting any Container object.
112        else:
113            value = to_container(value)
114
115            if isinstance(value, Window):
116                # This is a `Window`: focus that.
117                if value not in self.find_all_windows():
118                    raise ValueError('Invalid value. Window does not appear in the layout: %r' %
119                            (value, ))
120
121                self.current_window = value
122            else:
123                # Focus a window in this container.
124                # If we have many windows as part of this container, and some
125                # of them have been focused before, take the last focused
126                # item. (This is very useful when the UI is composed of more
127                # complex sub components.)
128                windows = []
129                for c in walk(value, skip_hidden=True):
130                    if isinstance(c, Window) and c.content.is_focusable():
131                        windows.append(c)
132
133                # Take the first one that was focused before.
134                for w in reversed(self._stack):
135                    if w in windows:
136                        self.current_window = w
137                        return
138
139                # None was focused before: take the very first focusable window.
140                if windows:
141                    self.current_window = windows[0]
142                    return
143
144                raise ValueError('Invalid value. Container cannot be focused: %r' % (value, ))
145
146    def has_focus(self, value):
147        """
148        Check whether the given control has the focus.
149        :param value: :class:`.UIControl` or :class:`.Window` instance.
150        """
151        if isinstance(value, six.text_type):
152            if self.current_buffer is None:
153                return False
154            return self.current_buffer.name == value
155        if isinstance(value, Buffer):
156            return self.current_buffer == value
157        if isinstance(value, UIControl):
158            return self.current_control == value
159        else:
160            value = to_container(value)
161            if isinstance(value, Window):
162                return self.current_window == value
163            else:
164                # Check whether this "container" is focused. This is true if
165                # one of the elements inside is focused.
166                for element in walk(value):
167                    if element == self.current_window:
168                        return True
169                return False
170
171    @property
172    def current_control(self):
173        """
174        Get the :class:`.UIControl` to currently has the focus.
175        """
176        return self._stack[-1].content
177
178    @current_control.setter
179    def current_control(self, control):
180        """
181        Set the :class:`.UIControl` to receive the focus.
182        """
183        assert isinstance(control, UIControl)
184
185        for window in self.find_all_windows():
186            if window.content == control:
187                self.current_window = window
188                return
189
190        raise ValueError('Control not found in the user interface.')
191
192    @property
193    def current_window(self):
194        " Return the :class:`.Window` object that is currently focused. "
195        return self._stack[-1]
196
197    @current_window.setter
198    def current_window(self, value):
199        " Set the :class:`.Window` object to be currently focused. "
200        assert isinstance(value, Window)
201        self._stack.append(value)
202
203    @property
204    def is_searching(self):
205        " True if we are searching right now. "
206        return self.current_control in self.search_links
207
208    @property
209    def search_target_buffer_control(self):
210        " Return the :class:`.BufferControl` in which we are searching or `None`. "
211        return self.search_links.get(self.current_control)
212
213    def get_focusable_windows(self):
214        """
215        Return all the :class:`.Window` objects which are focusable (in the
216        'modal' area).
217        """
218        for w in self.walk_through_modal_area():
219            if isinstance(w, Window) and w.content.is_focusable():
220                yield w
221
222    def get_visible_focusable_windows(self):
223        """
224        Return a list of :class:`.Window` objects that are focusable.
225        """
226        # focusable windows are windows that are visible, but also part of the
227        # modal container. Make sure to keep the ordering.
228        visible_windows = self.visible_windows
229        return [w for w in self.get_focusable_windows() if w in visible_windows]
230
231    @property
232    def current_buffer(self):
233        """
234        The currently focused :class:`~.Buffer` or `None`.
235        """
236        ui_control = self.current_control
237        if isinstance(ui_control, BufferControl):
238            return ui_control.buffer
239
240    def get_buffer_by_name(self, buffer_name):
241        """
242        Look in the layout for a buffer with the given name.
243        Return `None` when nothing was found.
244        """
245        for w in self.walk():
246            if isinstance(w, Window) and isinstance(w.content, BufferControl):
247                if w.content.buffer.name == buffer_name:
248                    return w.content.buffer
249
250    @property
251    def buffer_has_focus(self):
252        """
253        Return `True` if the currently focused control is a
254        :class:`.BufferControl`. (For instance, used to determine whether the
255        default key bindings should be active or not.)
256        """
257        ui_control = self.current_control
258        return isinstance(ui_control, BufferControl)
259
260    @property
261    def previous_control(self):
262        """
263        Get the :class:`.UIControl` to previously had the focus.
264        """
265        try:
266            return self._stack[-2].content
267        except IndexError:
268            return self._stack[-1].content
269
270    def focus_last(self):
271        """
272        Give the focus to the last focused control.
273        """
274        if len(self._stack) > 1:
275            self._stack = self._stack[:-1]
276
277    def focus_next(self):
278        """
279        Focus the next visible/focusable Window.
280        """
281        windows = self.get_visible_focusable_windows()
282
283        if len(windows) > 0:
284            try:
285                index = windows.index(self.current_window)
286            except ValueError:
287                index = 0
288            else:
289                index = (index + 1) % len(windows)
290
291            self.focus(windows[index])
292
293    def focus_previous(self):
294        """
295        Focus the previous visible/focusable Window.
296        """
297        windows = self.get_visible_focusable_windows()
298
299        if len(windows) > 0:
300            try:
301                index = windows.index(self.current_window)
302            except ValueError:
303                index = 0
304            else:
305                index = (index - 1) % len(windows)
306
307            self.focus(windows[index])
308
309    def walk(self):
310        """
311        Walk through all the layout nodes (and their children) and yield them.
312        """
313        for i in walk(self.container):
314            yield i
315
316    def walk_through_modal_area(self):
317        """
318        Walk through all the containers which are in the current 'modal' part
319        of the layout.
320        """
321        # Go up in the tree, and find the root. (it will be a part of the
322        # layout, if the focus is in a modal part.)
323        root = self.current_window
324        while not root.is_modal() and root in self._child_to_parent:
325            root = self._child_to_parent[root]
326
327        for container in walk(root):
328            yield container
329
330    def update_parents_relations(self):
331        """
332        Update child->parent relationships mapping.
333        """
334        parents = {}
335
336        def walk(e):
337            for c in e.get_children():
338                parents[c] = e
339                walk(c)
340
341        walk(self.container)
342
343        self._child_to_parent = parents
344
345    def reset(self):
346        # Remove all search links when the UI starts.
347        # (Important, for instance when control-c is been pressed while
348        #  searching. The prompt cancels, but next `run()` call the search
349        #  links are still there.)
350        self.search_links.clear()
351
352        return self.container.reset()
353
354    def get_parent(self, container):
355        """
356        Return the parent container for the given container, or ``None``, if it
357        wasn't found.
358        """
359        try:
360            return self._child_to_parent[container]
361        except KeyError:
362            return
363
364
365class InvalidLayoutError(Exception):
366    pass
367
368
369def walk(container, skip_hidden=False):
370    """
371    Walk through layout, starting at this container.
372    """
373    assert isinstance(container, Container)
374
375    # When `skip_hidden` is set, don't go into disabled ConditionalContainer containers.
376    if skip_hidden and isinstance(container, ConditionalContainer) and not container.filter():
377        return
378
379    yield container
380
381    for c in container.get_children():
382        # yield from walk(c)
383        for i in walk(c, skip_hidden=skip_hidden):
384            yield i
385