1# This file is part of ranger, the console file manager.
2# License: GNU GPL version 3, see the file "AUTHORS" for details.
3
4"""ViewMiller arranges the view in miller columns"""
5
6from __future__ import (absolute_import, division, print_function)
7
8import curses
9from ranger.container import settings
10from ranger.gui.widgets.view_base import ViewBase
11
12from .browsercolumn import BrowserColumn
13from .pager import Pager
14from ..displayable import DisplayableContainer
15
16
17class ViewMiller(ViewBase):  # pylint: disable=too-many-ancestors,too-many-instance-attributes
18    ratios = None
19    preview = True
20    is_collapsed = False
21    stretch_ratios = None
22    old_collapse = False
23
24    def __init__(self, win):
25        ViewBase.__init__(self, win)
26        self.preview = True
27        self.columns = []
28
29        self.rebuild()
30
31        for option in ('preview_directories', 'preview_files'):
32            self.settings.signal_bind('setopt.' + option,
33                                      self._request_clear_if_has_borders, weak=True)
34
35        self.settings.signal_bind('setopt.column_ratios', self.request_clear)
36        self.settings.signal_bind('setopt.column_ratios', self.rebuild,
37                                  priority=settings.SIGNAL_PRIORITY_AFTER_SYNC)
38
39        self.old_draw_borders = self.settings.draw_borders
40
41    def rebuild(self):
42        for child in self.container:
43            if isinstance(child, BrowserColumn):
44                self.remove_child(child)
45                child.destroy()
46
47        self.pager = Pager(self.win, embedded=True)
48        self.pager.visible = False
49        self.add_child(self.pager)
50
51        ratios = self.settings.column_ratios
52
53        for column in self.columns:
54            column.destroy()
55            self.remove_child(column)
56        self.columns = []
57
58        ratios_sum = sum(ratios)
59        self.ratios = tuple((x / ratios_sum) for x in ratios)
60
61        last = 0.1 if self.settings.padding_right else 0
62        if len(self.ratios) >= 2:
63            self.stretch_ratios = self.ratios[:-2] + \
64                ((self.ratios[-2] + self.ratios[-1] * 1.0 - last),
65                 (self.ratios[-1] * last))
66
67        offset = 1 - len(ratios)
68        if self.preview:
69            offset += 1
70
71        for level in range(len(ratios)):
72            column = BrowserColumn(self.win, level + offset)
73            self.add_child(column)
74            self.columns.append(column)
75
76        try:
77            self.main_column = self.columns[self.preview and -2 or -1]
78        except IndexError:
79            self.main_column = None
80        else:
81            self.main_column.display_infostring = True
82            self.main_column.main_column = True
83
84        self.resize(self.y, self.x, self.hei, self.wid)
85
86    def _request_clear_if_has_borders(self):
87        if self.settings.draw_borders:
88            self.request_clear()
89
90    def draw(self):
91        if self.need_clear:
92            self.win.erase()
93            self.need_redraw = True
94            self.need_clear = False
95        for tab in self.fm.tabs.values():
96            directory = tab.thisdir
97            if directory:
98                directory.load_content_if_outdated()
99                directory.use()
100        DisplayableContainer.draw(self)
101        if self.settings.draw_borders:
102            draw_borders = self.settings.draw_borders.lower()
103            if draw_borders in ['both', 'true']:   # 'true' for backwards compat.
104                border_types = ['separators', 'outline']
105            else:
106                border_types = [draw_borders]
107            self._draw_borders(border_types)
108        if self.draw_bookmarks:
109            self._draw_bookmarks()
110        elif self.draw_hints:
111            self._draw_hints()
112        elif self.draw_info:
113            self._draw_info(self.draw_info)
114
115    def _draw_borders(self, border_types):  # pylint: disable=too-many-branches
116        win = self.win
117
118        self.color('in_browser', 'border')
119
120        left_start = 0
121        right_end = self.wid - 1
122
123        for child in self.columns:
124            if not child.has_preview():
125                left_start = child.x + child.wid
126            else:
127                break
128
129        # Shift the rightmost vertical line to the left to create a padding,
130        # but only when padding_right is on, the preview column is collapsed
131        # and we did not open the pager to "zoom" in to the file.
132        if self.settings.padding_right and not self.pager.visible and self.is_collapsed:
133            right_end = self.columns[-1].x - 1
134            if right_end < left_start:
135                right_end = self.wid - 1
136
137        # Draw horizontal lines and the leftmost vertical line
138        if 'outline' in border_types:
139            try:
140                # pylint: disable=no-member
141                win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
142                win.hline(self.hei - 1, left_start, curses.ACS_HLINE, right_end - left_start)
143                win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)
144                # pylint: enable=no-member
145            except curses.error:
146                pass
147
148        # Draw the vertical lines in the middle
149        if 'separators' in border_types:
150            for child in self.columns[:-1]:
151                if not child.has_preview():
152                    continue
153                if child.main_column and self.pager.visible:
154                    # If we "zoom in" with the pager, we have to
155                    # skip the between main_column and pager.
156                    break
157                x = child.x + child.wid
158                y = self.hei - 1
159                try:
160                    # pylint: disable=no-member
161                    win.vline(1, x, curses.ACS_VLINE, y - 1)
162                    if 'outline' in border_types:
163                        self.addch(0, x, curses.ACS_TTEE, 0)
164                        self.addch(y, x, curses.ACS_BTEE, 0)
165                    else:
166                        self.addch(0, x, curses.ACS_VLINE, 0)
167                        self.addch(y, x, curses.ACS_VLINE, 0)
168                    # pylint: enable=no-member
169                except curses.error:
170                    # in case it's off the boundaries
171                    pass
172
173        if 'outline' in border_types:
174            # Draw the last vertical line
175            try:
176                # pylint: disable=no-member
177                win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
178                # pylint: enable=no-member
179            except curses.error:
180                pass
181
182        if 'outline' in border_types:
183            # pylint: disable=no-member
184            self.addch(0, left_start, curses.ACS_ULCORNER)
185            self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
186            self.addch(0, right_end, curses.ACS_URCORNER)
187            self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
188            # pylint: enable=no-member
189
190    def _collapse(self):
191        # Should the last column be cut off? (Because there is no preview)
192        if not self.settings.collapse_preview or not self.preview \
193                or not self.stretch_ratios:
194            return False
195        result = not self.columns[-1].has_preview()
196        target = self.columns[-1].target
197        if not result and target and target.is_file:
198            if self.fm.settings.preview_script and \
199                    self.fm.settings.use_preview_script:
200                try:
201                    result = not self.fm.previews[target.realpath]['foundpreview']
202                except KeyError:
203                    return self.old_collapse
204
205        self.old_collapse = result
206        return result
207
208    def resize(self, y, x, hei=None, wid=None):
209        """Resize all the columns according to the given ratio"""
210        ViewBase.resize(self, y, x, hei, wid)
211
212        border_type = self.settings.draw_borders.lower()
213        if border_type in ['outline', 'both', 'true']:
214            pad = 1
215        else:
216            pad = 0
217        left = pad
218        self.is_collapsed = self._collapse()
219        if self.is_collapsed:
220            generator = enumerate(self.stretch_ratios)
221        else:
222            generator = enumerate(self.ratios)
223
224        last_i = len(self.ratios) - 1
225
226        for i, ratio in generator:
227            wid = int(ratio * self.wid)
228
229            cut_off = self.is_collapsed and not self.settings.padding_right
230            if i == last_i:
231                if not cut_off:
232                    wid = int(self.wid - left + 1 - pad)
233                else:
234                    self.columns[i].resize(pad, max(0, left - 1), hei - pad * 2, 1)
235                    self.columns[i].visible = False
236                    continue
237
238            if i == last_i - 1:
239                self.pager.resize(pad, left, hei - pad * 2, max(1, self.wid - left - pad))
240
241                if cut_off:
242                    self.columns[i].resize(pad, left, hei - pad * 2, max(1, self.wid - left - pad))
243                    continue
244
245            try:
246                self.columns[i].resize(pad, left, hei - pad * 2, max(1, wid - 1))
247            except KeyError:
248                pass
249
250            left += wid
251
252    def open_pager(self):
253        self.pager.visible = True
254        self.pager.focused = True
255        self.need_clear = True
256        self.pager.open()
257        try:
258            self.columns[-1].visible = False
259            self.columns[-2].visible = False
260        except IndexError:
261            pass
262
263    def close_pager(self):
264        self.pager.visible = False
265        self.pager.focused = False
266        self.need_clear = True
267        self.pager.close()
268        try:
269            self.columns[-1].visible = True
270            self.columns[-2].visible = True
271        except IndexError:
272            pass
273
274    def poke(self):
275        ViewBase.poke(self)
276
277        # Show the preview column when it has a preview but has
278        # been hidden (e.g. because of padding_right = False)
279        if not self.columns[-1].visible and self.columns[-1].has_preview() \
280                and not self.pager.visible:
281            self.columns[-1].visible = True
282
283        if self.preview and self.is_collapsed != self._collapse():
284            if self.fm.settings.preview_files:
285                # force clearing the image when resizing preview column
286                self.columns[-1].clear_image(force=True)
287            self.resize(self.y, self.x, self.hei, self.wid)
288
289        if self.old_draw_borders != self.settings.draw_borders:
290            self.resize(self.y, self.x, self.hei, self.wid)
291            self.old_draw_borders = self.settings.draw_borders
292