# Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com>
# This software is distributed under the terms of the GNU GPL version 3.
"""The BrowserView manages a set of BrowserColumns."""
import curses, _curses
from ranger.ext.signals import Signal
from ranger.ext.keybinding_parser import key_to_string
from . import Widget
from .browsercolumn import BrowserColumn
from .pager import Pager
from ..displayable import DisplayableContainer
class BrowserView(Widget, DisplayableContainer):
ratios = None
preview = True
is_collapsed = False
draw_bookmarks = False
stretch_ratios = None
need_clear = False
old_collapse = False
draw_hints = False
draw_info = False
def __init__(self, win, ratios, preview = True):
DisplayableContainer.__init__(self, win)
self.preview = preview
self.columns = []
self.pager = Pager(self.win, embedded=True)
self.pager.visible = False
self.add_child(self.pager)
self.change_ratios(ratios)
for option in ('preview_directories', 'preview_files'):
self.settings.signal_bind('setopt.' + option,
self._request_clear_if_has_borders, weak=True)
self.fm.signal_bind('move', self.request_clear)
self.settings.signal_bind('setopt.column_ratios', self.request_clear)
def change_ratios(self, ratios):
if isinstance(ratios, Signal):
ratios = ratios.value
for column in self.columns:
column.destroy()
self.remove_child(column)
self.columns = []
ratio_sum = float(sum(ratios))
self.ratios = tuple(x / ratio_sum for x in ratios)
last = 0.1 if self.settings.padding_right else 0
if len(self.ratios) >= 2:
self.stretch_ratios = self.ratios[:-2] + \
((self.ratios[-2] + self.ratios[-1] * 1.0 - last),
(self.ratios[-1] * last))
offset = 1 - len(ratios)
if self.preview: offset += 1
for level in range(len(ratios)):
fl = BrowserColumn(self.win, level + offset)
self.add_child(fl)
self.columns.append(fl)
try:
self.main_column = self.columns[self.preview and -2 or -1]
except IndexError:
self.main_column = None
else:
self.main_column.display_infostring = True
self.main_column.main_column = True
self.resize(self.y, self.x, self.hei, self.wid)
def _request_clear_if_has_borders(self):
if self.settings.draw_borders:
self.request_clear()
def request_clear(self):
self.need_clear = True
def draw(self):
if self.need_clear:
self.win.erase()
self.need_redraw = True
self.need_clear = False
for tab in self.fm.tabs.values():
directory = tab.thisdir
if directory:
directory.load_content_if_outdated()
directory.use()
DisplayableContainer.draw(self)
if self.settings.draw_borders:
self._draw_borders()
if self.draw_bookmarks:
self._draw_bookmarks()
elif self.draw_hints:
self._draw_hints()
elif self.draw_info:
self._draw_info(self.draw_info)
def finalize(self):
if self.pager.visible:
try:
self.fm.ui.win.move(self.main_column.y, self.main_column.x)
except:
pass
self.pager.draw_image()
else:
try:
x = self.main_column.x
y = self.main_column.y + self.main_column.target.pointer\
- self.main_column.scroll_begin
self.fm.ui.win.move(y, x)
except:
pass
self.columns[-1].draw_image()
def _draw_borders(self):
win = self.win
self.color('in_browser', 'border')
left_start = 0
right_end = self.wid - 1
for child in self.columns:
if not child.has_preview():
left_start = child.x + child.wid
else:
break
if not self.pager.visible:
for child in reversed(self.columns):
if not child.has_preview():
right_end = child.x - 1
else:
break
if right_end < left_start:
right_end = self.wid - 1
try:
win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
win.hline(self.hei - 1, left_start, curses.ACS_HLINE,
right_end - left_start)
win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)
except _curses.error:
pass
for child in self.columns:
if not child.has_preview():
continue
if child.main_column and self.pager.visible:
win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
break
x = child.x + child.wid
y = self.hei - 1
try:
win.vline(1, x, curses.ACS_VLINE, y - 1)
win.addch(0, x, curses.ACS_TTEE, 0)
win.addch(y, x, curses.ACS_BTEE, 0)
except:
# in case it's off the boundaries
pass
self.addch(0, left_start, curses.ACS_ULCORNER)
self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
self.addch(0, right_end, curses.ACS_URCORNER)
self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
def _draw_bookmarks(self):
self.columns[-1].clear_image(force=True)
self.fm.bookmarks.update_if_outdated()
self.color_reset()
self.need_clear = True
sorted_bookmarks = sorted((item for item in self.fm.bookmarks \
if self.fm.settings.show_hidden_bookmarks or \
'/.' not in item[1].path), key=lambda t: t[0].lower())
hei = min(self.hei - 1, len(sorted_bookmarks))
ystart = self.hei - hei
maxlen = self.wid
self.addnstr(ystart - 1, 0, "mark path".ljust(self.wid), self.wid)
whitespace = " " * maxlen
for line, items in zip(range(self.hei-1), sorted_bookmarks):
key, mark = items
string = " " + key + " " + mark.path
self.addstr(ystart + line, 0, whitespace)
self.addnstr(ystart + line, 0, string, self.wid)
self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)
def _draw_info(self, lines):
self.columns[-1].clear_image(force=True)
self.need_clear = True
hei = min(self.hei - 1, len(lines))
ystart = self.hei - hei
i = ystart
whitespace = " " * self.wid
for line in lines:
if i >= self.hei:
break
self.addstr(i, 0, whitespace)
self.addnstr(i, 0, line, self.wid)
i += 1
def _draw_hints(self):
self.columns[-1].clear_image(force=True)
self.need_clear = True
hints = []
for k, v in self.fm.ui.keybuffer.pointer.items():
k = key_to_string(k)
if isinstance(v, dict):
text = '...'
else:
text = v
if text.startswith('hint') or text.startswith('chain hint'):
continue
hints.append((k, text))
hints.sort(key=lambda t: t[1])
hei = min(self.hei - 1, len(hints))
ystart = self.hei - hei
self.addnstr(ystart - 1, 0, "key command".ljust(self.wid),
self.wid)
try:
self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)
except:
pass
whitespace = " " * self.wid
i = ystart
for key, cmd in hints:
string = " " + key.ljust(11) + " " + cmd
self.addstr(i, 0, whitespace)
self.addnstr(i, 0, string, self.wid)
i += 1
def _collapse(self):
# Should the last column be cut off? (Because there is no preview)
if not self.settings.collapse_preview or not self.preview \
or not self.stretch_ratios:
return False
result = not self.columns[-1].has_preview()
target = self.columns[-1].target
if not result and target and target.is_file:
if target.image and self.fm.settings.preview_images:
result = False # don't collapse when drawing images
elif self.fm.settings.preview_script and \
self.fm.settings.use_preview_script:
try:
result = not self.fm.previews[target.realpath]['foundpreview']
except:
return self.old_collapse
self.old_collapse = result
return result
def resize(self, y, x, hei, wid):
"""Resize all the columns according to the given ratio"""
DisplayableContainer.resize(self, y, x, hei, wid)
borders = self.settings.draw_borders
pad = 1 if borders else 0
left = pad
self.is_collapsed = self._collapse()
if self.is_collapsed:
generator = enumerate(self.stretch_ratios)
else:
generator = enumerate(self.ratios)
last_i = len(self.ratios) - 1
for i, ratio in generator:
wid = int(ratio * self.wid)
cut_off = self.is_collapsed and not self.settings.padding_right
if i == last_i:
if not cut_off:
wid = int(self.wid - left + 1 - pad)
else:
self.columns[i].resize(pad, left - 1, hei - pad * 2, 1)
self.columns[i].visible = False
continue
if i == last_i - 1:
self.pager.resize(pad, left, hei - pad * 2, \
max(1, self.wid - left - pad))
if cut_off:
self.columns[i].resize(pad, left, hei - pad * 2, \
max(1, self.wid - left - pad))
continue
try:
self.columns[i].resize(pad, left, hei - pad * 2, \
max(1, wid - 1))
except KeyError:
pass
left += wid
def click(self, event):
if DisplayableContainer.click(self, event):
return True
direction = event.mouse_wheel_direction()
if direction:
self.main_column.scroll(direction)
return False
def open_pager(self):
self.pager.visible = True
self.pager.focused = True
self.need_clear = True
self.pager.open()
try:
self.columns[-1].visible = False
self.columns[-2].visible = False
except IndexError:
pass
def close_pager(self):
self.pager.visible = False
self.pager.focused = False
self.need_clear = True
self.pager.close()
try:
self.columns[-1].visible = True
self.columns[-2].visible = True
except IndexError:
pass
def poke(self):
DisplayableContainer.poke(self)
# Show the preview column when it has a preview but has
# been hidden (e.g. because of padding_right = False)
if not self.pager.visible and not self.columns[-1].visible and \
self.columns[-1].target and self.columns[-1].target.is_directory \
or self.columns[-1].has_preview() and not self.pager.visible:
self.columns[-1].visible = True
if self.preview and self.is_collapsed != self._collapse():
if (self.fm.settings.preview_images and
self.fm.settings.preview_files):
# force clearing the image when resizing preview column
self.columns[-1].clear_image(force=True)
self.resize(self.y, self.x, self.hei, self.wid)