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