diff options
author | hut <hut@lavabit.com> | 2013-02-10 03:28:06 +0100 |
---|---|---|
committer | hut <hut@lavabit.com> | 2013-02-10 03:35:27 +0100 |
commit | d1a1173ddc315f21a3d468f43ac55aa43d31883d (patch) | |
tree | 10d728b37294856eb21e6e962089ac38507d868c /ranger/gui | |
parent | 184e84284d2f4e48631a4b94b87ba76126470206 (diff) | |
download | ranger-d1a1173ddc315f21a3d468f43ac55aa43d31883d.tar.gz |
replaced tabs with 4 spaces in all python files
PEP 8 (Style Guide for Python Code) suggests the use of 4 spaces: http://www.python.org/dev/peps/pep-0008/#indentation If you need to use tools like "git blame", you can use the -w option to ignore this commit entirely. Patches will continue to work if you substitute tabs with 4 spaces everywhere except in the Makefile.
Diffstat (limited to 'ranger/gui')
-rw-r--r-- | ranger/gui/ansi.py | 282 | ||||
-rw-r--r-- | ranger/gui/bar.py | 242 | ||||
-rw-r--r-- | ranger/gui/color.py | 26 | ||||
-rw-r--r-- | ranger/gui/colorscheme.py | 196 | ||||
-rw-r--r-- | ranger/gui/context.py | 40 | ||||
-rw-r--r-- | ranger/gui/curses_shortcuts.py | 110 | ||||
-rw-r--r-- | ranger/gui/displayable.py | 612 | ||||
-rw-r--r-- | ranger/gui/mouse_event.py | 100 | ||||
-rw-r--r-- | ranger/gui/ui.py | 710 | ||||
-rw-r--r-- | ranger/gui/widgets/__init__.py | 8 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 758 | ||||
-rw-r--r-- | ranger/gui/widgets/browserview.py | 662 | ||||
-rw-r--r-- | ranger/gui/widgets/console.py | 838 | ||||
-rw-r--r-- | ranger/gui/widgets/pager.py | 398 | ||||
-rw-r--r-- | ranger/gui/widgets/statusbar.py | 546 | ||||
-rw-r--r-- | ranger/gui/widgets/taskview.py | 168 | ||||
-rw-r--r-- | ranger/gui/widgets/titlebar.py | 278 |
17 files changed, 2987 insertions, 2987 deletions
diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py index 1b693c7c..7019c6fb 100644 --- a/ranger/gui/ansi.py +++ b/ranger/gui/ansi.py @@ -14,154 +14,154 @@ codesplit_re = re.compile('38;5;(\d+);|48;5;(\d+);|(\d*);') reset = '\x1b[0m' def split_ansi_from_text(ansi_text): - return ansi_re.split(ansi_text) + return ansi_re.split(ansi_text) # For information on the ANSI codes see # githttp://en.wikipedia.org/wiki/ANSI_escape_code def text_with_fg_bg_attr(ansi_text): - fg, bg, attr = -1, -1, 0 - for chunk in split_ansi_from_text(ansi_text): - if chunk and chunk[0] == '\x1b': - if chunk[-1] != 'm': - continue - match = re.match(r'^.\[(.*).$', chunk) - if not match: - # XXX I have no test case to determine what should happen here - continue - attr_args = match.group(1) - - # Convert arguments to attributes/colors - for x256fg, x256bg, arg in codesplit_re.findall(attr_args + ';'): - # first handle xterm256 codes - try: - if len(x256fg) > 0: # xterm256 foreground - fg = int(x256fg) - continue - elif len(x256bg) > 0: # xterm256 background - bg = int(x256bg) - continue - elif len(arg) > 0: # usual ansi code - n = int(arg) - else: # empty code means reset - n = 0 - except: - continue - - if n == 0: # reset colors and attributes - fg, bg, attr = -1, -1, 0 - - elif n == 1: # enable attribute - attr |= color.bold - elif n == 4: - attr |= color.underline - elif n == 5: - attr |= color.blink - elif n == 7: - attr |= color.reverse - elif n == 8: - attr |= color.invisible - - elif n == 22: # disable attribute - attr &= not color.bold - elif n == 24: - attr &= not color.underline - elif n == 25: - attr &= not color.blink - elif n == 27: - attr &= not color.reverse - elif n == 28: - attr &= not color.invisible - - elif n >= 30 and n <= 37: # 8 ansi foreground and background colors - fg = n - 30 - elif n == 39: - fg = -1 - elif n >= 40 and n <= 47: - bg = n - 40 - elif n == 49: - bg = -1 - - elif n >= 90 and n <= 97: # 8 aixterm high intensity colors (light but not bold) - fg = n - 90 + 8 - elif n == 99: - fg = -1 - elif n >= 100 and n <= 107: - bg = n - 100 + 8 - elif n == 109: - bg = -1 - - yield (fg, bg, attr) - - else: - yield chunk + fg, bg, attr = -1, -1, 0 + for chunk in split_ansi_from_text(ansi_text): + if chunk and chunk[0] == '\x1b': + if chunk[-1] != 'm': + continue + match = re.match(r'^.\[(.*).$', chunk) + if not match: + # XXX I have no test case to determine what should happen here + continue + attr_args = match.group(1) + + # Convert arguments to attributes/colors + for x256fg, x256bg, arg in codesplit_re.findall(attr_args + ';'): + # first handle xterm256 codes + try: + if len(x256fg) > 0: # xterm256 foreground + fg = int(x256fg) + continue + elif len(x256bg) > 0: # xterm256 background + bg = int(x256bg) + continue + elif len(arg) > 0: # usual ansi code + n = int(arg) + else: # empty code means reset + n = 0 + except: + continue + + if n == 0: # reset colors and attributes + fg, bg, attr = -1, -1, 0 + + elif n == 1: # enable attribute + attr |= color.bold + elif n == 4: + attr |= color.underline + elif n == 5: + attr |= color.blink + elif n == 7: + attr |= color.reverse + elif n == 8: + attr |= color.invisible + + elif n == 22: # disable attribute + attr &= not color.bold + elif n == 24: + attr &= not color.underline + elif n == 25: + attr &= not color.blink + elif n == 27: + attr &= not color.reverse + elif n == 28: + attr &= not color.invisible + + elif n >= 30 and n <= 37: # 8 ansi foreground and background colors + fg = n - 30 + elif n == 39: + fg = -1 + elif n >= 40 and n <= 47: + bg = n - 40 + elif n == 49: + bg = -1 + + elif n >= 90 and n <= 97: # 8 aixterm high intensity colors (light but not bold) + fg = n - 90 + 8 + elif n == 99: + fg = -1 + elif n >= 100 and n <= 107: + bg = n - 100 + 8 + elif n == 109: + bg = -1 + + yield (fg, bg, attr) + + else: + yield chunk def char_len(ansi_text): - """ - Count the number of visible characters. - - >>> char_len("\x1b[0;30;40mX\x1b[0m") - 1 - >>> char_len("\x1b[0;30;40mXY\x1b[0m") - 2 - >>> char_len("\x1b[0;30;40mX\x1b[0mY") - 2 - >>> char_len("hello") - 5 - >>> char_len("") - 0 - """ - return len(ansi_re.sub('', ansi_text)) + """ + Count the number of visible characters. + + >>> char_len("\x1b[0;30;40mX\x1b[0m") + 1 + >>> char_len("\x1b[0;30;40mXY\x1b[0m") + 2 + >>> char_len("\x1b[0;30;40mX\x1b[0mY") + 2 + >>> char_len("hello") + 5 + >>> char_len("") + 0 + """ + return len(ansi_re.sub('', ansi_text)) def char_slice(ansi_text, start, length): - """ - Slices a string with respect to ansi code sequences - - Acts as if the ansi codes aren't there, slices the text from the - given start point to the given length and adds the codes back in. - - >>> test_string = "abcde\x1b[30mfoo\x1b[31mbar\x1b[0mnormal" - >>> split_ansi_from_text(test_string) - ['abcde', '\\x1b[30m', 'foo', '\\x1b[31m', 'bar', '\\x1b[0m', 'normal'] - >>> char_slice(test_string, 1, 3) - 'bcd' - >>> char_slice(test_string, 5, 6) - '\\x1b[30mfoo\\x1b[31mbar' - >>> char_slice(test_string, 0, 8) - 'abcde\\x1b[30mfoo' - >>> char_slice(test_string, 4, 4) - 'e\\x1b[30mfoo' - >>> char_slice(test_string, 11, 100) - '\\x1b[0mnormal' - >>> char_slice(test_string, 9, 100) - '\\x1b[31mar\\x1b[0mnormal' - >>> char_slice(test_string, 9, 4) - '\\x1b[31mar\\x1b[0mno' - """ - chunks = [] - last_color = "" - pos = old_pos = 0 - for i, chunk in enumerate(split_ansi_from_text(ansi_text)): - if i % 2 == 1: - last_color = chunk - continue - - old_pos = pos - pos += len(chunk) - if pos <= start: - pass # seek - elif old_pos < start and pos >= start: - chunks.append(last_color) - chunks.append(chunk[start-old_pos:start-old_pos+length]) - elif pos > length + start: - chunks.append(last_color) - chunks.append(chunk[:start-old_pos+length]) - else: - chunks.append(last_color) - chunks.append(chunk) - if pos - start >= length: - break - return ''.join(chunks) + """ + Slices a string with respect to ansi code sequences + + Acts as if the ansi codes aren't there, slices the text from the + given start point to the given length and adds the codes back in. + + >>> test_string = "abcde\x1b[30mfoo\x1b[31mbar\x1b[0mnormal" + >>> split_ansi_from_text(test_string) + ['abcde', '\\x1b[30m', 'foo', '\\x1b[31m', 'bar', '\\x1b[0m', 'normal'] + >>> char_slice(test_string, 1, 3) + 'bcd' + >>> char_slice(test_string, 5, 6) + '\\x1b[30mfoo\\x1b[31mbar' + >>> char_slice(test_string, 0, 8) + 'abcde\\x1b[30mfoo' + >>> char_slice(test_string, 4, 4) + 'e\\x1b[30mfoo' + >>> char_slice(test_string, 11, 100) + '\\x1b[0mnormal' + >>> char_slice(test_string, 9, 100) + '\\x1b[31mar\\x1b[0mnormal' + >>> char_slice(test_string, 9, 4) + '\\x1b[31mar\\x1b[0mno' + """ + chunks = [] + last_color = "" + pos = old_pos = 0 + for i, chunk in enumerate(split_ansi_from_text(ansi_text)): + if i % 2 == 1: + last_color = chunk + continue + + old_pos = pos + pos += len(chunk) + if pos <= start: + pass # seek + elif old_pos < start and pos >= start: + chunks.append(last_color) + chunks.append(chunk[start-old_pos:start-old_pos+length]) + elif pos > length + start: + chunks.append(last_color) + chunks.append(chunk[:start-old_pos+length]) + else: + chunks.append(last_color) + chunks.append(chunk) + if pos - start >= length: + break + return ''.join(chunks) if __name__ == '__main__': - import doctest - doctest.testmod() + import doctest + doctest.testmod() diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py index ae07dd35..a6596bf5 100644 --- a/ranger/gui/bar.py +++ b/ranger/gui/bar.py @@ -6,134 +6,134 @@ import sys PY3 = sys.version > '3' class Bar(object): - left = None - right = None - gap = None - - def __init__(self, base_color_tag): - self.left = BarSide(base_color_tag) - self.right = BarSide(base_color_tag) - self.gap = BarSide(base_color_tag) - - def add(self, *a, **kw): - self.left.add(*a, **kw) - - def addright(self, *a, **kw): - self.right.add(*a, **kw) - - def sumsize(self): - return self.left.sumsize() + self.right.sumsize() - - def fixedsize(self): - return self.left.fixedsize() + self.right.fixedsize() - - def shrink_by_removing(self, wid): - leftsize = self.left.sumsize() - rightsize = self.right.sumsize() - sumsize = leftsize + rightsize - - # remove elemets from the left until it fits - if sumsize > wid: - while len(self.left) > 0: - leftsize -= len(self.left.pop(-1)) - if leftsize + rightsize <= wid: - break - sumsize = leftsize + rightsize - - # remove elemets from the right until it fits - if sumsize > wid: - while len(self.right) > 0: - rightsize -= len(self.right.pop(0)) - if leftsize + rightsize <= wid: - break - sumsize = leftsize + rightsize - - if sumsize < wid: - self.fill_gap(' ', (wid - sumsize), gapwidth=True) - - def shrink_from_the_left(self, wid): - fixedsize = self.fixedsize() - if wid < fixedsize: - raise ValueError("Cannot shrink down to that size by cutting") - leftsize = self.left.sumsize() - rightsize = self.right.sumsize() - oversize = leftsize + rightsize - wid - if oversize <= 0: - return self.fill_gap(' ', wid, gapwidth=False) - - # Shrink items to a minimum size until there is enough room. - for item in self.left: - if not item.fixed: - itemlen = len(item) - if oversize > itemlen - item.min_size: - item.cut_off_to(item.min_size) - oversize -= (itemlen - item.min_size) - else: - item.cut_off(oversize) - break - - def fill_gap(self, char, wid, gapwidth=False): - del self.gap[:] - - if not gapwidth: - wid = wid - self.sumsize() - - if wid > 0: - self.gap.add(char * wid, 'space') - - def combine(self): - return self.left + self.gap + self.right + left = None + right = None + gap = None + + def __init__(self, base_color_tag): + self.left = BarSide(base_color_tag) + self.right = BarSide(base_color_tag) + self.gap = BarSide(base_color_tag) + + def add(self, *a, **kw): + self.left.add(*a, **kw) + + def addright(self, *a, **kw): + self.right.add(*a, **kw) + + def sumsize(self): + return self.left.sumsize() + self.right.sumsize() + + def fixedsize(self): + return self.left.fixedsize() + self.right.fixedsize() + + def shrink_by_removing(self, wid): + leftsize = self.left.sumsize() + rightsize = self.right.sumsize() + sumsize = leftsize + rightsize + + # remove elemets from the left until it fits + if sumsize > wid: + while len(self.left) > 0: + leftsize -= len(self.left.pop(-1)) + if leftsize + rightsize <= wid: + break + sumsize = leftsize + rightsize + + # remove elemets from the right until it fits + if sumsize > wid: + while len(self.right) > 0: + rightsize -= len(self.right.pop(0)) + if leftsize + rightsize <= wid: + break + sumsize = leftsize + rightsize + + if sumsize < wid: + self.fill_gap(' ', (wid - sumsize), gapwidth=True) + + def shrink_from_the_left(self, wid): + fixedsize = self.fixedsize() + if wid < fixedsize: + raise ValueError("Cannot shrink down to that size by cutting") + leftsize = self.left.sumsize() + rightsize = self.right.sumsize() + oversize = leftsize + rightsize - wid + if oversize <= 0: + return self.fill_gap(' ', wid, gapwidth=False) + + # Shrink items to a minimum size until there is enough room. + for item in self.left: + if not item.fixed: + itemlen = len(item) + if oversize > itemlen - item.min_size: + item.cut_off_to(item.min_size) + oversize -= (itemlen - item.min_size) + else: + item.cut_off(oversize) + break + + def fill_gap(self, char, wid, gapwidth=False): + del self.gap[:] + + if not gapwidth: + wid = wid - self.sumsize() + + if wid > 0: + self.gap.add(char * wid, 'space') + + def combine(self): + return self.left + self.gap + self.right class BarSide(list): - def __init__(self, base_color_tag): - self.base_color_tag = base_color_tag + def __init__(self, base_color_tag): + self.base_color_tag = base_color_tag - def add(self, string, *lst, **kw): - cs = ColoredString(string, self.base_color_tag, *lst) - cs.__dict__.update(kw) - self.append(cs) + def add(self, string, *lst, **kw): + cs = ColoredString(string, self.base_color_tag, *lst) + cs.__dict__.update(kw) + self.append(cs) - def add_space(self, n=1): - self.add(' ' * n, 'space') + def add_space(self, n=1): + self.add(' ' * n, 'space') - def sumsize(self): - return sum(len(item) for item in self) + def sumsize(self): + return sum(len(item) for item in self) - def fixedsize(self): - n = 0 - for item in self: - if item.fixed: - n += len(item) - else: - n += item.min_size - return n + def fixedsize(self): + n = 0 + for item in self: + if item.fixed: + n += len(item) + else: + n += item.min_size + return n class ColoredString(object): - def __init__(self, string, *lst): - self.string = WideString(string) - self.lst = lst - self.fixed = False - if not len(string): - self.min_size = 0 - elif PY3: - self.min_size = utf_char_width(string[0]) - else: - self.min_size = utf_char_width(self.string.chars[0].decode('utf-8')) - - def cut_off(self, n): - if n >= 1: - self.string = self.string[:-n] - - def cut_off_to(self, n): - if n < self.min_size: - self.string = self.string[:self.min_size] - elif n < len(self.string): - self.string = self.string[:n] - - def __len__(self): - return len(self.string) - - def __str__(self): - return str(self.string) + def __init__(self, string, *lst): + self.string = WideString(string) + self.lst = lst + self.fixed = False + if not len(string): + self.min_size = 0 + elif PY3: + self.min_size = utf_char_width(string[0]) + else: + self.min_size = utf_char_width(self.string.chars[0].decode('utf-8')) + + def cut_off(self, n): + if n >= 1: + self.string = self.string[:-n] + + def cut_off_to(self, n): + if n < self.min_size: + self.string = self.string[:self.min_size] + elif n < len(self.string): + self.string = self.string[:n] + + def __len__(self): + return len(self.string) + + def __str__(self): + return str(self.string) diff --git a/ranger/gui/color.py b/ranger/gui/color.py index 037ff5c1..ab986b55 100644 --- a/ranger/gui/color.py +++ b/ranger/gui/color.py @@ -19,18 +19,18 @@ import curses COLOR_PAIRS = {10: 0} def get_color(fg, bg): - """ - Returns the curses color pair for the given fg/bg combination. - """ + """ + Returns the curses color pair for the given fg/bg combination. + """ - c = bg+2 + 9*(fg + 2) + c = bg+2 + 9*(fg + 2) - if c not in COLOR_PAIRS: - size = len(COLOR_PAIRS) - curses.init_pair(size, fg, bg) - COLOR_PAIRS[c] = size + if c not in COLOR_PAIRS: + size = len(COLOR_PAIRS) + curses.init_pair(size, fg, bg) + COLOR_PAIRS[c] = size - return COLOR_PAIRS[c] + return COLOR_PAIRS[c] black = curses.COLOR_BLACK blue = curses.COLOR_BLUE @@ -52,7 +52,7 @@ invisible = curses.A_INVIS default_colors = (default, default, normal) def remove_attr(integer, attribute): - """Remove an attribute from an integer""" - if integer & attribute: - return integer ^ attribute - return integer + """Remove an attribute from an integer""" + if integer & attribute: + return integer ^ attribute + return integer diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py index 34e59f55..0a142d52 100644 --- a/ranger/gui/colorscheme.py +++ b/ranger/gui/colorscheme.py @@ -37,103 +37,103 @@ from ranger.ext.cached_function import cached_function from ranger.ext.iter_tools import flatten class ColorScheme(object): - """ - This is the class that colorschemes must inherit from. - - it defines the get() method, which returns the color tuple - which fits to the given keys. - """ - - @cached_function - def get(self, *keys): - """ - Returns the (fg, bg, attr) for the given keys. - - Using this function rather than use() will cache all - colors for faster access. - """ - context = Context(keys) - color = self.use(context) - if len(color) != 3 or not all(isinstance(value, int) \ - for value in color): - raise ValueError("Bad Value from colorscheme. Need " - "a tuple of (foreground_color, background_color, attribute).") - return color - - @cached_function - def get_attr(self, *keys): - """ - Returns the curses attribute for the specified keys - - Ready to use for curses.setattr() - """ - fg, bg, attr = self.get(*flatten(keys)) - return attr | color_pair(get_color(fg, bg)) - - def use(self, context): - """ - Use the colorscheme to determine the (fg, bg, attr) tuple. - - Override this method in your own colorscheme. - """ - return (-1, -1, 0) + """ + This is the class that colorschemes must inherit from. + + it defines the get() method, which returns the color tuple + which fits to the given keys. + """ + + @cached_function + def get(self, *keys): + """ + Returns the (fg, bg, attr) for the given keys. + + Using this function rather than use() will cache all + colors for faster access. + """ + context = Context(keys) + color = self.use(context) + if len(color) != 3 or not all(isinstance(value, int) \ + for value in color): + raise ValueError("Bad Value from colorscheme. Need " + "a tuple of (foreground_color, background_color, attribute).") + return color + + @cached_function + def get_attr(self, *keys): + """ + Returns the curses attribute for the specified keys + + Ready to use for curses.setattr() + """ + fg, bg, attr = self.get(*flatten(keys)) + return attr | color_pair(get_color(fg, bg)) + + def use(self, context): + """ + Use the colorscheme to determine the (fg, bg, attr) tuple. + + Override this method in your own colorscheme. + """ + return (-1, -1, 0) def _colorscheme_name_to_class(signal): - # Find the colorscheme. First look in ~/.config/ranger/colorschemes, - # then at RANGERDIR/colorschemes. If the file contains a class - # named Scheme, it is used. Otherwise, an arbitrary other class - # is picked. - if isinstance(signal.value, ColorScheme): return - - if not signal.value: - signal.value = 'default' - - scheme_name = signal.value - usecustom = not ranger.arg.clean - - def exists(colorscheme): - return os.path.exists(colorscheme + '.py') - - def is_scheme(x): - try: - return issubclass(x, ColorScheme) - except: - return False - - # create ~/.config/ranger/colorschemes/__init__.py if it doesn't exist - if usecustom: - if os.path.exists(signal.fm.confpath('colorschemes')): - initpy = signal.fm.confpath('colorschemes', '__init__.py') - if not os.path.exists(initpy): - open(initpy, 'a').close() - - if usecustom and \ - exists(signal.fm.confpath('colorschemes', scheme_name)): - scheme_supermodule = 'colorschemes' - elif exists(signal.fm.relpath('colorschemes', scheme_name)): - scheme_supermodule = 'ranger.colorschemes' - usecustom = False - else: - scheme_supermodule = None # found no matching file. - - if scheme_supermodule is None: - if signal.previous and isinstance(signal.previous, ColorScheme): - signal.value = signal.previous - else: - signal.value = ColorScheme() - raise Exception("Cannot locate colorscheme `%s'" % scheme_name) - else: - if usecustom: allow_access_to_confdir(ranger.arg.confdir, True) - scheme_module = getattr(__import__(scheme_supermodule, - globals(), locals(), [scheme_name], 0), scheme_name) - if usecustom: allow_access_to_confdir(ranger.arg.confdir, False) - if hasattr(scheme_module, 'Scheme') \ - and is_scheme(scheme_module.Scheme): - signal.value = scheme_module.Scheme() - else: - for var in scheme_module.__dict__.values(): - if var != ColorScheme and is_scheme(var): - signal.value = var() - break - else: - raise Exception("The module contains no valid colorscheme!") + # Find the colorscheme. First look in ~/.config/ranger/colorschemes, + # then at RANGERDIR/colorschemes. If the file contains a class + # named Scheme, it is used. Otherwise, an arbitrary other class + # is picked. + if isinstance(signal.value, ColorScheme): return + + if not signal.value: + signal.value = 'default' + + scheme_name = signal.value + usecustom = not ranger.arg.clean + + def exists(colorscheme): + return os.path.exists(colorscheme + '.py') + + def is_scheme(x): + try: + return issubclass(x, ColorScheme) + except: + return False + + # create ~/.config/ranger/colorschemes/__init__.py if it doesn't exist + if usecustom: + if os.path.exists(signal.fm.confpath('colorschemes')): + initpy = signal.fm.confpath('colorschemes', '__init__.py') + if not os.path.exists(initpy): + open(initpy, 'a').close() + + if usecustom and \ + exists(signal.fm.confpath('colorschemes', scheme_name)): + scheme_supermodule = 'colorschemes' + elif exists(signal.fm.relpath('colorschemes', scheme_name)): + scheme_supermodule = 'ranger.colorschemes' + usecustom = False + else: + scheme_supermodule = None # found no matching file. + + if scheme_supermodule is None: + if signal.previous and isinstance(signal.previous, ColorScheme): + signal.value = signal.previous + else: + signal.value = ColorScheme() + raise Exception("Cannot locate colorscheme `%s'" % scheme_name) + else: + if usecustom: allow_access_to_confdir(ranger.arg.confdir, True) + scheme_module = getattr(__import__(scheme_supermodule, + globals(), locals(), [scheme_name], 0), scheme_name) + if usecustom: allow_access_to_confdir(ranger.arg.confdir, False) + if hasattr(scheme_module, 'Scheme') \ + and is_scheme(scheme_module.Scheme): + signal.value = scheme_module.Scheme() + else: + for var in scheme_module.__dict__.values(): + if var != ColorScheme and is_scheme(var): + signal.value = var() + break + else: + raise Exception("The module contains no valid colorscheme!") diff --git a/ranger/gui/context.py b/ranger/gui/context.py index cdccecde..c9e8104e 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -2,28 +2,28 @@ # This software is distributed under the terms of the GNU GPL version 3. CONTEXT_KEYS = ['reset', 'error', 'badinfo', - 'in_browser', 'in_statusbar', 'in_titlebar', 'in_console', - 'in_pager', 'in_taskview', - 'directory', 'file', 'hostname', - 'executable', 'media', 'link', 'fifo', 'socket', 'device', - 'video', 'audio', 'image', 'media', 'document', 'container', - 'selected', 'empty', 'main_column', 'message', 'background', - 'good', 'bad', - 'space', 'permissions', 'owner', 'group', 'mtime', 'nlink', - 'scroll', 'all', 'bot', 'top', 'percentage', 'filter', - 'marked', 'tagged', 'tag_marker', 'cut', 'copied', - 'help_markup', # COMPAT - 'seperator', 'key', 'special', 'border', # COMPAT - 'title', 'text', 'highlight', 'bars', 'quotes', 'tab', 'loaded', - 'keybuffer'] + 'in_browser', 'in_statusbar', 'in_titlebar', 'in_console', + 'in_pager', 'in_taskview', + 'directory', 'file', 'hostname', + 'executable', 'media', 'link', 'fifo', 'socket', 'device', + 'video', 'audio', 'image', 'media', 'document', 'container', + 'selected', 'empty', 'main_column', 'message', 'background', + 'good', 'bad', + 'space', 'permissions', 'owner', 'group', 'mtime', 'nlink', + 'scroll', 'all', 'bot', 'top', 'percentage', 'filter', + 'marked', 'tagged', 'tag_marker', 'cut', 'copied', + 'help_markup', # COMPAT + 'seperator', 'key', 'special', 'border', # COMPAT + 'title', 'text', 'highlight', 'bars', 'quotes', 'tab', 'loaded', + 'keybuffer'] class Context(object): - def __init__(self, keys): - # set all given keys to True - d = self.__dict__ - for key in keys: - d[key] = True + def __init__(self, keys): + # set all given keys to True + d = self.__dict__ + for key in keys: + d[key] = True # set all keys to False for key in CONTEXT_KEYS: - setattr(Context, key, False) + setattr(Context, key, False) diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py index 43b583a6..82640322 100644 --- a/ranger/gui/curses_shortcuts.py +++ b/ranger/gui/curses_shortcuts.py @@ -9,67 +9,67 @@ from ranger.gui.color import get_color from ranger.core.shared import SettingsAware def _fix_surrogates(args): - return [isinstance(arg, str) and arg.encode('utf-8', 'surrogateescape') - .decode('utf-8', 'replace') or arg for arg in args] + return [isinstance(arg, str) and arg.encode('utf-8', 'surrogateescape') + .decode('utf-8', 'replace') or arg for arg in args] class CursesShortcuts(SettingsAware): - """ - This class defines shortcuts to faciliate operations with curses. - color(*keys) -- sets the color associated with the keys from - the current colorscheme. - color_at(y, x, wid, *keys) -- sets the color at the given position - color_reset() -- resets the color to the default - addstr(*args) -- failsafe version of self.win.addstr(*args) - """ + """ + This class defines shortcuts to faciliate operations with curses. + color(*keys) -- sets the color associated with the keys from + the current colorscheme. + color_at(y, x, wid, *keys) -- sets the color at the given position + color_reset() -- resets the color to the default + addstr(*args) -- failsafe version of self.win.addstr(*args) + """ - def addstr(self, *args): - try: - self.win.addstr(*args) - except: - if len(args) > 1: - try: - self.win.addstr(*_fix_surrogates(args)) - except: - pass + def addstr(self, *args): + try: + self.win.addstr(*args) + except: + if len(args) > 1: + try: + self.win.addstr(*_fix_surrogates(args)) + except: + pass - def addnstr(self, *args): - try: - self.win.addnstr(*args) - except: - if len(args) > 2: - try: - self.win.addnstr(*_fix_surrogates(args)) - except: - pass + def addnstr(self, *args): + try: + self.win.addnstr(*args) + except: + if len(args) > 2: + try: + self.win.addnstr(*_fix_surrogates(args)) + except: + pass - def addch(self, *args): - try: - self.win.addch(*args) - except: - pass + def addch(self, *args): + try: + self.win.addch(*args) + except: + pass - def color(self, *keys): - """Change the colors from now on.""" - attr = self.settings.colorscheme.get_attr(*keys) - try: - self.win.attrset(attr) - except _curses.error: - pass + def color(self, *keys): + """Change the colors from now on.""" + attr = self.settings.colorscheme.get_attr(*keys) + try: + self.win.attrset(attr) + except _curses.error: + pass - def color_at(self, y, x, wid, *keys): - """Change the colors at the specified position""" - attr = self.settings.colorscheme.get_attr(*keys) - try: - self.win.chgat(y, x, wid, attr) - except _curses.error: - pass + def color_at(self, y, x, wid, *keys): + """Change the colors at the specified position""" + attr = self.settings.colorscheme.get_attr(*keys) + try: + self.win.chgat(y, x, wid, attr) + except _curses.error: + pass - def set_fg_bg_attr(self, fg, bg, attr): - try: - self.win.attrset(curses.color_pair(get_color(fg, bg)) | attr) - except _curses.error: - pass + def set_fg_bg_attr(self, fg, bg, attr): + try: + self.win.attrset(curses.color_pair(get_color(fg, bg)) | attr) + except _curses.error: + pass - def color_reset(self): - """Change the colors to the default colors""" - CursesShortcuts.color(self, 'reset') + def color_reset(self): + """Change the colors to the default colors""" + CursesShortcuts.color(self, 'reset') diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py index 28526d7a..e5431039 100644 --- a/ranger/gui/displayable.py +++ b/ranger/gui/displayable.py @@ -5,311 +5,311 @@ from ranger.core.shared import FileManagerAware, EnvironmentAware from ranger.gui.curses_shortcuts import CursesShortcuts class Displayable(EnvironmentAware, FileManagerAware, CursesShortcuts): - """ - Displayables are objects which are displayed on the screen. - - This is just the abstract class, defining basic operations - such as resizing, printing, changing colors. - Subclasses of displayable can extend these methods: - - draw() -- draw the object. Is only called if visible. - poke() -- is called just before draw(), even if not visible. - finalize() -- called after all objects finished drawing. - click(event) -- called with a MouseEvent. This is called on all - visible objects under the mouse, until one returns True. - press(key) -- called after a key press on focused objects. - destroy() -- called before destroying the displayable object - - Additionally, there are these methods: - - __contains__(item) -- is the item (y, x) inside the widget? - - These attributes are set: - - Modifiable: - focused -- Focused objects receive press() calls. - visible -- Visible objects receive draw() and finalize() calls - need_redraw -- Should the widget be redrawn? This variable may - be set at various places in the script and should eventually be - handled (and unset) in the draw() method. - - Read-Only: (i.e. reccomended not to change manually) - win -- the own curses window object - parent -- the parent (DisplayableContainer) object or None - x, y, wid, hei -- absolute coordinates and boundaries - settings, fm -- inherited shared variables - """ - - def __init__(self, win, env=None, fm=None, settings=None): - from ranger.gui.ui import UI - if env is not None: - self.env = env - if fm is not None: - self.fm = fm - if settings is not None: - self.settings = settings - - self.need_redraw = True - self.focused = False - self.visible = True - self.x = 0 - self.y = 0 - self.wid = 0 - self.hei = 0 - self.paryx = (0, 0) - self.parent = None - - self._old_visible = self.visible - - if win is not None: - if isinstance(self, UI): - self.win = win - else: - self.win = win.derwin(1, 1, 0, 0) - - def __nonzero__(self): - """Always True""" - return True - __bool__ = __nonzero__ - - def __contains__(self, item): - """ - Is item inside the boundaries? - item can be an iterable like [y, x] or an object with x and y methods. - """ - try: - y, x = item.y, item.x - except AttributeError: - try: - y, x = item - except (ValueError, TypeError): - return False - - return self.contains_point(y, x) - - def draw(self): - """ - Draw the object. Called on every main iteration if visible. - Containers should call draw() on their contained objects here. - Override this! - """ - - def destroy(self): - """ - Called when the object is destroyed. - Override this! - """ - - def contains_point(self, y, x): - """ - Test whether the point (with absolute coordinates) lies - within the boundaries of this object. - """ - return (x >= self.x and x < self.x + self.wid) and \ - (y >= self.y and y < self.y + self.hei) - - def click(self, event): - """ - Called when a mouse key is pressed and self.focused is True. - Override this! - """ - pass - - def press(self, key): - """ - Called when a key is pressed and self.focused is True. - Override this! - """ - pass - - def poke(self): - """Called before drawing, even if invisible""" - if self._old_visible != self.visible: - self._old_visible = self.visible - self.need_redraw = True - - if not self.visible: - self.win.erase() - - def finalize(self): - """ - Called after every displayable is done drawing. - Override this! - """ - pass - - def resize(self, y, x, hei=None, wid=None): - """Resize the widget""" - do_move = True - try: - maxy, maxx = self.fm.ui.termsize - except TypeError: - pass - else: - if hei is None: - hei = maxy - y - - if wid is None: - wid = maxx - x - - if x < 0 or y < 0: - self.fm.notify("Warning: Subwindow origin below zero for <%s> " - "(x = %d, y = %d)" % (self, x, y), bad=True) - - if x + wid > maxx or y + hei > maxy: - self.fm.notify("Warning: Subwindow size out of bounds for <%s> " - "(x = %d, y = %d, hei = %d, wid = %d)" % (self, - x, y, hei, wid), bad=True) - - window_is_cleared = False - - if hei != self.hei or wid != self.wid: - #log("resizing " + str(self)) - self.win.erase() - self.need_redraw = True - window_is_cleared = True - try: - self.win.resize(hei, wid) - except: - # Not enough space for resizing... - try: - self.win.mvderwin(0, 0) - do_move = True - self.win.resize(hei, wid) - except: - pass - #raise ValueError("Resizing Failed!") - - self.hei, self.wid = self.win.getmaxyx() - - if do_move or y != self.paryx[0] or x != self.paryx[1]: - if not window_is_cleared: - self.win.erase() - self.need_redraw = True - #log("moving " + str(self)) - try: - self.win.mvderwin(y, x) - except: - pass - - self.paryx = self.win.getparyx() - self.y, self.x = self.paryx - if self.parent: - self.y += self.parent.y - self.x += self.parent.x - - def __str__(self): - return self.__class__.__name__ + """ + Displayables are objects which are displayed on the screen. + + This is just the abstract class, defining basic operations + such as resizing, printing, changing colors. + Subclasses of displayable can extend these methods: + + draw() -- draw the object. Is only called if visible. + poke() -- is called just before draw(), even if not visible. + finalize() -- called after all objects finished drawing. + click(event) -- called with a MouseEvent. This is called on all + visible objects under the mouse, until one returns True. + press(key) -- called after a key press on focused objects. + destroy() -- called before destroying the displayable object + + Additionally, there are these methods: + + __contains__(item) -- is the item (y, x) inside the widget? + + These attributes are set: + + Modifiable: + focused -- Focused objects receive press() calls. + visible -- Visible objects receive draw() and finalize() calls + need_redraw -- Should the widget be redrawn? This variable may + be set at various places in the script and should eventually be + handled (and unset) in the draw() method. + + Read-Only: (i.e. reccomended not to change manually) + win -- the own curses window object + parent -- the parent (DisplayableContainer) object or None + x, y, wid, hei -- absolute coordinates and boundaries + settings, fm -- inherited shared variables + """ + + def __init__(self, win, env=None, fm=None, settings=None): + from ranger.gui.ui import UI + if env is not None: + self.env = env + if fm is not None: + self.fm = fm + if settings is not None: + self.settings = settings + + self.need_redraw = True + self.focused = False + self.visible = True + self.x = 0 + self.y = 0 + self.wid = 0 + self.hei = 0 + self.paryx = (0, 0) + self.parent = None + + self._old_visible = self.visible + + if win is not None: + if isinstance(self, UI): + self.win = win + else: + self.win = win.derwin(1, 1, 0, 0) + + def __nonzero__(self): + """Always True""" + return True + __bool__ = __nonzero__ + + def __contains__(self, item): + """ + Is item inside the boundaries? + item can be an iterable like [y, x] or an object with x and y methods. + """ + try: + y, x = item.y, item.x + except AttributeError: + try: + y, x = item + except (ValueError, TypeError): + return False + + return self.contains_point(y, x) + + def draw(self): + """ + Draw the object. Called on every main iteration if visible. + Containers should call draw() on their contained objects here. + Override this! + """ + + def destroy(self): + """ + Called when the object is destroyed. + Override this! + """ + + def contains_point(self, y, x): + """ + Test whether the point (with absolute coordinates) lies + within the boundaries of this object. + """ + return (x >= self.x and x < self.x + self.wid) and \ + (y >= self.y and y < self.y + self.hei) + + def click(self, event): + """ + Called when a mouse key is pressed and self.focused is True. + Override this! + """ + pass + + def press(self, key): + """ + Called when a key is pressed and self.focused is True. + Override this! + """ + pass + + def poke(self): + """Called before drawing, even if invisible""" + if self._old_visible != self.visible: + self._old_visible = self.visible + self.need_redraw = True + + if not self.visible: + self.win.erase() + + def finalize(self): + """ + Called after every displayable is done drawing. + Override this! + """ + pass + + def resize(self, y, x, hei=None, wid=None): + """Resize the widget""" + do_move = True + try: + maxy, maxx = self.fm.ui.termsize + except TypeError: + pass + else: + if hei is None: + hei = maxy - y + + if wid is None: + wid = maxx - x + + if x < 0 or y < 0: + self.fm.notify("Warning: Subwindow origin below zero for <%s> " + "(x = %d, y = %d)" % (self, x, y), bad=True) + + if x + wid > maxx or y + hei > maxy: + self.fm.notify("Warning: Subwindow size out of bounds for <%s> " + "(x = %d, y = %d, hei = %d, wid = %d)" % (self, + x, y, hei, wid), bad=True) + + window_is_cleared = False + + if hei != self.hei or wid != self.wid: + #log("resizing " + str(self)) + self.win.erase() + self.need_redraw = True + window_is_cleared = True + try: + self.win.resize(hei, wid) + except: + # Not enough space for resizing... + try: + self.win.mvderwin(0, 0) + do_move = True + self.win.resize(hei, wid) + except: + pass + #raise ValueError("Resizing Failed!") + + self.hei, self.wid = self.win.getmaxyx() + + if do_move or y != self.paryx[0] or x != self.paryx[1]: + if not window_is_cleared: + self.win.erase() + self.need_redraw = True + #log("moving " + str(self)) + try: + self.win.mvderwin(y, x) + except: + pass + + self.paryx = self.win.getparyx() + self.y, self.x = self.paryx + if self.parent: + self.y += self.parent.y + self.x += self.parent.x + + def __str__(self): + return self.__class__.__name__ class DisplayableContainer(Displayable): - """ - DisplayableContainers are Displayables which contain other Displayables. - - This is also an abstract class. The methods draw, poke, finalize, - click, press and destroy are extended here and will recursively - call the function on all contained objects. - - New methods: - - add_child(object) -- add the object to the container. - remove_child(object) -- remove the object from the container. - - New attributes: - - container -- a list with all contained objects (rw) - """ - - def __init__(self, win, env=None, fm=None, settings=None): - if env is not None: - self.env = env - if fm is not None: - self.fm = fm - if settings is not None: - self.settings = settings - - self.container = [] - - Displayable.__init__(self, win) - - # ------------------------------------ extended or overidden methods - - def poke(self): - """Recursively called on objects in container""" - Displayable.poke(self) - for displayable in self.container: - displayable.poke() - - def draw(self): - """Recursively called on visible objects in container""" - for displayable in self.container: - if self.need_redraw: - displayable.need_redraw = True - if displayable.visible: - displayable.draw() - - self.need_redraw = False - - def finalize(self): - """Recursively called on visible objects in container""" - for displayable in self.container: - if displayable.visible: - displayable.finalize() - - def press(self, key): - """Recursively called on objects in container""" - focused_obj = self._get_focused_obj() - - if focused_obj: - focused_obj.press(key) - return True - return False - - def click(self, event): - """Recursively called on objects in container""" - focused_obj = self._get_focused_obj() - if focused_obj and focused_obj.click(event): - return True - - for displayable in self.container: - if displayable.visible and event in displayable: - if displayable.click(event): - return True - - return False - - def destroy(self): - """Recursively called on objects in container""" - for displayable in self.container: - displayable.destroy() - - # ----------------------------------------------- new methods - - def add_child(self, obj): - """Add the objects to the container.""" - if obj.parent: - obj.parent.remove_child(obj) - self.container.append(obj) - obj.parent = self - - def remove_child(self, obj): - """Remove the object from the container.""" - try: - self.container.remove(obj) - except ValueError: - pass - else: - obj.parent = None - - def _get_focused_obj(self): - # Finds a focused displayable object in the container. - for displayable in self.container: - if displayable.focused: - return displayable - try: - obj = displayable._get_focused_obj() - except AttributeError: - pass - else: - if obj is not None: - return obj - return None + """ + DisplayableContainers are Displayables which contain other Displayables. + + This is also an abstract class. The methods draw, poke, finalize, + click, press and destroy are extended here and will recursively + call the function on all contained objects. + + New methods: + + add_child(object) -- add the object to the container. + remove_child(object) -- remove the object from the container. + + New attributes: + + container -- a list with all contained objects (rw) + """ + + def __init__(self, win, env=None, fm=None, settings=None): + if env is not None: + self.env = env + if fm is not None: + self.fm = fm + if settings is not None: + self.settings = settings + + self.container = [] + + Displayable.__init__(self, win) + + # ------------------------------------ extended or overidden methods + + def poke(self): + """Recursively called on objects in container""" + Displayable.poke(self) + for displayable in self.container: + displayable.poke() + + def draw(self): + """Recursively called on visible objects in container""" + for displayable in self.container: + if self.need_redraw: + displayable.need_redraw = True + if displayable.visible: + displayable.draw() + + self.need_redraw = False + + def finalize(self): + """Recursively called on visible objects in container""" + for displayable in self.container: + if displayable.visible: + displayable.finalize() + + def press(self, key): + """Recursively called on objects in container""" + focused_obj = self._get_focused_obj() + + if focused_obj: + focused_obj.press(key) + return True + return False + + def click(self, event): + """Recursively called on objects in container""" + focused_obj = self._get_focused_obj() + if focused_obj and focused_obj.click(event): + return True + + for displayable in self.container: + if displayable.visible and event in displayable: + if displayable.click(event): + return True + + return False + + def destroy(self): + """Recursively called on objects in container""" + for displayable in self.container: + displayable.destroy() + + # ----------------------------------------------- new methods + + def add_child(self, obj): + """Add the objects to the container.""" + if obj.parent: + obj.parent.remove_child(obj) + self.container.append(obj) + obj.parent = self + + def remove_child(self, obj): + """Remove the object from the container.""" + try: + self.container.remove(obj) + except ValueError: + pass + else: + obj.parent = None + + def _get_focused_obj(self): + # Finds a focused displayable object in the container. + for displayable in self.container: + if displayable.focused: + return displayable + try: + obj = displayable._get_focused_obj() + except AttributeError: + pass + else: + if obj is not None: + return obj + return None diff --git a/ranger/gui/mouse_event.py b/ranger/gui/mouse_event.py index ed370e54..1f157570 100644 --- a/ranger/gui/mouse_event.py +++ b/ranger/gui/mouse_event.py @@ -4,53 +4,53 @@ import curses class MouseEvent(object): - PRESSED = [ 0, - curses.BUTTON1_PRESSED, - curses.BUTTON2_PRESSED, - curses.BUTTON3_PRESSED, - curses.BUTTON4_PRESSED ] - CTRL_SCROLLWHEEL_MULTIPLIER = 5 - - def __init__(self, getmouse): - """Creates a MouseEvent object from the result of win.getmouse()""" - _, self.x, self.y, _, self.bstate = getmouse - - # x-values above ~220 suddenly became negative, apparently - # it's sufficient to add 0xFF to fix that error. - if self.x < 0: - self.x += 0xFF - - if self.y < 0: - self.y += 0xFF - - def pressed(self, n): - """Returns whether the mouse key n is pressed""" - try: - return (self.bstate & MouseEvent.PRESSED[n]) != 0 - except: - return False - - def mouse_wheel_direction(self): - """Returns the direction of the scroll action, 0 if there was none""" - # If the bstate > ALL_MOUSE_EVENTS, it's an invalid mouse button. - # I interpret invalid buttons as "scroll down" because all tested - # systems have a broken curses implementation and this is a workaround. - if self.bstate & curses.BUTTON4_PRESSED: - return self.ctrl() and -self.CTRL_SCROLLWHEEL_MULTIPLIER or -1 - elif self.bstate & curses.BUTTON2_PRESSED \ - or self.bstate > curses.ALL_MOUSE_EVENTS: - return self.ctrl() and self.CTRL_SCROLLWHEEL_MULTIPLIER or 1 - else: - return 0 - - def ctrl(self): - return self.bstate & curses.BUTTON_CTRL - - def alt(self): - return self.bstate & curses.BUTTON_ALT - - def shift(self): - return self.bstate & curses.BUTTON_SHIFT - - def key_invalid(self): - return self.bstate > curses.ALL_MOUSE_EVENTS + PRESSED = [ 0, + curses.BUTTON1_PRESSED, + curses.BUTTON2_PRESSED, + curses.BUTTON3_PRESSED, + curses.BUTTON4_PRESSED ] + CTRL_SCROLLWHEEL_MULTIPLIER = 5 + + def __init__(self, getmouse): + """Creates a MouseEvent object from the result of win.getmouse()""" + _, self.x, self.y, _, self.bstate = getmouse + + # x-values above ~220 suddenly became negative, apparently + # it's sufficient to add 0xFF to fix that error. + if self.x < 0: + self.x += 0xFF + + if self.y < 0: + self.y += 0xFF + + def pressed(self, n): + """Returns whether the mouse key n is pressed""" + try: + return (self.bstate & MouseEvent.PRESSED[n]) != 0 + except: + return False + + def mouse_wheel_direction(self): + """Returns the direction of the scroll action, 0 if there was none""" + # If the bstate > ALL_MOUSE_EVENTS, it's an invalid mouse button. + # I interpret invalid buttons as "scroll down" because all tested + # systems have a broken curses implementation and this is a workaround. + if self.bstate & curses.BUTTON4_PRESSED: + return self.ctrl() and -self.CTRL_SCROLLWHEEL_MULTIPLIER or -1 + elif self.bstate & curses.BUTTON2_PRESSED \ + or self.bstate > curses.ALL_MOUSE_EVENTS: + return self.ctrl() and self.CTRL_SCROLLWHEEL_MULTIPLIER or 1 + else: + return 0 + + def ctrl(self): + return self.bstate & curses.BUTTON_CTRL + + def alt(self): + return self.bstate & curses.BUTTON_ALT + + def shift(self): + return self.bstate & curses.BUTTON_SHIFT + + def key_invalid(self): + return self.bstate > curses.ALL_MOUSE_EVENTS diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index f01b7b17..f35b11bf 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -14,364 +14,364 @@ MOUSEMASK = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION _ASCII = ''.join(chr(c) for c in range(32, 127)) def ascii_only(string): - return ''.join(c if c in _ASCII else '?' for c in string) + return ''.join(c if c in _ASCII else '?' for c in string) def _setup_mouse(signal): - if signal['value']: - curses.mousemask(MOUSEMASK) - curses.mouseinterval(0) - - ## this line solves this problem: - ## If an action, following a mouse click, includes the - ## suspension and re-initializion of the ui (e.g. running a - ## file by clicking on its preview) and the next key is another - ## mouse click, the bstate of this mouse event will be invalid. - ## (atm, invalid bstates are recognized as scroll-down) - curses.ungetmouse(0,0,0,0,0) - else: - curses.mousemask(0) + if signal['value']: + curses.mousemask(MOUSEMASK) + curses.mouseinterval(0) + + ## this line solves this problem: + ## If an action, following a mouse click, includes the + ## suspension and re-initializion of the ui (e.g. running a + ## file by clicking on its preview) and the next key is another + ## mouse click, the bstate of this mouse event will be invalid. + ## (atm, invalid bstates are recognized as scroll-down) + curses.ungetmouse(0,0,0,0,0) + else: + curses.mousemask(0) # TODO: progress bar # TODO: branch view class UI(DisplayableContainer): - is_set_up = False - load_mode = False - is_on = False - termsize = None - - def __init__(self, env=None, fm=None): - self.keybuffer = KeyBuffer() - self.keymaps = KeyMaps(self.keybuffer) - - if fm is not None: - self.fm = fm - - def setup_curses(self): - os.environ['ESCDELAY'] = '25' # don't know a cleaner way - try: - self.win = curses.initscr() - except _curses.error as e: - if e.args[0] == "setupterm: could not find terminal": - os.environ['TERM'] = 'linux' - self.win = curses.initscr() - self.keymaps.use_keymap('browser') - DisplayableContainer.__init__(self, None) - - def initialize(self): - """initialize curses, then call setup (at the first time) and resize.""" - self.win.leaveok(0) - self.win.keypad(1) - self.load_mode = False - - curses.cbreak() - curses.noecho() - curses.halfdelay(20) - try: - curses.curs_set(int(bool(self.settings.show_cursor))) - except: - pass - curses.start_color() - curses.use_default_colors() - - self.settings.signal_bind('setopt.mouse_enabled', _setup_mouse) - _setup_mouse(dict(value=self.settings.mouse_enabled)) - - if not self.is_set_up: - self.is_set_up = True - self.setup() - self.win.addstr("loading...") - self.win.refresh() - curses.setupterm() - self._draw_title = curses.tigetflag('hs') # has_status_line - self.update_size() - self.is_on = True - - if self.settings.update_tmux_title: - sys.stdout.write("\033kranger\033\\") - sys.stdout.flush() - - def suspend(self): - """Turn off curses""" - self.win.keypad(0) - curses.nocbreak() - curses.echo() - try: - curses.curs_set(1) - except: - pass - if self.settings.mouse_enabled: - _setup_mouse(dict(value=False)) - curses.endwin() - self.is_on = False - - def set_load_mode(self, boolean): - boolean = bool(boolean) - if boolean != self.load_mode: - self.load_mode = boolean - - if boolean: - # don't wait for key presses in the load mode - curses.cbreak() - self.win.nodelay(1) - else: - self.win.nodelay(0) - curses.halfdelay(20) - - def destroy(self): - """Destroy all widgets and turn off curses""" - self.suspend() - DisplayableContainer.destroy(self) - - def handle_mouse(self): - """Handles mouse input""" - try: - event = MouseEvent(curses.getmouse()) - except _curses.error: - return - if not self.console.visible: - DisplayableContainer.click(self, event) - - def handle_key(self, key): - """Handles key input""" - - if hasattr(self, 'hint'): - self.hint() - - if key < 0: - self.keybuffer.clear() - - elif not DisplayableContainer.press(self, key): - self.keymaps.use_keymap('browser') - self.press(key) - - def press(self, key): - keybuffer = self.keybuffer - self.status.clear_message() - - keybuffer.add(key) - self.fm.hide_bookmarks() - self.browser.draw_hints = not keybuffer.finished_parsing \ - and keybuffer.finished_parsing_quantifier - - if keybuffer.result is not None: - try: - self.fm.execute_console(keybuffer.result, - wildcards=keybuffer.wildcards, - quantifier=keybuffer.quantifier) - finally: - if keybuffer.finished_parsing: - keybuffer.clear() - elif keybuffer.finished_parsing: - keybuffer.clear() - return False - return True - - def handle_keys(self, *keys): - for key in keys: - self.handle_key(key) - - def handle_input(self): - key = self.win.getch() - if key is 27 or key >= 128 and key < 256: - # Handle special keys like ALT+X or unicode here: - keys = [key] - previous_load_mode = self.load_mode - self.set_load_mode(True) - for n in range(4): - getkey = self.win.getch() - if getkey is not -1: - keys.append(getkey) - if len(keys) == 1: - keys.append(-1) - elif keys[0] == 27: - keys[0] = ALT_KEY - if self.settings.xterm_alt_key: - if len(keys) == 2 and keys[1] in range(127, 256): - if keys[0] == 195: - keys = [ALT_KEY, keys[1] - 64] - elif keys[0] == 194: - keys = [ALT_KEY, keys[1] - 128] - self.handle_keys(*keys) - self.set_load_mode(previous_load_mode) - if self.settings.flushinput and not self.console.visible: - curses.flushinp() - else: - # Handle simple key presses, CTRL+X, etc here: - if key > 0: - if self.settings.flushinput and not self.console.visible: - curses.flushinp() - if key == curses.KEY_MOUSE: - self.handle_mouse() - elif key == curses.KEY_RESIZE: - self.update_size() - else: - if not self.fm.input_is_blocked(): - self.handle_key(key) - - def setup(self): - """Build up the UI by initializing widgets.""" - from ranger.gui.widgets.browserview import BrowserView - from ranger.gui.widgets.titlebar import TitleBar - from ranger.gui.widgets.console import Console - from ranger.gui.widgets.statusbar import StatusBar - from ranger.gui.widgets.taskview import TaskView - from ranger.gui.widgets.pager import Pager - - # Create a title bar - self.titlebar = TitleBar(self.win) - self.add_child(self.titlebar) - - # Create the browser view - self.browser = BrowserView(self.win, self.settings.column_ratios) - self.settings.signal_bind('setopt.column_ratios', - self.browser.change_ratios) - self.add_child(self.browser) - - # Create the process manager - self.taskview = TaskView(self.win) - self.taskview.visible = False - self.add_child(self.taskview) - - # Create the status bar - self.status = StatusBar(self.win, self.browser.main_column) - self.add_child(self.status) - - # Create the console - self.console = Console(self.win) - self.add_child(self.console) - self.console.visible = False - - # Create the pager - self.pager = Pager(self.win) - self.pager.visible = False - self.add_child(self.pager) - - def redraw(self): - """Redraw all widgets""" - self.poke() - - # determine which widgets are shown - if self.console.wait_for_command_input or self.console.question_queue: - self.console.focused = True - self.console.visible = True - self.status.visible = False - else: - self.console.focused = False - self.console.visible = False - self.status.visible = True - - self.draw() - self.finalize() - - def redraw_window(self): - """Redraw the window. This only calls self.win.redrawwin().""" - self.win.erase() - self.win.redrawwin() - self.win.refresh() - self.win.redrawwin() - self.need_redraw = True - - def update_size(self): - """resize all widgets""" - self.termsize = self.win.getmaxyx() - y, x = self.termsize - - self.browser.resize(self.settings.status_bar_on_top and 2 or 1, 0, y - 2, x) - self.taskview.resize(1, 0, y - 2, x) - self.pager.resize(1, 0, y - 2, x) - self.titlebar.resize(0, 0, 1, x) - self.status.resize(self.settings.status_bar_on_top and 1 or y-1, 0, 1, x) - self.console.resize(y - 1, 0, 1, x) - - def draw(self): - """Draw all objects in the container""" - self.win.touchwin() - DisplayableContainer.draw(self) - if self._draw_title and self.settings.update_title: - cwd = self.fm.thisdir.path - if cwd.startswith(self.fm.home_path): - cwd = '~' + cwd[len(self.fm.home_path):] - if self.settings.shorten_title: - split = cwd.rsplit(os.sep, self.settings.shorten_title) - if os.sep in split[0]: - cwd = os.sep.join(split[1:]) - try: - fixed_cwd = cwd.encode('utf-8', 'surrogateescape'). \ - decode('utf-8', 'replace') - sys.stdout.write("%sranger:%s%s" % - (curses.tigetstr('tsl').decode('latin-1'), fixed_cwd, - curses.tigetstr('fsl').decode('latin-1'))) - sys.stdout.flush() - except: - pass - - self.win.refresh() - - def finalize(self): - """Finalize every object in container and refresh the window""" - DisplayableContainer.finalize(self) - self.win.refresh() - - def close_pager(self): - if self.console.visible: - self.console.focused = True - self.pager.close() - self.pager.visible = False - self.pager.focused = False - self.browser.visible = True - - def open_pager(self): - if self.console.focused: - self.console.focused = False - self.pager.open() - self.pager.visible = True - self.pager.focused = True - self.browser.visible = False - return self.pager - - def open_embedded_pager(self): - self.browser.open_pager() - for column in self.browser.columns: - if column == self.browser.main_column: - break - column.level_shift(amount=1) - return self.browser.pager - - def close_embedded_pager(self): - self.browser.close_pager() - for column in self.browser.columns: - column.level_restore() - - def open_console(self, string='', prompt=None, position=None): - if self.console.open(string, prompt=prompt, position=position): - self.status.msg = None - - def close_console(self): - self.console.close() - self.close_pager() - - def open_taskview(self): - self.pager.close() - self.pager.visible = False - self.pager.focused = False - self.console.visible = False - self.browser.visible = False - self.taskview.visible = True - self.taskview.focused = True - - def redraw_main_column(self): - self.browser.main_column.need_redraw = True - - def close_taskview(self): - self.taskview.visible = False - self.browser.visible = True - self.taskview.focused = False - - def throbber(self, string='.', remove=False): - if remove: - self.titlebar.throbber = type(self.titlebar).throbber - else: - self.titlebar.throbber = string - - def hint(self, text=None): - self.status.hint = text + is_set_up = False + load_mode = False + is_on = False + termsize = None + + def __init__(self, env=None, fm=None): + self.keybuffer = KeyBuffer() + self.keymaps = KeyMaps(self.keybuffer) + + if fm is not None: + self.fm = fm + + def setup_curses(self): + os.environ['ESCDELAY'] = '25' # don't know a cleaner way + try: + self.win = curses.initscr() + except _curses.error as e: + if e.args[0] == "setupterm: could not find terminal": + os.environ['TERM'] = 'linux' + self.win = curses.initscr() + self.keymaps.use_keymap('browser') + DisplayableContainer.__init__(self, None) + + def initialize(self): + """initialize curses, then call setup (at the first time) and resize.""" + self.win.leaveok(0) + self.win.keypad(1) + self.load_mode = False + + curses.cbreak() + curses.noecho() + curses.halfdelay(20) + try: + curses.curs_set(int(bool(self.settings.show_cursor))) + except: + pass + curses.start_color() + curses.use_default_colors() + + self.settings.signal_bind('setopt.mouse_enabled', _setup_mouse) + _setup_mouse(dict(value=self.settings.mouse_enabled)) + + if not self.is_set_up: + self.is_set_up = True + self.setup() + self.win.addstr("loading...") + self.win.refresh() + curses.setupterm() + self._draw_title = curses.tigetflag('hs') # has_status_line + self.update_size() + self.is_on = True + + if self.settings.update_tmux_title: + sys.stdout.write("\033kranger\033\\") + sys.stdout.flush() + + def suspend(self): + """Turn off curses""" + self.win.keypad(0) + curses.nocbreak() + curses.echo() + try: + curses.curs_set(1) + except: + pass + if self.settings.mouse_enabled: + _setup_mouse(dict(value=False)) + curses.endwin() + self.is_on = False + + def set_load_mode(self, boolean): + boolean = bool(boolean) + if boolean != self.load_mode: + self.load_mode = boolean + + if boolean: + # don't wait for key presses in the load mode + curses.cbreak() + self.win.nodelay(1) + else: + self.win.nodelay(0) + curses.halfdelay(20) + + def destroy(self): + """Destroy all widgets and turn off curses""" + self.suspend() + DisplayableContainer.destroy(self) + + def handle_mouse(self): + """Handles mouse input""" + try: + event = MouseEvent(curses.getmouse()) + except _curses.error: + return + if not self.console.visible: + DisplayableContainer.click(self, event) + + def handle_key(self, key): + """Handles key input""" + + if hasattr(self, 'hint'): + self.hint() + + if key < 0: + self.keybuffer.clear() + + elif not DisplayableContainer.press(self, key): + self.keymaps.use_keymap('browser') + self.press(key) + + def press(self, key): + keybuffer = self.keybuffer + self.status.clear_message() + + keybuffer.add(key) + self.fm.hide_bookmarks() + self.browser.draw_hints = not keybuffer.finished_parsing \ + and keybuffer.finished_parsing_quantifier + + if keybuffer.result is not None: + try: + self.fm.execute_console(keybuffer.result, + wildcards=keybuffer.wildcards, + quantifier=keybuffer.quantifier) + finally: + if keybuffer.finished_parsing: + keybuffer.clear() + elif keybuffer.finished_parsing: + keybuffer.clear() + return False + return True + + def handle_keys(self, *keys): + for key in keys: + self.handle_key(key) + + def handle_input(self): + key = self.win.getch() + if key is 27 or key >= 128 and key < 256: + # Handle special keys like ALT+X or unicode here: + keys = [key] + previous_load_mode = self.load_mode + self.set_load_mode(True) + for n in range(4): + getkey = self.win.getch() + if getkey is not -1: + keys.append(getkey) + if len(keys) == 1: + keys.append(-1) + elif keys[0] == 27: + keys[0] = ALT_KEY + if self.settings.xterm_alt_key: + if len(keys) == 2 and keys[1] in range(127, 256): + if keys[0] == 195: + keys = [ALT_KEY, keys[1] - 64] + elif keys[0] == 194: + keys = [ALT_KEY, keys[1] - 128] + self.handle_keys(*keys) + self.set_load_mode(previous_load_mode) + if self.settings.flushinput and not self.console.visible: + curses.flushinp() + else: + # Handle simple key presses, CTRL+X, etc here: + if key > 0: + if self.settings.flushinput and not self.console.visible: + curses.flushinp() + if key == curses.KEY_MOUSE: + self.handle_mouse() + elif key == curses.KEY_RESIZE: + self.update_size() + else: + if not self.fm.input_is_blocked(): + self.handle_key(key) + + def setup(self): + """Build up the UI by initializing widgets.""" + from ranger.gui.widgets.browserview import BrowserView + from ranger.gui.widgets.titlebar import TitleBar + from ranger.gui.widgets.console import Console + from ranger.gui.widgets.statusbar import StatusBar + from ranger.gui.widgets.taskview import TaskView + from ranger.gui.widgets.pager import Pager + + # Create a title bar + self.titlebar = TitleBar(self.win) + self.add_child(self.titlebar) + + # Create the browser view + self.browser = BrowserView(self.win, self.settings.column_ratios) + self.settings.signal_bind('setopt.column_ratios', + self.browser.change_ratios) + self.add_child(self.browser) + + # Create the process manager + self.taskview = TaskView(self.win) + self.taskview.visible = False + self.add_child(self.taskview) + + # Create the status bar + self.status = StatusBar(self.win, self.browser.main_column) + self.add_child(self.status) + + # Create the console + self.console = Console(self.win) + self.add_child(self.console) + self.console.visible = False + + # Create the pager + self.pager = Pager(self.win) + self.pager.visible = False + self.add_child(self.pager) + + def redraw(self): + """Redraw all widgets""" + self.poke() + + # determine which widgets are shown + if self.console.wait_for_command_input or self.console.question_queue: + self.console.focused = True + self.console.visible = True + self.status.visible = False + else: + self.console.focused = False + self.console.visible = False + self.status.visible = True + + self.draw() + self.finalize() + + def redraw_window(self): + """Redraw the window. This only calls self.win.redrawwin().""" + self.win.erase() + self.win.redrawwin() + self.win.refresh() + self.win.redrawwin() + self.need_redraw = True + + def update_size(self): + """resize all widgets""" + self.termsize = self.win.getmaxyx() + y, x = self.termsize + + self.browser.resize(self.settings.status_bar_on_top and 2 or 1, 0, y - 2, x) + self.taskview.resize(1, 0, y - 2, x) + self.pager.resize(1, 0, y - 2, x) + self.titlebar.resize(0, 0, 1, x) + self.status.resize(self.settings.status_bar_on_top and 1 or y-1, 0, 1, x) + self.console.resize(y - 1, 0, 1, x) + + def draw(self): + """Draw all objects in the container""" + self.win.touchwin() + DisplayableContainer.draw(self) + if self._draw_title and self.settings.update_title: + cwd = self.fm.thisdir.path + if cwd.startswith(self.fm.home_path): + cwd = '~' + cwd[len(self.fm.home_path):] + if self.settings.shorten_title: + split = cwd.rsplit(os.sep, self.settings.shorten_title) + if os.sep in split[0]: + cwd = os.sep.join(split[1:]) + try: + fixed_cwd = cwd.encode('utf-8', 'surrogateescape'). \ + decode('utf-8', 'replace') + sys.stdout.write("%sranger:%s%s" % + (curses.tigetstr('tsl').decode('latin-1'), fixed_cwd, + curses.tigetstr('fsl').decode('latin-1'))) + sys.stdout.flush() + except: + pass + + self.win.refresh() + + def finalize(self): + """Finalize every object in container and refresh the window""" + DisplayableContainer.finalize(self) + self.win.refresh() + + def close_pager(self): + if self.console.visible: + self.console.focused = True + self.pager.close() + self.pager.visible = False + self.pager.focused = False + self.browser.visible = True + + def open_pager(self): + if self.console.focused: + self.console.focused = False + self.pager.open() + self.pager.visible = True + self.pager.focused = True + self.browser.visible = False + return self.pager + + def open_embedded_pager(self): + self.browser.open_pager() + for column in self.browser.columns: + if column == self.browser.main_column: + break + column.level_shift(amount=1) + return self.browser.pager + + def close_embedded_pager(self): + self.browser.close_pager() + for column in self.browser.columns: + column.level_restore() + + def open_console(self, string='', prompt=None, position=None): + if self.console.open(string, prompt=prompt, position=position): + self.status.msg = None + + def close_console(self): + self.console.close() + self.close_pager() + + def open_taskview(self): + self.pager.close() + self.pager.visible = False + self.pager.focused = False + self.console.visible = False + self.browser.visible = False + self.taskview.visible = True + self.taskview.focused = True + + def redraw_main_column(self): + self.browser.main_column.need_redraw = True + + def close_taskview(self): + self.taskview.visible = False + self.browser.visible = True + self.taskview.focused = False + + def throbber(self, string='.', remove=False): + if remove: + self.titlebar.throbber = type(self.titlebar).throbber + else: + self.titlebar.throbber = string + + def hint(self, text=None): + self.status.hint = text diff --git a/ranger/gui/widgets/__init__.py b/ranger/gui/widgets/__init__.py index 82e592ee..2a930b6c 100644 --- a/ranger/gui/widgets/__init__.py +++ b/ranger/gui/widgets/__init__.py @@ -1,7 +1,7 @@ from ranger.gui.displayable import Displayable class Widget(Displayable): - """ - The Widget class defines no methods and only exists for - classification of widgets. - """ + """ + The Widget class defines no methods and only exists for + classification of widgets. + """ diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 3324d9a8..a6653070 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -13,382 +13,382 @@ from ranger.fsobject import BAD_INFO from ranger.ext.widestring import WideString class BrowserColumn(Pager): - main_column = False - display_infostring = False - scroll_begin = 0 - target = None - last_redraw_time = -1 - ellipsis = { False: '~', True: '…' } - - old_dir = None - old_thisfile = None - - def __init__(self, win, level): - """ - win = the curses window object of the BrowserView - level = what to display? - - level >0 => previews - level 0 => current file/directory - level <0 => parent directories - """ - Pager.__init__(self, win) - Widget.__init__(self, win) - self.level = level - self.original_level = level - - self.settings.signal_bind('setopt.display_size_in_main_column', - self.request_redraw, weak=True) - - def request_redraw(self): - self.need_redraw = True - - def resize(self, y, x, hei, wid): - Widget.resize(self, y, x, hei, wid) - - def click(self, event): - """Handle a MouseEvent""" - direction = event.mouse_wheel_direction() - if not (event.pressed(1) or event.pressed(3) or direction): - return False - - if self.target is None: - pass - - elif self.target.is_directory: - if self.target.accessible and self.target.content_loaded: - index = self.scroll_begin + event.y - self.y - - if direction: - if self.level == -1: - self.fm.move_parent(direction) - else: - return False - elif event.pressed(1): - if not self.main_column: - self.fm.enter_dir(self.target.path) - - if index < len(self.target): - self.fm.move(to=index) - elif event.pressed(3): - try: - clicked_file = self.target.files[index] - if clicked_file.is_directory: - self.fm.enter_dir(clicked_file.path) - elif self.level == 0: - self.fm.thisdir.move_to_obj(clicked_file) - self.fm.execute_file(clicked_file) - except: - pass - - else: - if self.level > 0 and not direction: - self.fm.move(right=0) - - return True - - def execute_curses_batch(self, line, commands): - """ - Executes a list of "commands" which can be easily cached. - - "commands" is a list of lists. Each element contains - a text and an attribute. First, the attribute will be - set with attrset, then the text is printed. - - Example: - execute_curses_batch(0, [["hello ", 0], ["world", curses.A_BOLD]]) - """ - try: - self.win.move(line, 0) - except: - return - for entry in commands: - text, attr = entry - self.addstr(text, attr) - - def has_preview(self): - if self.target is None: - return False - - if self.target.is_file: - if not self.target.has_preview(): - return False - - if self.target.is_directory: - if self.level > 0 and not self.settings.preview_directories: - return False - - return True - - def level_shift(self, amount): - self.level = self.original_level + amount - - def level_restore(self): - self.level = self.original_level - - def poke(self): - Widget.poke(self) - self.target = self.fm.thistab.at_level(self.level) - - def draw(self): - """Call either _draw_file() or _draw_directory()""" - if self.target != self.old_dir: - self.need_redraw = True - self.old_dir = self.target - - if self.target: # don't garbage collect this directory please - self.target.use() - - if self.target and self.target.is_directory \ - and (self.level <= 0 or self.settings.preview_directories): - if self.target.pointed_obj != self.old_thisfile: - self.need_redraw = True - self.old_thisfile = self.target.pointed_obj - - if self.target.load_content_if_outdated() \ - or self.target.sort_if_outdated() \ - or self.last_redraw_time < self.target.last_update_time: - self.need_redraw = True - - if self.need_redraw: - self.win.erase() - if self.target is None: - pass - elif self.target.is_file: - Pager.open(self) - self._draw_file() - elif self.target.is_directory: - self._draw_directory() - Widget.draw(self) - self.need_redraw = False - self.last_redraw_time = time() - - def _draw_file(self): - """Draw a preview of the file, if the settings allow it""" - self.win.move(0, 0) - if not self.target.accessible: - self.addnstr("not accessible", self.wid) - Pager.close(self) - return - - if self.target is None or not self.target.has_preview(): - Pager.close(self) - return - - if self.fm.settings.preview_images and self.target.image: - self.set_image(self.target.realpath) - Pager.draw(self) - else: - f = self.target.get_preview_source(self.wid, self.hei) - if f is None: - Pager.close(self) - else: - self.set_source(f) - Pager.draw(self) - - def _draw_directory(self): - """Draw the contents of a directory""" - if self.image: - self.image = None - self.need_clear_image = True - Pager.clear_image(self) - - if self.level > 0 and not self.settings.preview_directories: - return - - base_color = ['in_browser'] - - self.win.move(0, 0) - - if not self.target.content_loaded: - self.color(tuple(base_color)) - self.addnstr("...", self.wid) - self.color_reset() - return - - if self.main_column: - base_color.append('main_column') - - if not self.target.accessible: - self.color(tuple(base_color + ['error'])) - self.addnstr("not accessible", self.wid) - self.color_reset() - return - - if self.target.empty(): - self.color(tuple(base_color + ['empty'])) - self.addnstr("empty", self.wid) - self.color_reset() - return - - self._set_scroll_begin() - - copied = [f.path for f in self.fm.copy_buffer] - ellipsis = self.ellipsis[self.settings.unicode_ellipsis] - - selected_i = self.target.pointer - for line in range(self.hei): - i = line + self.scroll_begin - if line > self.hei: - break - - try: - drawn = self.target.files[i] - except IndexError: - break - - tagged = self.fm.tags and drawn.realpath in self.fm.tags - if tagged: - tagged_marker = self.fm.tags.marker(drawn.realpath) - else: - tagged_marker = " " - - key = (self.wid, selected_i == i, drawn.marked, self.main_column, - drawn.path in copied, tagged_marker, drawn.infostring, - self.fm.do_cut) - - if key in drawn.display_data: - self.execute_curses_batch(line, drawn.display_data[key]) - self.color_reset() - continue - - display_data = [] - drawn.display_data[key] = display_data - - if self.display_infostring and drawn.infostring \ - and self.settings.display_size_in_main_column: - infostring = str(drawn.infostring) + " " - else: - infostring = "" - - this_color = base_color + list(drawn.mimetype_tuple) - text = drawn.basename - - space = self.wid - len(infostring) - if self.main_column: - space -= 2 - elif self.settings.display_tags_in_all_columns: - space -= 1 - - if i == selected_i: - this_color.append('selected') - - if drawn.marked: - this_color.append('marked') - if self.main_column or self.settings.display_tags_in_all_columns: - text = " " + text - - if tagged: - this_color.append('tagged') - - if drawn.is_directory: - this_color.append('directory') - else: - this_color.append('file') - - if drawn.stat: - mode = drawn.stat.st_mode - if mode & stat.S_IXUSR: - this_color.append('executable') - if stat.S_ISFIFO(mode): - this_color.append('fifo') - if stat.S_ISSOCK(mode): - this_color.append('socket') - if drawn.is_device: - this_color.append('device') - - if drawn.path in copied: - this_color.append('cut' if self.fm.do_cut else 'copied') - - if drawn.is_link: - this_color.append('link') - this_color.append(drawn.exists and 'good' or 'bad') - - attr = self.settings.colorscheme.get_attr(*this_color) - - if (self.main_column or self.settings.display_tags_in_all_columns) \ - and tagged and self.wid > 2: - this_color.append('tag_marker') - tag_attr = self.settings.colorscheme.get_attr(*this_color) - display_data.append([tagged_marker, tag_attr]) - else: - text = " " + text - space += 1 - - wtext = WideString(text) - if len(wtext) > space: - wtext = wtext[:max(0, space - 1)] + ellipsis - text = str(wtext) - - display_data.append([text, attr]) - - padding = self.wid - len(wtext) - if tagged and (self.main_column or \ - self.settings.display_tags_in_all_columns): - padding -= 1 - if infostring: - if len(wtext) + 1 + len(infostring) > self.wid: - pass - else: - padding -= len(infostring) - padding = max(0, padding) - infostring = (" " * padding) + infostring - display_data.append([infostring, attr]) - else: - display_data.append([" " * max(0, padding), attr]) - - self.execute_curses_batch(line, display_data) - self.color_reset() - - def _get_scroll_begin(self): - """Determines scroll_begin (the position of the first displayed file)""" - offset = self.settings.scroll_offset - dirsize = len(self.target) - winsize = self.hei - halfwinsize = winsize // 2 - index = self.target.pointer or 0 - original = self.target.scroll_begin - projected = index - original - - upper_limit = winsize - 1 - offset - lower_limit = offset - - if original < 0: - return 0 - - if dirsize < winsize: - return 0 - - if halfwinsize < offset: - return min( dirsize - winsize, max( 0, index - halfwinsize )) - - if original > dirsize - winsize: - self.target.scroll_begin = dirsize - winsize - return self._get_scroll_begin() - - if projected < upper_limit and projected > lower_limit: - return original - - if projected > upper_limit: - return min( dirsize - winsize, - original + (projected - upper_limit)) - - if projected < upper_limit: - return max( 0, - original - (lower_limit - projected)) - - return original - - def _set_scroll_begin(self): - """Updates the scroll_begin value""" - self.scroll_begin = self._get_scroll_begin() - self.target.scroll_begin = self.scroll_begin - - def scroll(self, n): - """scroll down by n lines""" - self.need_redraw = True - self.target.move(down=n) - self.target.scroll_begin += 3 * n - - def __str__(self): - return self.__class__.__name__ + ' at level ' + str(self.level) + main_column = False + display_infostring = False + scroll_begin = 0 + target = None + last_redraw_time = -1 + ellipsis = { False: '~', True: '…' } + + old_dir = None + old_thisfile = None + + def __init__(self, win, level): + """ + win = the curses window object of the BrowserView + level = what to display? + + level >0 => previews + level 0 => current file/directory + level <0 => parent directories + """ + Pager.__init__(self, win) + Widget.__init__(self, win) + self.level = level + self.original_level = level + + self.settings.signal_bind('setopt.display_size_in_main_column', + self.request_redraw, weak=True) + + def request_redraw(self): + self.need_redraw = True + + def resize(self, y, x, hei, wid): + Widget.resize(self, y, x, hei, wid) + + def click(self, event): + """Handle a MouseEvent""" + direction = event.mouse_wheel_direction() + if not (event.pressed(1) or event.pressed(3) or direction): + return False + + if self.target is None: + pass + + elif self.target.is_directory: + if self.target.accessible and self.target.content_loaded: + index = self.scroll_begin + event.y - self.y + + if direction: + if self.level == -1: + self.fm.move_parent(direction) + else: + return False + elif event.pressed(1): + if not self.main_column: + self.fm.enter_dir(self.target.path) + + if index < len(self.target): + self.fm.move(to=index) + elif event.pressed(3): + try: + clicked_file = self.target.files[index] + if clicked_file.is_directory: + self.fm.enter_dir(clicked_file.path) + elif self.level == 0: + self.fm.thisdir.move_to_obj(clicked_file) + self.fm.execute_file(clicked_file) + except: + pass + + else: + if self.level > 0 and not direction: + self.fm.move(right=0) + + return True + + def execute_curses_batch(self, line, commands): + """ + Executes a list of "commands" which can be easily cached. + + "commands" is a list of lists. Each element contains + a text and an attribute. First, the attribute will be + set with attrset, then the text is printed. + + Example: + execute_curses_batch(0, [["hello ", 0], ["world", curses.A_BOLD]]) + """ + try: + self.win.move(line, 0) + except: + return + for entry in commands: + text, attr = entry + self.addstr(text, attr) + + def has_preview(self): + if self.target is None: + return False + + if self.target.is_file: + if not self.target.has_preview(): + return False + + if self.target.is_directory: + if self.level > 0 and not self.settings.preview_directories: + return False + + return True + + def level_shift(self, amount): + self.level = self.original_level + amount + + def level_restore(self): + self.level = self.original_level + + def poke(self): + Widget.poke(self) + self.target = self.fm.thistab.at_level(self.level) + + def draw(self): + """Call either _draw_file() or _draw_directory()""" + if self.target != self.old_dir: + self.need_redraw = True + self.old_dir = self.target + + if self.target: # don't garbage collect this directory please + self.target.use() + + if self.target and self.target.is_directory \ + and (self.level <= 0 or self.settings.preview_directories): + if self.target.pointed_obj != self.old_thisfile: + self.need_redraw = True + self.old_thisfile = self.target.pointed_obj + + if self.target.load_content_if_outdated() \ + or self.target.sort_if_outdated() \ + or self.last_redraw_time < self.target.last_update_time: + self.need_redraw = True + + if self.need_redraw: + self.win.erase() + if self.target is None: + pass + elif self.target.is_file: + Pager.open(self) + self._draw_file() + elif self.target.is_directory: + self._draw_directory() + Widget.draw(self) + self.need_redraw = False + self.last_redraw_time = time() + + def _draw_file(self): + """Draw a preview of the file, if the settings allow it""" + self.win.move(0, 0) + if not self.target.accessible: + self.addnstr("not accessible", self.wid) + Pager.close(self) + return + + if self.target is None or not self.target.has_preview(): + Pager.close(self) + return + + if self.fm.settings.preview_images and self.target.image: + self.set_image(self.target.realpath) + Pager.draw(self) + else: + f = self.target.get_preview_source(self.wid, self.hei) + if f is None: + Pager.close(self) + else: + self.set_source(f) + Pager.draw(self) + + def _draw_directory(self): + """Draw the contents of a directory""" + if self.image: + self.image = None + self.need_clear_image = True + Pager.clear_image(self) + + if self.level > 0 and not self.settings.preview_directories: + return + + base_color = ['in_browser'] + + self.win.move(0, 0) + + if not self.target.content_loaded: + self.color(tuple(base_color)) + self.addnstr("...", self.wid) + self.color_reset() + return + + if self.main_column: + base_color.append('main_column') + + if not self.target.accessible: + self.color(tuple(base_color + ['error'])) + self.addnstr("not accessible", self.wid) + self.color_reset() + return + + if self.target.empty(): + self.color(tuple(base_color + ['empty'])) + self.addnstr("empty", self.wid) + self.color_reset() + return + + self._set_scroll_begin() + + copied = [f.path for f in self.fm.copy_buffer] + ellipsis = self.ellipsis[self.settings.unicode_ellipsis] + + selected_i = self.target.pointer + for line in range(self.hei): + i = line + self.scroll_begin + if line > self.hei: + break + + try: + drawn = self.target.files[i] + except IndexError: + break + + tagged = self.fm.tags and drawn.realpath in self.fm.tags + if tagged: + tagged_marker = self.fm.tags.marker(drawn.realpath) + else: + tagged_marker = " " + + key = (self.wid, selected_i == i, drawn.marked, self.main_column, + drawn.path in copied, tagged_marker, drawn.infostring, + self.fm.do_cut) + + if key in drawn.display_data: + self.execute_curses_batch(line, drawn.display_data[key]) + self.color_reset() + continue + + display_data = [] + drawn.display_data[key] = display_data + + if self.display_infostring and drawn.infostring \ + and self.settings.display_size_in_main_column: + infostring = str(drawn.infostring) + " " + else: + infostring = "" + + this_color = base_color + list(drawn.mimetype_tuple) + text = drawn.basename + + space = self.wid - len(infostring) + if self.main_column: + space -= 2 + elif self.settings.display_tags_in_all_columns: + space -= 1 + + if i == selected_i: + this_color.append('selected') + + if drawn.marked: + this_color.append('marked') + if self.main_column or self.settings.display_tags_in_all_columns: + text = " " + text + + if tagged: + this_color.append('tagged') + + if drawn.is_directory: + this_color.append('directory') + else: + this_color.append('file') + + if drawn.stat: + mode = drawn.stat.st_mode + if mode & stat.S_IXUSR: + this_color.append('executable') + if stat.S_ISFIFO(mode): + this_color.append('fifo') + if stat.S_ISSOCK(mode): + this_color.append('socket') + if drawn.is_device: + this_color.append('device') + + if drawn.path in copied: + this_color.append('cut' if self.fm.do_cut else 'copied') + + if drawn.is_link: + this_color.append('link') + this_color.append(drawn.exists and 'good' or 'bad') + + attr = self.settings.colorscheme.get_attr(*this_color) + + if (self.main_column or self.settings.display_tags_in_all_columns) \ + and tagged and self.wid > 2: + this_color.append('tag_marker') + tag_attr = self.settings.colorscheme.get_attr(*this_color) + display_data.append([tagged_marker, tag_attr]) + else: + text = " " + text + space += 1 + + wtext = WideString(text) + if len(wtext) > space: + wtext = wtext[:max(0, space - 1)] + ellipsis + text = str(wtext) + + display_data.append([text, attr]) + + padding = self.wid - len(wtext) + if tagged and (self.main_column or \ + self.settings.display_tags_in_all_columns): + padding -= 1 + if infostring: + if len(wtext) + 1 + len(infostring) > self.wid: + pass + else: + padding -= len(infostring) + padding = max(0, padding) + infostring = (" " * padding) + infostring + display_data.append([infostring, attr]) + else: + display_data.append([" " * max(0, padding), attr]) + + self.execute_curses_batch(line, display_data) + self.color_reset() + + def _get_scroll_begin(self): + """Determines scroll_begin (the position of the first displayed file)""" + offset = self.settings.scroll_offset + dirsize = len(self.target) + winsize = self.hei + halfwinsize = winsize // 2 + index = self.target.pointer or 0 + original = self.target.scroll_begin + projected = index - original + + upper_limit = winsize - 1 - offset + lower_limit = offset + + if original < 0: + return 0 + + if dirsize < winsize: + return 0 + + if halfwinsize < offset: + return min( dirsize - winsize, max( 0, index - halfwinsize )) + + if original > dirsize - winsize: + self.target.scroll_begin = dirsize - winsize + return self._get_scroll_begin() + + if projected < upper_limit and projected > lower_limit: + return original + + if projected > upper_limit: + return min( dirsize - winsize, + original + (projected - upper_limit)) + + if projected < upper_limit: + return max( 0, + original - (lower_limit - projected)) + + return original + + def _set_scroll_begin(self): + """Updates the scroll_begin value""" + self.scroll_begin = self._get_scroll_begin() + self.target.scroll_begin = self.scroll_begin + + def scroll(self, n): + """scroll down by n lines""" + self.need_redraw = True + self.target.move(down=n) + self.target.scroll_begin += 3 * n + + def __str__(self): + return self.__class__.__name__ + ' at level ' + str(self.level) diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py index e4ab2166..742bd9d4 100644 --- a/ranger/gui/widgets/browserview.py +++ b/ranger/gui/widgets/browserview.py @@ -11,334 +11,334 @@ 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.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.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.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(): - self.resize(self.y, self.x, self.hei, self.wid) + 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.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.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.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(): + self.resize(self.y, self.x, self.hei, self.wid) diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 4d1855f7..f6f433bb 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -17,422 +17,422 @@ from ranger.container.history import History, HistoryEmptyException import ranger class Console(Widget): - visible = False - last_cursor_mode = None - history_search_pattern = None - prompt = ':' - copy = '' - tab_deque = None - original_line = None - history = None - history_backup = None - override = None - allow_close = False - historypath = None - wait_for_command_input = False - unicode_buffer = "" - - def __init__(self, win): - Widget.__init__(self, win) - self.clear() - self.history = History(self.settings.max_console_history_size) - # load history from files - if not ranger.arg.clean: - self.historypath = self.fm.confpath('history') - try: - f = open(self.historypath, 'r') - except: - pass - else: - for line in f: - self.history.add(line[:-1]) - f.close() - self.line = "" - self.history_backup = History(self.history) - - # NOTE: the console is considered in the "question mode" when the - # question_queue is non-empty. In that case, the console will draw the - # question instead of the regular console, and the input you give is - # used to answer the question instead of typing in commands. - # - # A question is a tuple of (question_string, callback_func, - # tuple_of_choices). callback_func is a function that is called when - # the question is answered which gets the answer as an argument. - # tuple_of_choices looks like ('y', 'n'). Only one-letter-answers are - # currently supported. Pressing enter uses the first choice whereas - # pressing ESC uses the second choice. - self.question_queue = [] - - def destroy(self): - # save history to files - if ranger.arg.clean or not self.settings.save_console_history: - return - if self.historypath: - try: - f = open(self.historypath, 'w') - except: - pass - else: - for entry in self.history_backup: - try: - f.write(entry + '\n') - except UnicodeEncodeError: - pass - f.close() - - def draw(self): - self.win.erase() - if self.question_queue: - assert isinstance(self.question_queue[0], tuple) - assert len(self.question_queue[0]) == 3 - self.addstr(0, 0, self.question_queue[0][0]) - return - - self.addstr(0, 0, self.prompt) - line = WideString(self.line) - overflow = -self.wid + len(self.prompt) + len(line) + 1 - if overflow > 0: - self.addstr(0, len(self.prompt), str(line[overflow:])) - else: - self.addstr(0, len(self.prompt), self.line) - - def finalize(self): - move = self.fm.ui.win.move - if self.question_queue: - try: - move(self.y, len(self.question_queue[0][0])) - except: - pass - else: - try: - pos = uwid(self.line[0:self.pos]) + len(self.prompt) - move(self.y, self.x + min(self.wid-1, pos)) - except: - pass - - def open(self, string='', prompt=None, position=None): - if prompt is not None: - assert isinstance(prompt, str) - self.prompt = prompt - elif 'prompt' in self.__dict__: - del self.prompt - - if self.last_cursor_mode is None: - try: - self.last_cursor_mode = curses.curs_set(1) - except: - pass - self.allow_close = False - self.tab_deque = None - self.unicode_buffer = "" - self.line = string - self.history_search_pattern = self.line - self.pos = len(string) - if position is not None: - self.pos = min(self.pos, position) - self.history_backup.fast_forward() - self.history = History(self.history_backup) - self.history.add('') - self.wait_for_command_input = True - return True - - def close(self, trigger_cancel_function=True): - if self.question_queue: - question = self.question_queue[0] - answers = question[2] - if len(answers) >= 2: - self._answer_question(answers[1]) - else: - self._close_command_prompt(trigger_cancel_function) - - def _close_command_prompt(self, trigger_cancel_function=True): - if trigger_cancel_function: - cmd = self._get_cmd(quiet=True) - if cmd: - try: - cmd.cancel() - except Exception as error: - self.fm.notify(error) - if self.last_cursor_mode is not None: - try: - curses.curs_set(self.last_cursor_mode) - except: - pass - self.last_cursor_mode = None - self.fm.hide_console_info() - self.add_to_history() - self.tab_deque = None - self.clear() - self.__class__ = Console - self.wait_for_command_input = False - - def clear(self): - self.pos = 0 - self.line = '' - - def press(self, key): - self.fm.ui.keymaps.use_keymap('console') - if not self.fm.ui.press(key): - self.type_key(key) - - def _answer_question(self, answer): - if not self.question_queue: - return False - question = self.question_queue[0] - text, callback, answers = question - if answer in answers: - self.question_queue.pop(0) - callback(answer) - return True - return False - - def type_key(self, key): - self.tab_deque = None - - line = "" if self.question_queue else self.line - result = self._add_character(key, self.unicode_buffer, line, self.pos) - if result[1] == line: - # line didn't change, so we don't need to do anything, just update - # the unicode _buffer. - self.unicode_buffer = result[0] - return - - if self.question_queue: - self.unicode_buffer, answer, self.pos = result - self._answer_question(answer) - else: - self.unicode_buffer, self.line, self.pos = result - self.on_line_change() - - def _add_character(self, key, unicode_buffer, line, pos): - # Takes the pressed key, a string "unicode_buffer" containing a - # potentially incomplete unicode character, the current line and the - # position of the cursor inside the line. - # This function returns the new unicode buffer, the modified line and - # position. - if isinstance(key, int): - try: - key = chr(key) - except ValueError: - return unicode_buffer, line, pos - - if self.fm.py3: - unicode_buffer += key - try: - decoded = unicode_buffer.encode("latin-1").decode("utf-8") - except UnicodeDecodeError: - return unicode_buffer, line, pos - except UnicodeEncodeError: - return unicode_buffer, line, pos - else: - unicode_buffer = "" - if pos == len(line): - line += decoded - else: - line = line[:pos] + decoded + line[pos:] - pos += len(decoded) - else: - if pos == len(line): - line += key - else: - line = line[:pos] + key + line[pos:] - pos += len(key) - return unicode_buffer, line, pos - - def history_move(self, n): - try: - current = self.history.current() - except HistoryEmptyException: - pass - else: - if self.line != current and self.line != self.history.top(): - self.history.modify(self.line) - if self.history_search_pattern: - self.history.search(self.history_search_pattern, n) - else: - self.history.move(n) - current = self.history.current() - if self.line != current: - self.line = self.history.current() - self.pos = len(self.line) - - def add_to_history(self): - self.history_backup.fast_forward() - self.history_backup.add(self.line) - self.history = History(self.history_backup) - - def move(self, **keywords): - direction = Direction(keywords) - if direction.horizontal(): - # Ensure that the pointer is moved utf-char-wise - if self.fm.py3: - self.pos = direction.move( - direction=direction.right(), - minimum=0, - maximum=len(self.line) + 1, - current=self.pos) - else: - if self.fm.py3: - uc = list(self.line) - upos = len(self.line[:self.pos]) - else: - uc = list(self.line.decode('utf-8', 'ignore')) - upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) - newupos = direction.move( - direction=direction.right(), - minimum=0, - maximum=len(uc) + 1, - current=upos) - self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore')) - - def delete_rest(self, direction): - self.tab_deque = None - if direction > 0: - self.copy = self.line[self.pos:] - self.line = self.line[:self.pos] - else: - self.copy = self.line[:self.pos] - self.line = self.line[self.pos:] - self.pos = 0 - self.on_line_change() - - def paste(self): - if self.pos == len(self.line): - self.line += self.copy - else: - self.line = self.line[:self.pos] + self.copy + self.line[self.pos:] - self.pos += len(self.copy) - self.on_line_change() - - def delete_word(self, backward=True): - if self.line: - self.tab_deque = None - if backward: - right_part = self.line[self.pos:] - i = self.pos - 2 - while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U): - i -= 1 - self.copy = self.line[i + 1:self.pos] - self.line = self.line[:i + 1] + right_part - self.pos = i + 1 - else: - left_part = self.line[:self.pos] - i = self.pos + 1 - while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U): - i += 1 - self.copy = self.line[self.pos:i] - if i >= len(self.line): - self.line = left_part - self.pos = len(self.line) - else: - self.line = left_part + self.line[i:] - self.pos = len(left_part) - self.on_line_change() - - def delete(self, mod): - self.tab_deque = None - if mod == -1 and self.pos == 0: - if not self.line: - self.close(trigger_cancel_function=False) - return - # Delete utf-char-wise - if self.fm.py3: - left_part = self.line[:self.pos + mod] - self.pos = len(left_part) - self.line = left_part + self.line[self.pos + 1:] - else: - uc = list(self.line.decode('utf-8', 'ignore')) - upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod - left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore') - self.pos = len(left_part) - self.line = left_part + ''.join(uc[upos+1:]).encode('utf-8', 'ignore') - self.on_line_change() - - def execute(self, cmd=None): - if self.question_queue and cmd is None: - question = self.question_queue[0] - answers = question[2] - if len(answers) >= 1: - self._answer_question(answers[0]) - else: - self.question_queue.pop(0) - return - - self.allow_close = True - self.fm.execute_console(self.line) - if self.allow_close: - self._close_command_prompt(trigger_cancel_function=False) - - def _get_cmd(self, quiet=False): - try: - command_class = self._get_cmd_class() - except KeyError: - if not quiet: - error = "Command not found: `%s'" % self.line.split()[0] - self.fm.notify(error, bad=True) - except: - return None - else: - return command_class(self.line) - - def _get_cmd_class(self): - return self.fm.commands.get_command(self.line.split()[0]) - - def _get_tab(self): - if ' ' in self.line: - cmd = self._get_cmd() - if cmd: - return cmd.tab() - else: - return None - - return self.fm.commands.command_generator(self.line) - - def tab(self, n=1): - if self.tab_deque is None: - tab_result = self._get_tab() - - if isinstance(tab_result, str): - self.line = tab_result - self.pos = len(tab_result) - self.on_line_change() - - elif tab_result == None: - pass - - elif hasattr(tab_result, '__iter__'): - self.tab_deque = deque(tab_result) - self.tab_deque.appendleft(self.line) - - if self.tab_deque is not None: - self.tab_deque.rotate(-n) - self.line = self.tab_deque[0] - self.pos = len(self.line) - self.on_line_change() - - def on_line_change(self): - self.history_search_pattern = self.line - try: - cls = self._get_cmd_class() - except (KeyError, ValueError, IndexError): - pass - else: - cmd = cls(self.line) - if cmd and cmd.quick(): - self.execute(cmd) - - def ask(self, text, callback, choices=['y', 'n']): - """ - Open a question prompt with predefined choices - - The "text" is displayed as the question text and should include a list - of possible keys that the user can type. The "callback" is a function - that is called when the question is answered. It only gets the answer - as an argument. "choices" is a tuple of one-letter strings that can be - typed in by the user. Every other input gets ignored, except <Enter> - and <ESC>. - - The first choice is used when the user presses <Enter>, the second - choice is used when the user presses <ESC>. - """ - self.question_queue.append((text, callback, choices)) + visible = False + last_cursor_mode = None + history_search_pattern = None + prompt = ':' + copy = '' + tab_deque = None + original_line = None + history = None + history_backup = None + override = None + allow_close = False + historypath = None + wait_for_command_input = False + unicode_buffer = "" + + def __init__(self, win): + Widget.__init__(self, win) + self.clear() + self.history = History(self.settings.max_console_history_size) + # load history from files + if not ranger.arg.clean: + self.historypath = self.fm.confpath('history') + try: + f = open(self.historypath, 'r') + except: + pass + else: + for line in f: + self.history.add(line[:-1]) + f.close() + self.line = "" + self.history_backup = History(self.history) + + # NOTE: the console is considered in the "question mode" when the + # question_queue is non-empty. In that case, the console will draw the + # question instead of the regular console, and the input you give is + # used to answer the question instead of typing in commands. + # + # A question is a tuple of (question_string, callback_func, + # tuple_of_choices). callback_func is a function that is called when + # the question is answered which gets the answer as an argument. + # tuple_of_choices looks like ('y', 'n'). Only one-letter-answers are + # currently supported. Pressing enter uses the first choice whereas + # pressing ESC uses the second choice. + self.question_queue = [] + + def destroy(self): + # save history to files + if ranger.arg.clean or not self.settings.save_console_history: + return + if self.historypath: + try: + f = open(self.historypath, 'w') + except: + pass + else: + for entry in self.history_backup: + try: + f.write(entry + '\n') + except UnicodeEncodeError: + pass + f.close() + + def draw(self): + self.win.erase() + if self.question_queue: + assert isinstance(self.question_queue[0], tuple) + assert len(self.question_queue[0]) == 3 + self.addstr(0, 0, self.question_queue[0][0]) + return + + self.addstr(0, 0, self.prompt) + line = WideString(self.line) + overflow = -self.wid + len(self.prompt) + len(line) + 1 + if overflow > 0: + self.addstr(0, len(self.prompt), str(line[overflow:])) + else: + self.addstr(0, len(self.prompt), self.line) + + def finalize(self): + move = self.fm.ui.win.move + if self.question_queue: + try: + move(self.y, len(self.question_queue[0][0])) + except: + pass + else: + try: + pos = uwid(self.line[0:self.pos]) + len(self.prompt) + move(self.y, self.x + min(self.wid-1, pos)) + except: + pass + + def open(self, string='', prompt=None, position=None): + if prompt is not None: + assert isinstance(prompt, str) + self.prompt = prompt + elif 'prompt' in self.__dict__: + del self.prompt + + if self.last_cursor_mode is None: + try: + self.last_cursor_mode = curses.curs_set(1) + except: + pass + self.allow_close = False + self.tab_deque = None + self.unicode_buffer = "" + self.line = string + self.history_search_pattern = self.line + self.pos = len(string) + if position is not None: + self.pos = min(self.pos, position) + self.history_backup.fast_forward() + self.history = History(self.history_backup) + self.history.add('') + self.wait_for_command_input = True + return True + + def close(self, trigger_cancel_function=True): + if self.question_queue: + question = self.question_queue[0] + answers = question[2] + if len(answers) >= 2: + self._answer_question(answers[1]) + else: + self._close_command_prompt(trigger_cancel_function) + + def _close_command_prompt(self, trigger_cancel_function=True): + if trigger_cancel_function: + cmd = self._get_cmd(quiet=True) + if cmd: + try: + cmd.cancel() + except Exception as error: + self.fm.notify(error) + if self.last_cursor_mode is not None: + try: + curses.curs_set(self.last_cursor_mode) + except: + pass + self.last_cursor_mode = None + self.fm.hide_console_info() + self.add_to_history() + self.tab_deque = None + self.clear() + self.__class__ = Console + self.wait_for_command_input = False + + def clear(self): + self.pos = 0 + self.line = '' + + def press(self, key): + self.fm.ui.keymaps.use_keymap('console') + if not self.fm.ui.press(key): + self.type_key(key) + + def _answer_question(self, answer): + if not self.question_queue: + return False + question = self.question_queue[0] + text, callback, answers = question + if answer in answers: + self.question_queue.pop(0) + callback(answer) + return True + return False + + def type_key(self, key): + self.tab_deque = None + + line = "" if self.question_queue else self.line + result = self._add_character(key, self.unicode_buffer, line, self.pos) + if result[1] == line: + # line didn't change, so we don't need to do anything, just update + # the unicode _buffer. + self.unicode_buffer = result[0] + return + + if self.question_queue: + self.unicode_buffer, answer, self.pos = result + self._answer_question(answer) + else: + self.unicode_buffer, self.line, self.pos = result + self.on_line_change() + + def _add_character(self, key, unicode_buffer, line, pos): + # Takes the pressed key, a string "unicode_buffer" containing a + # potentially incomplete unicode character, the current line and the + # position of the cursor inside the line. + # This function returns the new unicode buffer, the modified line and + # position. + if isinstance(key, int): + try: + key = chr(key) + except ValueError: + return unicode_buffer, line, pos + + if self.fm.py3: + unicode_buffer += key + try: + decoded = unicode_buffer.encode("latin-1").decode("utf-8") + except UnicodeDecodeError: + return unicode_buffer, line, pos + except UnicodeEncodeError: + return unicode_buffer, line, pos + else: + unicode_buffer = "" + if pos == len(line): + line += decoded + else: + line = line[:pos] + decoded + line[pos:] + pos += len(decoded) + else: + if pos == len(line): + line += key + else: + line = line[:pos] + key + line[pos:] + pos += len(key) + return unicode_buffer, line, pos + + def history_move(self, n): + try: + current = self.history.current() + except HistoryEmptyException: + pass + else: + if self.line != current and self.line != self.history.top(): + self.history.modify(self.line) + if self.history_search_pattern: + self.history.search(self.history_search_pattern, n) + else: + self.history.move(n) + current = self.history.current() + if self.line != current: + self.line = self.history.current() + self.pos = len(self.line) + + def add_to_history(self): + self.history_backup.fast_forward() + self.history_backup.add(self.line) + self.history = History(self.history_backup) + + def move(self, **keywords): + direction = Direction(keywords) + if direction.horizontal(): + # Ensure that the pointer is moved utf-char-wise + if self.fm.py3: + self.pos = direction.move( + direction=direction.right(), + minimum=0, + maximum=len(self.line) + 1, + current=self.pos) + else: + if self.fm.py3: + uc = list(self.line) + upos = len(self.line[:self.pos]) + else: + uc = list(self.line.decode('utf-8', 'ignore')) + upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + newupos = direction.move( + direction=direction.right(), + minimum=0, + maximum=len(uc) + 1, + current=upos) + self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore')) + + def delete_rest(self, direction): + self.tab_deque = None + if direction > 0: + self.copy = self.line[self.pos:] + self.line = self.line[:self.pos] + else: + self.copy = self.line[:self.pos] + self.line = self.line[self.pos:] + self.pos = 0 + self.on_line_change() + + def paste(self): + if self.pos == len(self.line): + self.line += self.copy + else: + self.line = self.line[:self.pos] + self.copy + self.line[self.pos:] + self.pos += len(self.copy) + self.on_line_change() + + def delete_word(self, backward=True): + if self.line: + self.tab_deque = None + if backward: + right_part = self.line[self.pos:] + i = self.pos - 2 + while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U): + i -= 1 + self.copy = self.line[i + 1:self.pos] + self.line = self.line[:i + 1] + right_part + self.pos = i + 1 + else: + left_part = self.line[:self.pos] + i = self.pos + 1 + while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U): + i += 1 + self.copy = self.line[self.pos:i] + if i >= len(self.line): + self.line = left_part + self.pos = len(self.line) + else: + self.line = left_part + self.line[i:] + self.pos = len(left_part) + self.on_line_change() + + def delete(self, mod): + self.tab_deque = None + if mod == -1 and self.pos == 0: + if not self.line: + self.close(trigger_cancel_function=False) + return + # Delete utf-char-wise + if self.fm.py3: + left_part = self.line[:self.pos + mod] + self.pos = len(left_part) + self.line = left_part + self.line[self.pos + 1:] + else: + uc = list(self.line.decode('utf-8', 'ignore')) + upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod + left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore') + self.pos = len(left_part) + self.line = left_part + ''.join(uc[upos+1:]).encode('utf-8', 'ignore') + self.on_line_change() + + def execute(self, cmd=None): + if self.question_queue and cmd is None: + question = self.question_queue[0] + answers = question[2] + if len(answers) >= 1: + self._answer_question(answers[0]) + else: + self.question_queue.pop(0) + return + + self.allow_close = True + self.fm.execute_console(self.line) + if self.allow_close: + self._close_command_prompt(trigger_cancel_function=False) + + def _get_cmd(self, quiet=False): + try: + command_class = self._get_cmd_class() + except KeyError: + if not quiet: + error = "Command not found: `%s'" % self.line.split()[0] + self.fm.notify(error, bad=True) + except: + return None + else: + return command_class(self.line) + + def _get_cmd_class(self): + return self.fm.commands.get_command(self.line.split()[0]) + + def _get_tab(self): + if ' ' in self.line: + cmd = self._get_cmd() + if cmd: + return cmd.tab() + else: + return None + + return self.fm.commands.command_generator(self.line) + + def tab(self, n=1): + if self.tab_deque is None: + tab_result = self._get_tab() + + if isinstance(tab_result, str): + self.line = tab_result + self.pos = len(tab_result) + self.on_line_change() + + elif tab_result == None: + pass + + elif hasattr(tab_result, '__iter__'): + self.tab_deque = deque(tab_result) + self.tab_deque.appendleft(self.line) + + if self.tab_deque is not None: + self.tab_deque.rotate(-n) + self.line = self.tab_deque[0] + self.pos = len(self.line) + self.on_line_change() + + def on_line_change(self): + self.history_search_pattern = self.line + try: + cls = self._get_cmd_class() + except (KeyError, ValueError, IndexError): + pass + else: + cmd = cls(self.line) + if cmd and cmd.quick(): + self.execute(cmd) + + def ask(self, text, callback, choices=['y', 'n']): + """ + Open a question prompt with predefined choices + + The "text" is displayed as the question text and should include a list + of possible keys that the user can type. The "callback" is a function + that is called when the question is answered. It only gets the answer + as an argument. "choices" is a tuple of one-letter strings that can be + typed in by the user. Every other input gets ignored, except <Enter> + and <ESC>. + + The first choice is used when the user presses <Enter>, the second + choice is used when the user presses <ESC>. + """ + self.question_queue.append((text, callback, choices)) diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index 6ebde5a9..e9ce8b5c 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -12,202 +12,202 @@ import ranger.ext.img_display as img_display # TODO: Scrolling in embedded pager class Pager(Widget): - source = None - source_is_stream = False - - old_source = None - old_scroll_begin = 0 - old_startx = 0 - need_clear_image = False - need_redraw_image = False - max_width = None - def __init__(self, win, embedded=False): - Widget.__init__(self, win) - self.embedded = embedded - self.scroll_begin = 0 - self.startx = 0 - self.markup = None - self.lines = [] - self.image = None - - def open(self): - self.scroll_begin = 0 - self.markup = None - self.max_width = 0 - self.startx = 0 - self.need_redraw = True - - def clear_image(self): - if self.need_clear_image: - img_display.clear(self.x, self.y, self.wid, self.hei) - self.need_clear_image = False - - def close(self): - if self.image: - self.need_clear_image = True - self.clear_image() - if self.source and self.source_is_stream: - self.source.close() - - def finalize(self): - self.fm.ui.win.move(self.y, self.x) - - def draw(self): - if self.need_clear_image: - self.need_redraw = True - - if self.old_source != self.source: - self.old_source = self.source - self.need_redraw = True - - if self.old_scroll_begin != self.scroll_begin or \ - self.old_startx != self.startx: - self.old_startx = self.startx - self.old_scroll_begin = self.scroll_begin - - if self.need_redraw: - self.need_redraw_image = True - self.clear_image() - - if not self.image: - line_gen = self._generate_lines( - starty=self.scroll_begin, startx=self.startx) - - for line, i in zip(line_gen, range(self.hei)): - self._draw_line(i, line) - - self.need_redraw = False - - def draw_image(self): - if self.image and self.need_redraw_image: - self.source = None - self.need_redraw_image = False - try: - img_display.draw(self.image, self.x, self.y, self.wid, self.hei) - except Exception as e: - self.fm.notify(e, bad=True) - - def _draw_line(self, i, line): - if self.markup is None: - self.addstr(i, 0, line) - elif self.markup == 'ansi': - try: - self.win.move(i, 0) - except: - pass - else: - for chunk in ansi.text_with_fg_bg_attr(line): - if isinstance(chunk, tuple): - self.set_fg_bg_attr(*chunk) - else: - self.addstr(chunk) - - def move(self, narg=None, **kw): - direction = Direction(kw) - if direction.horizontal(): - self.startx = direction.move( - direction=direction.right(), - override=narg, - maximum=self.max_width, - current=self.startx, - pagesize=self.wid, - offset=-self.wid + 1) - if direction.vertical(): - if self.source_is_stream: - self._get_line(self.scroll_begin + self.hei * 2) - self.scroll_begin = direction.move( - direction=direction.down(), - override=narg, - maximum=len(self.lines), - current=self.scroll_begin, - pagesize=self.hei, - offset=-self.hei + 1) - - def press(self, key): - self.fm.ui.keymaps.use_keymap('pager') - self.fm.ui.press(key) - - def set_image(self, image): - if self.image: - self.need_clear_image = True - self.image = image - - if self.source and self.source_is_stream: - self.source.close() - self.source = None - self.source_is_stream = False - - def set_source(self, source, strip=False): - if self.image: - self.image = None - self.need_clear_image = True - - if self.source and self.source_is_stream: - self.source.close() - - self.max_width = 0 - if isinstance(source, str): - self.source_is_stream = False - self.lines = source.splitlines() - if self.lines: - self.max_width = max(len(line) for line in self.lines) - elif hasattr(source, '__getitem__'): - self.source_is_stream = False - self.lines = source - if self.lines: - self.max_width = max(len(line) for line in source) - elif hasattr(source, 'readline'): - self.source_is_stream = True - self.lines = [] - else: - self.source = None - self.source_is_stream = False - return False - self.markup = 'ansi' - - if not self.source_is_stream and strip: - self.lines = map(lambda x: x.strip(), self.lines) - - self.source = source - return True - - def click(self, event): - n = event.ctrl() and 1 or 3 - direction = event.mouse_wheel_direction() - if direction: - self.move(down=direction * n) - return True - - def _get_line(self, n, attempt_to_read=True): - assert isinstance(n, int), n - try: - return self.lines[n] - except (KeyError, IndexError): - if attempt_to_read and self.source_is_stream: - try: - for l in self.source: - if len(l) > self.max_width: - self.max_width = len(l) - self.lines.append(l) - if len(self.lines) > n: - break - except (UnicodeError, IOError): - pass - return self._get_line(n, attempt_to_read=False) - return "" - - def _generate_lines(self, starty, startx): - i = starty - if not self.source: - raise StopIteration - while True: - try: - line = self._get_line(i).expandtabs(4) - if self.markup is 'ansi': - line = ansi.char_slice(line, startx, self.wid) + ansi.reset - else: - line = line[startx:self.wid + startx] - yield line.rstrip() - except IndexError: - raise StopIteration - i += 1 + source = None + source_is_stream = False + + old_source = None + old_scroll_begin = 0 + old_startx = 0 + need_clear_image = False + need_redraw_image = False + max_width = None + def __init__(self, win, embedded=False): + Widget.__init__(self, win) + self.embedded = embedded + self.scroll_begin = 0 + self.startx = 0 + self.markup = None + self.lines = [] + self.image = None + + def open(self): + self.scroll_begin = 0 + self.markup = None + self.max_width = 0 + self.startx = 0 + self.need_redraw = True + + def clear_image(self): + if self.need_clear_image: + img_display.clear(self.x, self.y, self.wid, self.hei) + self.need_clear_image = False + + def close(self): + if self.image: + self.need_clear_image = True + self.clear_image() + if self.source and self.source_is_stream: + self.source.close() + + def finalize(self): + self.fm.ui.win.move(self.y, self.x) + + def draw(self): + if self.need_clear_image: + self.need_redraw = True + + if self.old_source != self.source: + self.old_source = self.source + self.need_redraw = True + + if self.old_scroll_begin != self.scroll_begin or \ + self.old_startx != self.startx: + self.old_startx = self.startx + self.old_scroll_begin = self.scroll_begin + + if self.need_redraw: + self.need_redraw_image = True + self.clear_image() + + if not self.image: + line_gen = self._generate_lines( + starty=self.scroll_begin, startx=self.startx) + + for line, i in zip(line_gen, range(self.hei)): + self._draw_line(i, line) + + self.need_redraw = False + + def draw_image(self): + if self.image and self.need_redraw_image: + self.source = None + self.need_redraw_image = False + try: + img_display.draw(self.image, self.x, self.y, self.wid, self.hei) + except Exception as e: + self.fm.notify(e, bad=True) + + def _draw_line(self, i, line): + if self.markup is None: + self.addstr(i, 0, line) + elif self.markup == 'ansi': + try: + self.win.move(i, 0) + except: + pass + else: + for chunk in ansi.text_with_fg_bg_attr(line): + if isinstance(chunk, tuple): + self.set_fg_bg_attr(*chunk) + else: + self.addstr(chunk) + + def move(self, narg=None, **kw): + direction = Direction(kw) + if direction.horizontal(): + self.startx = direction.move( + direction=direction.right(), + override=narg, + maximum=self.max_width, + current=self.startx, + pagesize=self.wid, + offset=-self.wid + 1) + if direction.vertical(): + if self.source_is_stream: + self._get_line(self.scroll_begin + self.hei * 2) + self.scroll_begin = direction.move( + direction=direction.down(), + override=narg, + maximum=len(self.lines), + current=self.scroll_begin, + pagesize=self.hei, + offset=-self.hei + 1) + + def press(self, key): + self.fm.ui.keymaps.use_keymap('pager') + self.fm.ui.press(key) + + def set_image(self, image): + if self.image: + self.need_clear_image = True + self.image = image + + if self.source and self.source_is_stream: + self.source.close() + self.source = None + self.source_is_stream = False + + def set_source(self, source, strip=False): + if self.image: + self.image = None + self.need_clear_image = True + + if self.source and self.source_is_stream: + self.source.close() + + self.max_width = 0 + if isinstance(source, str): + self.source_is_stream = False + self.lines = source.splitlines() + if self.lines: + self.max_width = max(len(line) for line in self.lines) + elif hasattr(source, '__getitem__'): + self.source_is_stream = False + self.lines = source + if self.lines: + self.max_width = max(len(line) for line in source) + elif hasattr(source, 'readline'): + self.source_is_stream = True + self.lines = [] + else: + self.source = None + self.source_is_stream = False + return False + self.markup = 'ansi' + + if not self.source_is_stream and strip: + self.lines = map(lambda x: x.strip(), self.lines) + + self.source = source + return True + + def click(self, event): + n = event.ctrl() and 1 or 3 + direction = event.mouse_wheel_direction() + if direction: + self.move(down=direction * n) + return True + + def _get_line(self, n, attempt_to_read=True): + assert isinstance(n, int), n + try: + return self.lines[n] + except (KeyError, IndexError): + if attempt_to_read and self.source_is_stream: + try: + for l in self.source: + if len(l) > self.max_width: + self.max_width = len(l) + self.lines.append(l) + if len(self.lines) > n: + break + except (UnicodeError, IOError): + pass + return self._get_line(n, attempt_to_read=False) + return "" + + def _generate_lines(self, starty, startx): + i = starty + if not self.source: + raise StopIteration + while True: + try: + line = self._get_line(i).expandtabs(4) + if self.markup is 'ansi': + line = ansi.char_slice(line, startx, self.wid) + ansi.reset + else: + line = line[startx:self.wid + startx] + yield line.rstrip() + except IndexError: + raise StopIteration + i += 1 diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index 247ebe62..a2ce3830 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -20,282 +20,282 @@ from . import Widget from ranger.gui.bar import Bar class StatusBar(Widget): - __doc__ = __doc__ - owners = {} - groups = {} - timeformat = '%Y-%m-%d %H:%M' - hint = None - msg = None - - old_thisfile = None - old_ctime = None - old_du = None - old_hint = None - result = None - - def __init__(self, win, column=None): - Widget.__init__(self, win) - self.column = column - self.settings.signal_bind('setopt.display_size_in_status_bar', - self.request_redraw, weak=True) - - def request_redraw(self): - self.need_redraw = True - - def notify(self, text, duration=0, bad=False): - self.msg = Message(text, duration, bad) - - def clear_message(self): - self.msg = None - - def draw(self): - """Draw the statusbar""" - - if self.hint and isinstance(self.hint, str): - if self.old_hint != self.hint: - self.need_redraw = True - if self.need_redraw: - self._draw_hint() - return - - if self.old_hint and not self.hint: - self.old_hint = None - self.need_redraw = True - - if self.msg: - if self.msg.is_alive(): - self._draw_message() - return - else: - self.msg = None - self.need_redraw = True - - if self.fm.thisfile: - self.fm.thisfile.load_if_outdated() - try: - ctime = self.fm.thisfile.stat.st_ctime - except: - ctime = -1 - else: - ctime = -1 - - if not self.result: - self.need_redraw = True - - if self.old_du and not self.fm.thisdir.disk_usage: - self.old_du = self.fm.thisdir.disk_usage - self.need_redraw = True - - if self.old_thisfile != self.fm.thisfile: - self.old_thisfile = self.fm.thisfile - self.need_redraw = True - - if self.old_ctime != ctime: - self.old_ctime = ctime - self.need_redraw = True - - if self.need_redraw: - self.need_redraw = False - - self._calc_bar() - self._print_result(self.result) - - def _calc_bar(self): - bar = Bar('in_statusbar') - self._get_left_part(bar) - self._get_right_part(bar) - bar.shrink_by_removing(self.wid) - - self.result = bar.combine() - - def _draw_message(self): - self.win.erase() - self.color('in_statusbar', 'message', - self.msg.bad and 'bad' or 'good') - self.addnstr(0, 0, self.msg.text, self.wid) - - def _draw_hint(self): - self.win.erase() - highlight = True - space_left = self.wid - starting_point = self.x - for string in self.hint.split('*'): - highlight = not highlight - if highlight: - self.color('in_statusbar', 'text', 'highlight') - else: - self.color('in_statusbar', 'text') - - try: - self.addnstr(0, starting_point, string, space_left) - except: - break - space_left -= len(string) - starting_point += len(string) - - def _get_left_part(self, bar): - left = bar.left - - if self.column is not None and self.column.target is not None\ - and self.column.target.is_directory: - target = self.column.target.pointed_obj - else: - directory = self.fm.thistab.at_level(0) - if directory: - target = directory.pointed_obj - else: - return - try: - stat = target.stat - except: - return - if stat is None: - return - - if self.fm.mode != 'normal': - perms = '--%s--' % self.fm.mode.upper() - else: - perms = target.get_permission_string() - how = getuid() == stat.st_uid and 'good' or 'bad' - left.add(perms, 'permissions', how) - left.add_space() - left.add(str(stat.st_nlink), 'nlink') - left.add_space() - left.add(self._get_owner(target), 'owner') - left.add_space() - left.add(self._get_group(target), 'group') - - if target.is_link: - how = target.exists and 'good' or 'bad' - try: - dest = readlink(target.path) - except: - dest = '?' - left.add(' -> ' + dest, 'link', how) - else: - left.add_space() - - if self.settings.display_size_in_status_bar and target.infostring: - left.add(target.infostring.replace(" ", "")) - - left.add_space() - - left.add(strftime(self.timeformat, - localtime(stat.st_mtime)), 'mtime') - - def _get_owner(self, target): - uid = target.stat.st_uid - - try: - return self.owners[uid] - except KeyError: - try: - self.owners[uid] = getpwuid(uid)[0] - return self.owners[uid] - except KeyError: - return str(uid) - - def _get_group(self, target): - gid = target.stat.st_gid - - try: - return self.groups[gid] - except KeyError: - try: - self.groups[gid] = getgrgid(gid)[0] - return self.groups[gid] - except KeyError: - return str(gid) - - def _get_right_part(self, bar): - right = bar.right - if self.column is None: - return - - target = self.column.target - if target is None \ - or not target.accessible \ - or (target.is_directory and target.files is None): - return - - pos = target.scroll_begin - max_pos = len(target) - self.column.hei - base = 'scroll' - - if self.fm.thisdir.filter: - right.add(" f=", base, 'filter') - right.add(repr(self.fm.thisdir.filter), base, 'filter') - right.add(", ", "space") - - if target.marked_items: - if len(target.marked_items) == len(target.files): - right.add(human_readable(target.disk_usage, separator='')) - else: - sumsize = sum(f.size for f in target.marked_items if not - f.is_directory or f._cumulative_size_calculated) - right.add(human_readable(sumsize, separator='')) - right.add("/" + str(len(target.marked_items))) - else: - right.add(human_readable(target.disk_usage, separator='') + " sum") - try: - free = get_free_space(target.mount_path) - except OSError: - pass - else: - right.add(", ", "space") - right.add(human_readable(free, separator='') + " free") - right.add(" ", "space") - - if target.marked_items: - # Indicate that there are marked files. Useful if you scroll - # away and don't see them anymore. - right.add('Mrk', base, 'marked') - elif len(target.files): - right.add(str(target.pointer + 1) + '/' - + str(len(target.files)) + ' ', base) - if max_pos <= 0: - right.add('All', base, 'all') - elif pos == 0: - right.add('Top', base, 'top') - elif pos >= max_pos: - right.add('Bot', base, 'bot') - else: - right.add('{0:0>.0f}%'.format(100.0 * pos / max_pos), - base, 'percentage') - else: - right.add('0/0 All', base, 'all') - - def _print_result(self, result): - self.win.move(0, 0) - for part in result: - self.color(*part.lst) - self.addstr(str(part)) - - if self.settings.draw_progress_bar_in_status_bar: - queue = self.fm.loader.queue - states = [] - for item in queue: - if item.progressbar_supported: - states.append(item.percent) - if states: - state = sum(states) / len(states) - barwidth = state / 100.0 * self.wid - self.color_at(0, 0, int(barwidth), ("in_statusbar", "loaded")) - self.color_reset() + __doc__ = __doc__ + owners = {} + groups = {} + timeformat = '%Y-%m-%d %H:%M' + hint = None + msg = None + + old_thisfile = None + old_ctime = None + old_du = None + old_hint = None + result = None + + def __init__(self, win, column=None): + Widget.__init__(self, win) + self.column = column + self.settings.signal_bind('setopt.display_size_in_status_bar', + self.request_redraw, weak=True) + + def request_redraw(self): + self.need_redraw = True + + def notify(self, text, duration=0, bad=False): + self.msg = Message(text, duration, bad) + + def clear_message(self): + self.msg = None + + def draw(self): + """Draw the statusbar""" + + if self.hint and isinstance(self.hint, str): + if self.old_hint != self.hint: + self.need_redraw = True + if self.need_redraw: + self._draw_hint() + return + + if self.old_hint and not self.hint: + self.old_hint = None + self.need_redraw = True + + if self.msg: + if self.msg.is_alive(): + self._draw_message() + return + else: + self.msg = None + self.need_redraw = True + + if self.fm.thisfile: + self.fm.thisfile.load_if_outdated() + try: + ctime = self.fm.thisfile.stat.st_ctime + except: + ctime = -1 + else: + ctime = -1 + + if not self.result: + self.need_redraw = True + + if self.old_du and not self.fm.thisdir.disk_usage: + self.old_du = self.fm.thisdir.disk_usage + self.need_redraw = True + + if self.old_thisfile != self.fm.thisfile: + self.old_thisfile = self.fm.thisfile + self.need_redraw = True + + if self.old_ctime != ctime: + self.old_ctime = ctime + self.need_redraw = True + + if self.need_redraw: + self.need_redraw = False + + self._calc_bar() + self._print_result(self.result) + + def _calc_bar(self): + bar = Bar('in_statusbar') + self._get_left_part(bar) + self._get_right_part(bar) + bar.shrink_by_removing(self.wid) + + self.result = bar.combine() + + def _draw_message(self): + self.win.erase() + self.color('in_statusbar', 'message', + self.msg.bad and 'bad' or 'good') + self.addnstr(0, 0, self.msg.text, self.wid) + + def _draw_hint(self): + self.win.erase() + highlight = True + space_left = self.wid + starting_point = self.x + for string in self.hint.split('*'): + highlight = not highlight + if highlight: + self.color('in_statusbar', 'text', 'highlight') + else: + self.color('in_statusbar', 'text') + + try: + self.addnstr(0, starting_point, string, space_left) + except: + break + space_left -= len(string) + starting_point += len(string) + + def _get_left_part(self, bar): + left = bar.left + + if self.column is not None and self.column.target is not None\ + and self.column.target.is_directory: + target = self.column.target.pointed_obj + else: + directory = self.fm.thistab.at_level(0) + if directory: + target = directory.pointed_obj + else: + return + try: + stat = target.stat + except: + return + if stat is None: + return + + if self.fm.mode != 'normal': + perms = '--%s--' % self.fm.mode.upper() + else: + perms = target.get_permission_string() + how = getuid() == stat.st_uid and 'good' or 'bad' + left.add(perms, 'permissions', how) + left.add_space() + left.add(str(stat.st_nlink), 'nlink') + left.add_space() + left.add(self._get_owner(target), 'owner') + left.add_space() + left.add(self._get_group(target), 'group') + + if target.is_link: + how = target.exists and 'good' or 'bad' + try: + dest = readlink(target.path) + except: + dest = '?' + left.add(' -> ' + dest, 'link', how) + else: + left.add_space() + + if self.settings.display_size_in_status_bar and target.infostring: + left.add(target.infostring.replace(" ", "")) + + left.add_space() + + left.add(strftime(self.timeformat, + localtime(stat.st_mtime)), 'mtime') + + def _get_owner(self, target): + uid = target.stat.st_uid + + try: + return self.owners[uid] + except KeyError: + try: + self.owners[uid] = getpwuid(uid)[0] + return self.owners[uid] + except KeyError: + return str(uid) + + def _get_group(self, target): + gid = target.stat.st_gid + + try: + return self.groups[gid] + except KeyError: + try: + self.groups[gid] = getgrgid(gid)[0] + return self.groups[gid] + except KeyError: + return str(gid) + + def _get_right_part(self, bar): + right = bar.right + if self.column is None: + return + + target = self.column.target + if target is None \ + or not target.accessible \ + or (target.is_directory and target.files is None): + return + + pos = target.scroll_begin + max_pos = len(target) - self.column.hei + base = 'scroll' + + if self.fm.thisdir.filter: + right.add(" f=", base, 'filter') + right.add(repr(self.fm.thisdir.filter), base, 'filter') + right.add(", ", "space") + + if target.marked_items: + if len(target.marked_items) == len(target.files): + right.add(human_readable(target.disk_usage, separator='')) + else: + sumsize = sum(f.size for f in target.marked_items if not + f.is_directory or f._cumulative_size_calculated) + right.add(human_readable(sumsize, separator='')) + right.add("/" + str(len(target.marked_items))) + else: + right.add(human_readable(target.disk_usage, separator='') + " sum") + try: + free = get_free_space(target.mount_path) + except OSError: + pass + else: + right.add(", ", "space") + right.add(human_readable(free, separator='') + " free") + right.add(" ", "space") + + if target.marked_items: + # Indicate that there are marked files. Useful if you scroll + # away and don't see them anymore. + right.add('Mrk', base, 'marked') + elif len(target.files): + right.add(str(target.pointer + 1) + '/' + + str(len(target.files)) + ' ', base) + if max_pos <= 0: + right.add('All', base, 'all') + elif pos == 0: + right.add('Top', base, 'top') + elif pos >= max_pos: + right.add('Bot', base, 'bot') + else: + right.add('{0:0>.0f}%'.format(100.0 * pos / max_pos), + base, 'percentage') + else: + right.add('0/0 All', base, 'all') + + def _print_result(self, result): + self.win.move(0, 0) + for part in result: + self.color(*part.lst) + self.addstr(str(part)) + + if self.settings.draw_progress_bar_in_status_bar: + queue = self.fm.loader.queue + states = [] + for item in queue: + if item.progressbar_supported: + states.append(item.percent) + if states: + state = sum(states) / len(states) + barwidth = state / 100.0 * self.wid + self.color_at(0, 0, int(barwidth), ("in_statusbar", "loaded")) + self.color_reset() def get_free_space(path): - stat = os.statvfs(path) - return stat.f_bavail * stat.f_bsize + stat = os.statvfs(path) + return stat.f_bavail * stat.f_bsize class Message(object): - elapse = None - text = None - bad = False + elapse = None + text = None + bad = False - def __init__(self, text, duration, bad): - self.text = text - self.bad = bad - self.elapse = time() + duration + def __init__(self, text, duration, bad): + self.text = text + self.bad = bad + self.elapse = time() + duration - def is_alive(self): - return time() <= self.elapse + def is_alive(self): + return time() <= self.elapse diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py index e5efc417..3637d0e3 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -9,87 +9,87 @@ from . import Widget from ranger.ext.accumulator import Accumulator class TaskView(Widget, Accumulator): - old_lst = None - - def __init__(self, win): - Widget.__init__(self, win) - Accumulator.__init__(self) - self.scroll_begin = 0 - - def draw(self): - base_clr = [] - base_clr.append('in_taskview') - lst = self.get_list() - - if self.old_lst != lst: - self.old_lst = lst - self.need_redraw = True - - if self.need_redraw: - self.win.erase() - if not self.pointer_is_synced(): - self.sync_index() - - if self.hei <= 0: - return - - self.addstr(0, 0, "Task View") - self.color_at(0, 0, self.wid, tuple(base_clr), 'title') - - if lst: - for i in range(self.hei - 1): - i += self.scroll_begin - try: - obj = lst[i] - except IndexError: - break - - y = i + 1 - clr = list(base_clr) - - if self.pointer == i: - clr.append('selected') - - descr = obj.get_description() - if obj.progressbar_supported and obj.percent >= 0 \ - and obj.percent <= 100: - self.addstr(y, 0, "%3d%% - %s" % \ - (obj.percent, descr), self.wid) - wid = int(self.wid / 100.0 * obj.percent) - self.color_at(y, 0, self.wid, tuple(clr)) - self.color_at(y, 0, wid, tuple(clr), 'loaded') - else: - self.addstr(y, 0, descr, self.wid) - self.color_at(y, 0, self.wid, tuple(clr)) - - else: - if self.hei > 1: - self.addstr(1, 0, "No task in the queue.") - self.color_at(1, 0, self.wid, tuple(base_clr), 'error') - - self.color_reset() - - def finalize(self): - y = self.y + 1 + self.pointer - self.scroll_begin - self.fm.ui.win.move(y, self.x) - - - def task_remove(self, i=None): - if i is None: - i = self.pointer - - if self.fm.loader.queue: - self.fm.loader.remove(index=i) - - def task_move(self, to, i=None): - if i is None: - i = self.pointer - - self.fm.loader.move(_from=i, to=to) - - def press(self, key): - self.fm.ui.keymaps.use_keymap('taskview') - self.fm.ui.press(key) - - def get_list(self): - return self.fm.loader.queue + old_lst = None + + def __init__(self, win): + Widget.__init__(self, win) + Accumulator.__init__(self) + self.scroll_begin = 0 + + def draw(self): + base_clr = [] + base_clr.append('in_taskview') + lst = self.get_list() + + if self.old_lst != lst: + self.old_lst = lst + self.need_redraw = True + + if self.need_redraw: + self.win.erase() + if not self.pointer_is_synced(): + self.sync_index() + + if self.hei <= 0: + return + + self.addstr(0, 0, "Task View") + self.color_at(0, 0, self.wid, tuple(base_clr), 'title') + + if lst: + for i in range(self.hei - 1): + i += self.scroll_begin + try: + obj = lst[i] + except IndexError: + break + + y = i + 1 + clr = list(base_clr) + + if self.pointer == i: + clr.append('selected') + + descr = obj.get_description() + if obj.progressbar_supported and obj.percent >= 0 \ + and obj.percent <= 100: + self.addstr(y, 0, "%3d%% - %s" % \ + (obj.percent, descr), self.wid) + wid = int(self.wid / 100.0 * obj.percent) + self.color_at(y, 0, self.wid, tuple(clr)) + self.color_at(y, 0, wid, tuple(clr), 'loaded') + else: + self.addstr(y, 0, descr, self.wid) + self.color_at(y, 0, self.wid, tuple(clr)) + + else: + if self.hei > 1: + self.addstr(1, 0, "No task in the queue.") + self.color_at(1, 0, self.wid, tuple(base_clr), 'error') + + self.color_reset() + + def finalize(self): + y = self.y + 1 + self.pointer - self.scroll_begin + self.fm.ui.win.move(y, self.x) + + + def task_remove(self, i=None): + if i is None: + i = self.pointer + + if self.fm.loader.queue: + self.fm.loader.remove(index=i) + + def task_move(self, to, i=None): + if i is None: + i = self.pointer + + self.fm.loader.move(_from=i, to=to) + + def press(self, key): + self.fm.ui.keymaps.use_keymap('taskview') + self.fm.ui.press(key) + + def get_list(self): + return self.fm.loader.queue diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py index d37a2fd3..5986ec7a 100644 --- a/ranger/gui/widgets/titlebar.py +++ b/ranger/gui/widgets/titlebar.py @@ -13,142 +13,142 @@ from . import Widget from ranger.gui.bar import Bar class TitleBar(Widget): - old_thisfile = None - old_keybuffer = None - old_wid = None - result = None - throbber = ' ' - need_redraw = False - tab_width = 0 - - def __init__(self, *args, **keywords): - Widget.__init__(self, *args, **keywords) - self.fm.signal_bind('tab.change', self.request_redraw, weak=True) - - def request_redraw(self): - self.need_redraw = True - - def draw(self): - if self.need_redraw or \ - self.fm.thisfile != self.old_thisfile or\ - str(self.fm.ui.keybuffer) != str(self.old_keybuffer) or\ - self.wid != self.old_wid: - self.need_redraw = False - self.old_wid = self.wid - self.old_thisfile = self.fm.thisfile - self._calc_bar() - self._print_result(self.result) - if self.wid > 2: - self.color('in_titlebar', 'throbber') - self.addnstr(self.y, self.wid - 2 - self.tab_width, - self.throbber, 1) - - def click(self, event): - """Handle a MouseEvent""" - direction = event.mouse_wheel_direction() - if direction: - self.fm.tab_move(direction) - self.need_redraw = True - return True - - if not event.pressed(1) or not self.result: - return False - - pos = self.wid - 1 - for tabname in reversed(self.fm._get_tab_list()): - tabtext = self._get_tab_text(tabname) - pos -= len(tabtext) - if event.x > pos: - self.fm.tab_open(tabname) - self.need_redraw = True - return True - - pos = 0 - for i, part in enumerate(self.result): - pos += len(part) - if event.x < pos: - if i < 2: - self.fm.enter_dir("~") - elif i == 2: - self.fm.enter_dir("/") - else: - try: - self.fm.enter_dir(part.directory) - except: - pass - return True - return False - - def _calc_bar(self): - bar = Bar('in_titlebar') - self._get_left_part(bar) - self._get_right_part(bar) - try: - bar.shrink_from_the_left(self.wid) - except ValueError: - bar.shrink_by_removing(self.wid) - self.result = bar.combine() - - def _get_left_part(self, bar): - # TODO: Properly escape non-printable chars without breaking unicode - if self.fm.username == 'root': - clr = 'bad' - else: - clr = 'good' - - bar.add(self.fm.username, 'hostname', clr, fixed=True) - bar.add('@', 'hostname', clr, fixed=True) - bar.add(self.fm.hostname, 'hostname', clr, fixed=True) - bar.add(':', 'hostname', clr, fixed=True) - - pathway = self.fm.thistab.pathway - if self.settings.tilde_in_titlebar and \ - self.fm.thisdir.path.startswith(self.fm.home_path): - pathway = pathway[self.fm.home_path.count('/')+1:] - bar.add('~/', 'directory', fixed=True) - - for path in pathway: - if path.is_link: - clr = 'link' - else: - clr = 'directory' - - bar.add(path.basename, clr, directory=path) - bar.add('/', clr, fixed=True, directory=path) - - if self.fm.thisfile is not None: - bar.add(self.fm.thisfile.basename, 'file') - - def _get_right_part(self, bar): - # TODO: fix that pressed keys are cut off when chaining CTRL keys - kb = str(self.fm.ui.keybuffer) - self.old_keybuffer = kb - bar.addright(kb, 'keybuffer', fixed=True) - bar.addright(' ', 'space', fixed=True) - self.tab_width = 0 - if len(self.fm.tabs) > 1: - for tabname in self.fm._get_tab_list(): - tabtext = self._get_tab_text(tabname) - self.tab_width += len(tabtext) - clr = 'good' if tabname == self.fm.current_tab else 'bad' - bar.addright(tabtext, 'tab', clr, fixed=True) - - def _get_tab_text(self, tabname): - result = ' ' + str(tabname) - if self.settings.dirname_in_tabs: - dirname = basename(self.fm.tabs[tabname].path) - if not dirname: - result += ":/" - elif len(dirname) > 15: - result += ":" + dirname[:14] + "~" - else: - result += ":" + dirname - return result - - def _print_result(self, result): - self.win.move(0, 0) - for part in result: - self.color(*part.lst) - y, x = self.win.getyx() - self.addstr(y, x, str(part)) - self.color_reset() + old_thisfile = None + old_keybuffer = None + old_wid = None + result = None + throbber = ' ' + need_redraw = False + tab_width = 0 + + def __init__(self, *args, **keywords): + Widget.__init__(self, *args, **keywords) + self.fm.signal_bind('tab.change', self.request_redraw, weak=True) + + def request_redraw(self): + self.need_redraw = True + + def draw(self): + if self.need_redraw or \ + self.fm.thisfile != self.old_thisfile or\ + str(self.fm.ui.keybuffer) != str(self.old_keybuffer) or\ + self.wid != self.old_wid: + self.need_redraw = False + self.old_wid = self.wid + self.old_thisfile = self.fm.thisfile + self._calc_bar() + self._print_result(self.result) + if self.wid > 2: + self.color('in_titlebar', 'throbber') + self.addnstr(self.y, self.wid - 2 - self.tab_width, + self.throbber, 1) + + def click(self, event): + """Handle a MouseEvent""" + direction = event.mouse_wheel_direction() + if direction: + self.fm.tab_move(direction) + self.need_redraw = True + return True + + if not event.pressed(1) or not self.result: + return False + + pos = self.wid - 1 + for tabname in reversed(self.fm._get_tab_list()): + tabtext = self._get_tab_text(tabname) + pos -= len(tabtext) + if event.x > pos: + self.fm.tab_open(tabname) + self.need_redraw = True + return True + + pos = 0 + for i, part in enumerate(self.result): + pos += len(part) + if event.x < pos: + if i < 2: + self.fm.enter_dir("~") + elif i == 2: + self.fm.enter_dir("/") + else: + try: + self.fm.enter_dir(part.directory) + except: + pass + return True + return False + + def _calc_bar(self): + bar = Bar('in_titlebar') + self._get_left_part(bar) + self._get_right_part(bar) + try: + bar.shrink_from_the_left(self.wid) + except ValueError: + bar.shrink_by_removing(self.wid) + self.result = bar.combine() + + def _get_left_part(self, bar): + # TODO: Properly escape non-printable chars without breaking unicode + if self.fm.username == 'root': + clr = 'bad' + else: + clr = 'good' + + bar.add(self.fm.username, 'hostname', clr, fixed=True) + bar.add('@', 'hostname', clr, fixed=True) + bar.add(self.fm.hostname, 'hostname', clr, fixed=True) + bar.add(':', 'hostname', clr, fixed=True) + + pathway = self.fm.thistab.pathway + if self.settings.tilde_in_titlebar and \ + self.fm.thisdir.path.startswith(self.fm.home_path): + pathway = pathway[self.fm.home_path.count('/')+1:] + bar.add('~/', 'directory', fixed=True) + + for path in pathway: + if path.is_link: + clr = 'link' + else: + clr = 'directory' + + bar.add(path.basename, clr, directory=path) + bar.add('/', clr, fixed=True, directory=path) + + if self.fm.thisfile is not None: + bar.add(self.fm.thisfile.basename, 'file') + + def _get_right_part(self, bar): + # TODO: fix that pressed keys are cut off when chaining CTRL keys + kb = str(self.fm.ui.keybuffer) + self.old_keybuffer = kb + bar.addright(kb, 'keybuffer', fixed=True) + bar.addright(' ', 'space', fixed=True) + self.tab_width = 0 + if len(self.fm.tabs) > 1: + for tabname in self.fm._get_tab_list(): + tabtext = self._get_tab_text(tabname) + self.tab_width += len(tabtext) + clr = 'good' if tabname == self.fm.current_tab else 'bad' + bar.addright(tabtext, 'tab', clr, fixed=True) + + def _get_tab_text(self, tabname): + result = ' ' + str(tabname) + if self.settings.dirname_in_tabs: + dirname = basename(self.fm.tabs[tabname].path) + if not dirname: + result += ":/" + elif len(dirname) > 15: + result += ":" + dirname[:14] + "~" + else: + result += ":" + dirname + return result + + def _print_result(self, result): + self.win.move(0, 0) + for part in result: + self.color(*part.lst) + y, x = self.win.getyx() + self.addstr(y, x, str(part)) + self.color_reset() |