diff options
-rw-r--r-- | ranger/container/settings.py | 1 | ||||
-rw-r--r-- | ranger/core/actions.py | 4 | ||||
-rw-r--r-- | ranger/gui/context.py | 1 | ||||
-rw-r--r-- | ranger/gui/displayable.py | 6 | ||||
-rw-r--r-- | ranger/gui/ui.py | 59 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 35 | ||||
-rw-r--r-- | ranger/gui/widgets/console.py | 1 | ||||
-rw-r--r-- | ranger/gui/widgets/pager.py | 1 | ||||
-rw-r--r-- | ranger/gui/widgets/view_base.py | 159 | ||||
-rw-r--r-- | ranger/gui/widgets/view_miller.py (renamed from ranger/gui/widgets/browserview.py) | 132 | ||||
-rw-r--r-- | ranger/gui/widgets/view_multipane.py | 50 |
11 files changed, 312 insertions, 137 deletions
diff --git a/ranger/container/settings.py b/ranger/container/settings.py index d7258d6d..36db03ad 100644 --- a/ranger/container/settings.py +++ b/ranger/container/settings.py @@ -56,6 +56,7 @@ ALLOWED_SETTINGS = { 'update_title': bool, 'update_tmux_title': bool, 'use_preview_script': bool, + 'viewmode': str, 'vcs_aware': bool, 'vcs_backend_bzr': str, 'vcs_backend_git': str, diff --git a/ranger/core/actions.py b/ranger/core/actions.py index a168a095..825a1cc0 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -566,7 +566,7 @@ class Actions(FileManagerAware, SettingsAware): def pager_close(self): if self.ui.pager.visible: self.ui.close_pager() - if self.ui.browser.pager.visible: + if hasattr(self.ui.browser, 'pager') and self.ui.browser.pager.visible: self.ui.close_embedded_pager() def taskview_open(self): @@ -1042,6 +1042,7 @@ class Actions(FileManagerAware, SettingsAware): if tab_has_changed: self.change_mode('normal') self.signal_emit('tab.change', old=previous_tab, new=self.thistab) + self.signal_emit('tab.layoutchange') def tab_close(self, name=None): if name is None: @@ -1056,6 +1057,7 @@ class Actions(FileManagerAware, SettingsAware): if name in self.tabs: del self.tabs[name] self.restorable_tabs.append(tab) + self.signal_emit('tab.layoutchange') def tab_restore(self): # NOTE: The name of the tab is not restored. diff --git a/ranger/gui/context.py b/ranger/gui/context.py index ac597e5a..2d23d4f1 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -4,6 +4,7 @@ CONTEXT_KEYS = ['reset', 'error', 'badinfo', 'in_browser', 'in_statusbar', 'in_titlebar', 'in_console', 'in_pager', 'in_taskview', + 'active_pane', 'inactive_pane', 'directory', 'file', 'hostname', 'executable', 'media', 'link', 'fifo', 'socket', 'device', 'video', 'audio', 'image', 'media', 'document', 'container', diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py index d3adfe50..8415b82c 100644 --- a/ranger/gui/displayable.py +++ b/ranger/gui/displayable.py @@ -94,10 +94,8 @@ class Displayable(FileManagerAware, CursesShortcuts): """ def destroy(self): - """Called when the object is destroyed. - - Override this! - """ + """Called when the object is destroyed.""" + del self.win def contains_point(self, y, x): """Test whether the point lies inside this object. diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index ad95d754..5d06ca13 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -11,6 +11,7 @@ from .displayable import DisplayableContainer from .mouse_event import MouseEvent from ranger.ext.keybinding_parser import KeyBuffer, KeyMaps, ALT_KEY from ranger.ext.lazy_property import lazy_property +from ranger.ext.signals import Signal MOUSEMASK = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION @@ -34,6 +35,8 @@ def _setup_mouse(signal): curses.mousemask(0) class UI(DisplayableContainer): + ALLOWED_VIEWMODES = ('multipane', ) + is_set_up = False load_mode = False is_on = False @@ -44,6 +47,7 @@ class UI(DisplayableContainer): self.keymaps = KeyMaps(self.keybuffer) self.redrawlock = threading.Event() self.redrawlock.set() + self._browser_viewmodes = dict() if fm is not None: self.fm = fm @@ -87,6 +91,7 @@ class UI(DisplayableContainer): self.win.addstr("loading...") self.win.refresh() self._draw_title = curses.tigetflag('hs') # has_status_line + self.update_size() self.is_on = True @@ -223,7 +228,7 @@ class UI(DisplayableContainer): def setup(self): """Build up the UI by initializing widgets.""" - from ranger.gui.widgets.browserview import BrowserView + from ranger.gui.widgets.view_miller import ViewMiller from ranger.gui.widgets.titlebar import TitleBar from ranger.gui.widgets.console import Console from ranger.gui.widgets.statusbar import StatusBar @@ -235,9 +240,10 @@ class UI(DisplayableContainer): 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.settings.signal_bind('setopt.viewmode', self._set_viewmode) + self._viewmode = None + self.viewmode = 'multipane' # this line sets self.browser implicitly + # through the signal handler bound above self.add_child(self.browser) # Create the process manager @@ -339,10 +345,11 @@ class UI(DisplayableContainer): def draw_images(self): if self.pager.visible: self.pager.draw_image() - elif self.browser.pager.visible: - self.browser.pager.draw_image() - else: - self.browser.columns[-1].draw_image() + elif hasattr(self.browser, 'pager'): + if self.browser.pager.visible: + self.browser.pager.draw_image() + else: + self.browser.columns[-1].draw_image() def close_pager(self): if self.console.visible: @@ -411,7 +418,41 @@ class UI(DisplayableContainer): self.status.hint = text def get_pager(self): - if self.browser.pager.visible: + if hasattr(self.browser, 'pager') and self.browser.pager.visible: return self.browser.pager else: return self.pager + + def _get_viewmode(self): + return self._viewmode + + def _set_viewmode(self, value): + if isinstance(value, Signal): + value = value.value + if value == '': + value = self.ALLOWED_VIEWMODES[0] + if value in self.ALLOWED_VIEWMODES: + if self._viewmode != value: + self._viewmode = value + if hasattr(self, 'browser'): + self.remove_child(self.browser) + if value in self._browser_viewmodes: + self.browser = self._browser_viewmodes[value] + else: + browser = self._viewmode_to_class(value)(self.win) + self.browser = self._browser_viewmodes[value] = browser + self.browser.resize(self.y, self.x, self.hei, self.wid) + self.add_child(self.browser) + else: + raise ValueError("Attempting to set invalid viewmode `%s`, should " + "be one of `%s`." % (value, "`, `".join(self.ALLOWED_VIEWMODES))) + + viewmode = property(_get_viewmode, _set_viewmode) + + def _viewmode_to_class(self, viewmode): + if viewmode == 'miller': + from ranger.gui.widgets.view_miller import ViewMiller + return ViewMiller + if viewmode == 'multipane': + from ranger.gui.widgets.view_multipane import ViewMultipane + return ViewMultipane diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 07830b31..129d8486 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -27,7 +27,7 @@ class BrowserColumn(Pager): old_dir = None old_thisfile = None - def __init__(self, win, level): + def __init__(self, win, level, tab=None): """Initializes a Browser Column Widget win = the curses window object of the BrowserView @@ -40,6 +40,7 @@ class BrowserColumn(Pager): Pager.__init__(self, win) Widget.__init__(self, win) self.level = level + self.tab = tab self.original_level = level self.settings.signal_bind('setopt.display_size_in_main_column', @@ -48,9 +49,6 @@ class BrowserColumn(Pager): 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() @@ -132,7 +130,11 @@ class BrowserColumn(Pager): def poke(self): Widget.poke(self) - self.target = self.fm.thistab.at_level(self.level) + if self.tab is None: + tab = self.fm.thistab + else: + tab = self.tab + self.target = tab.at_level(self.level) def draw(self): """Call either _draw_file() or _draw_directory()""" @@ -201,6 +203,15 @@ class BrowserColumn(Pager): base_color = ['in_browser'] + if self.fm.ui.viewmode == 'multipane' and self.tab is not None: + active_pane = self.tab == self.fm.thistab + if active_pane: + base_color.append('active_pane') + else: + base_color.append('inactive_pane') + else: + active_pane = False + self.win.move(0, 0) if not self.target.content_loaded: @@ -228,7 +239,7 @@ class BrowserColumn(Pager): copied = [f.path for f in self.fm.copy_buffer] - selected_i = self.target.pointer + selected_i = self._get_index_of_selected_file() for line in range(self.hei): i = line + self.scroll_begin if line > self.hei: @@ -258,7 +269,7 @@ class BrowserColumn(Pager): key = (self.wid, selected_i == i, drawn.marked, self.main_column, drawn.path in copied, tagged_marker, drawn.infostring, drawn.vcsstatus, drawn.vcsremotestatus, self.target.has_vcschild, - self.fm.do_cut, current_linemode.name, metakey) + self.fm.do_cut, current_linemode.name, metakey, active_pane) if key in drawn.display_data: self.execute_curses_batch(line, drawn.display_data[key]) @@ -336,6 +347,12 @@ class BrowserColumn(Pager): self.execute_curses_batch(line, display_data) self.color_reset() + def _get_index_of_selected_file(self): + if self.fm.ui.viewmode == 'multipane' and hasattr(self, 'tab'): + return self.tab.pointer + else: + return self.target.pointer + def _total_len(self, predisplay): return sum([len(WideString(s)) for s, L in predisplay]) @@ -391,7 +408,7 @@ class BrowserColumn(Pager): def _draw_directory_color(self, i, drawn, copied): this_color = [] - if i == self.target.pointer: + if i == self._get_index_of_selected_file(): this_color.append('selected') if drawn.marked: @@ -431,7 +448,7 @@ class BrowserColumn(Pager): dirsize = len(self.target) winsize = self.hei halfwinsize = winsize // 2 - index = self.target.pointer or 0 + index = self._get_index_of_selected_file() or 0 original = self.target.scroll_begin projected = index - original diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 159b8f1e..156298ab 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -76,6 +76,7 @@ class Console(Widget): except UnicodeEncodeError: pass f.close() + Widget.destroy(self) def draw(self): self.win.erase() diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index ce3cc1bf..0d185f98 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -52,6 +52,7 @@ class Pager(Widget): def destroy(self): self.clear_image(force=True) + Widget.destroy(self) def finalize(self): self.fm.ui.win.move(self.y, self.x) diff --git a/ranger/gui/widgets/view_base.py b/ranger/gui/widgets/view_base.py new file mode 100644 index 00000000..9fbd9586 --- /dev/null +++ b/ranger/gui/widgets/view_base.py @@ -0,0 +1,159 @@ +# This file is part of ranger, the console file manager. +# License: GNU GPL version 3, see the file "AUTHORS" for details. + +"""The base GUI element for views on the directory""" + +import curses, _curses +from ranger.ext.keybinding_parser import key_to_string +from . import Widget +from ..displayable import DisplayableContainer + +class ViewBase(Widget, DisplayableContainer): + draw_bookmarks = False + need_clear = False + draw_hints = False + draw_info = False + + def __init__(self, win): + DisplayableContainer.__init__(self, win) + + self.fm.signal_bind('move', self.request_clear) + self.old_draw_borders = self.settings.draw_borders + + 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.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 hasattr(self, 'pager') and self.pager.visible: + try: + self.fm.ui.win.move(self.main_column.y, self.main_column.x) + except: + pass + 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 + + def _draw_bookmarks(self): + self.columns[-1].clear_image(force=True) + self.fm.bookmarks.update_if_outdated() + self.color_reset() + self.need_clear = True + + sorted_bookmarks = sorted((item for item in self.fm.bookmarks \ + if self.fm.settings.show_hidden_bookmarks or \ + '/.' not in item[1].path), key=lambda t: t[0].lower()) + + hei = min(self.hei - 1, len(sorted_bookmarks)) + ystart = self.hei - hei + + maxlen = self.wid + self.addnstr(ystart - 1, 0, "mark path".ljust(self.wid), self.wid) + + whitespace = " " * maxlen + for line, items in zip(range(self.hei-1), sorted_bookmarks): + key, mark = items + string = " " + key + " " + mark.path + self.addstr(ystart + line, 0, whitespace) + self.addnstr(ystart + line, 0, string, self.wid) + + self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE) + + def _draw_info(self, lines): + self.columns[-1].clear_image(force=True) + self.need_clear = True + hei = min(self.hei - 1, len(lines)) + ystart = self.hei - hei + i = ystart + whitespace = " " * self.wid + for line in lines: + if i >= self.hei: + break + self.addstr(i, 0, whitespace) + self.addnstr(i, 0, line, self.wid) + i += 1 + + def _draw_hints(self): + self.columns[-1].clear_image(force=True) + self.need_clear = True + hints = [] + for k, v in self.fm.ui.keybuffer.pointer.items(): + k = key_to_string(k) + if isinstance(v, dict): + text = '...' + else: + text = v + if text.startswith('hint') or text.startswith('chain hint'): + continue + hints.append((k, text)) + hints.sort(key=lambda t: t[1]) + + hei = min(self.hei - 1, len(hints)) + ystart = self.hei - hei + self.addnstr(ystart - 1, 0, "key command".ljust(self.wid), + self.wid) + try: + self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE) + except: + pass + whitespace = " " * self.wid + i = ystart + for key, cmd in hints: + string = " " + key.ljust(11) + " " + cmd + self.addstr(i, 0, whitespace) + self.addnstr(i, 0, string, self.wid) + i += 1 + + def _collapse(self): + # Should the last column be cut off? (Because there is no preview) + if not self.settings.collapse_preview or not self.preview \ + or not self.stretch_ratios: + return False + result = not self.columns[-1].has_preview() + target = self.columns[-1].target + if not result and target and target.is_file: + if 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 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 resize(self, y, x, hei, wid): + DisplayableContainer.resize(self, y, x, hei, wid) + + def poke(self): + DisplayableContainer.poke(self) diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/view_miller.py index e9640208..b34cc579 100644 --- a/ranger/gui/widgets/browserview.py +++ b/ranger/gui/widgets/view_miller.py @@ -1,50 +1,49 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. -"""The BrowserView manages a set of BrowserColumns.""" +"""ViewMiller arranges the view in miller columns""" import curses, _curses from ranger.ext.signals import Signal -from ranger.ext.keybinding_parser import key_to_string -from . import Widget from .browsercolumn import BrowserColumn from .pager import Pager from ..displayable import DisplayableContainer +from ranger.gui.widgets.view_base import ViewBase -class BrowserView(Widget, DisplayableContainer): +class ViewMiller(ViewBase): 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 + def __init__(self, win): + ViewBase.__init__(self, win) + self.preview = True self.columns = [] self.pager = Pager(self.win, embedded=True) self.pager.visible = False self.add_child(self.pager) - self.change_ratios(ratios) + self.rebuild() 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) + self.settings.signal_bind('setopt.column_ratios', self.rebuild) self.old_draw_borders = self.settings.draw_borders - def change_ratios(self, ratios): - if isinstance(ratios, Signal): - ratios = ratios.value + def rebuild(self): + for child in self.container: + if isinstance(child, BrowserColumn): + self.remove_child(child) + child.destroy() + + ratios = self.settings.column_ratios for column in self.columns: column.destroy() @@ -82,9 +81,6 @@ class BrowserView(Widget, DisplayableContainer): 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() @@ -105,21 +101,6 @@ class BrowserView(Widget, DisplayableContainer): 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 - 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 - def _draw_borders(self): win = self.win self.color('in_browser', 'border') @@ -180,76 +161,6 @@ class BrowserView(Widget, DisplayableContainer): self.addch(0, right_end, curses.ACS_URCORNER) self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER) - def _draw_bookmarks(self): - self.columns[-1].clear_image(force=True) - self.fm.bookmarks.update_if_outdated() - self.color_reset() - self.need_clear = True - - sorted_bookmarks = sorted((item for item in self.fm.bookmarks \ - if self.fm.settings.show_hidden_bookmarks or \ - '/.' not in item[1].path), key=lambda t: t[0].lower()) - - hei = min(self.hei - 1, len(sorted_bookmarks)) - ystart = self.hei - hei - - maxlen = self.wid - self.addnstr(ystart - 1, 0, "mark path".ljust(self.wid), self.wid) - - whitespace = " " * maxlen - for line, items in zip(range(self.hei-1), sorted_bookmarks): - key, mark = items - string = " " + key + " " + mark.path - self.addstr(ystart + line, 0, whitespace) - self.addnstr(ystart + line, 0, string, self.wid) - - self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE) - - def _draw_info(self, lines): - self.columns[-1].clear_image(force=True) - self.need_clear = True - hei = min(self.hei - 1, len(lines)) - ystart = self.hei - hei - i = ystart - whitespace = " " * self.wid - for line in lines: - if i >= self.hei: - break - self.addstr(i, 0, whitespace) - self.addnstr(i, 0, line, self.wid) - i += 1 - - def _draw_hints(self): - self.columns[-1].clear_image(force=True) - self.need_clear = True - hints = [] - for k, v in self.fm.ui.keybuffer.pointer.items(): - k = key_to_string(k) - if isinstance(v, dict): - text = '...' - else: - text = v - if text.startswith('hint') or text.startswith('chain hint'): - continue - hints.append((k, text)) - hints.sort(key=lambda t: t[1]) - - hei = min(self.hei - 1, len(hints)) - ystart = self.hei - hei - self.addnstr(ystart - 1, 0, "key command".ljust(self.wid), - self.wid) - try: - self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE) - except: - pass - whitespace = " " * self.wid - i = ystart - for key, cmd in hints: - string = " " + key.ljust(11) + " " + cmd - self.addstr(i, 0, whitespace) - self.addnstr(i, 0, string, self.wid) - i += 1 - def _collapse(self): # Should the last column be cut off? (Because there is no preview) if not self.settings.collapse_preview or not self.preview \ @@ -270,7 +181,8 @@ class BrowserView(Widget, DisplayableContainer): def resize(self, y, x, hei, wid): """Resize all the columns according to the given ratio""" - DisplayableContainer.resize(self, y, x, hei, wid) + ViewBase.resize(self, y, x, hei, wid) + borders = self.settings.draw_borders pad = 1 if borders else 0 left = pad @@ -312,14 +224,6 @@ class BrowserView(Widget, DisplayableContainer): 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 @@ -343,7 +247,7 @@ class BrowserView(Widget, DisplayableContainer): pass def poke(self): - DisplayableContainer.poke(self) + ViewBase.poke(self) # Show the preview column when it has a preview but has # been hidden (e.g. because of padding_right = False) diff --git a/ranger/gui/widgets/view_multipane.py b/ranger/gui/widgets/view_multipane.py new file mode 100644 index 00000000..745a08dd --- /dev/null +++ b/ranger/gui/widgets/view_multipane.py @@ -0,0 +1,50 @@ +# This file is part of ranger, the console file manager. +# License: GNU GPL version 3, see the file "AUTHORS" for details. + +from ranger.gui.widgets.view_base import ViewBase +from ranger.gui.widgets.browsercolumn import BrowserColumn + +class ViewMultipane(ViewBase): + def __init__(self, win): + ViewBase.__init__(self, win) + + self.fm.signal_bind('tab.layoutchange', self._layoutchange_handler) + self.fm.signal_bind('tab.change', self._tabchange_handler) + self.rebuild() + + def _layoutchange_handler(self): + if self.fm.ui.browser == self: + self.rebuild() + + def _tabchange_handler(self, signal): + if self.fm.ui.browser == self: + if signal.old: + signal.old.need_redraw = True + if signal.new: + signal.new.need_redraw = True + + def rebuild(self): + self.columns = [] + + for child in self.container: + self.remove_child(child) + child.destroy() + for name, tab in self.fm.tabs.items(): + column = BrowserColumn(self.win, 0, tab=tab) + column.main_column = True + if name == self.fm.current_tab: + self.main_column = column + self.columns.append(column) + self.add_child(column) + self.resize(self.y, self.x, self.hei, self.wid) + + def resize(self, y, x, hei, wid): + ViewBase.resize(self, y, x, hei, wid) + column_width = int(float(wid) / len(self.columns)) + left = 0 + top = 0 + for i, column in enumerate(self.columns): + column.resize(top, left, hei, max(1, column_width - 1)) + left += column_width + column.need_redraw = True + self.need_redraw = True |