Staging
v0.5.0
https://foss.heptapod.net/mercurial/hgview
Raw File
Tip revision: 0fdcc033b9b3b62cd94fbbe463670786f500da8e authored by Alain Leufroy on 29 September 2011, 10:54:33 UTC
Added tag hgview-version-1.4.0 for changeset 1576aa8d7b12
Tip revision: 0fdcc03
mainframe.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""
Module that contains the curses main frame, using urwid, that mimics the 
vim/emacs interface.

+------------------------------------------------+
|                                                |
|                                                |
| body                                           |
|                                                |
|                                                |
+------------------------------------------------+
| banner                                         |
+------------------------------------------------+
| footer                                         |
+------------------------------------------------+

* *body* display the main contant
* *banner* display some short information on the current program state
* *footer* display program logs and it is used as input area

"""

import urwid
import logging
import urwid.raw_display
from urwid.signals import connect_signal, emit_signal

from hgviewlib.curses import helpviewer
from hgviewlib.curses import (CommandArg as CA, help_command,
                              register_command, unregister_command,
                              emit_command, connect_command,
                              hg_command_map)

def quitall():
    """
    usage: quall

    Quit the program
    """
    raise urwid.ExitMainLoop()

def close(mainframe):
    """
    Close the current buffer
    """
    try:
        mainframe.pop()
    except StopIteration: # last body => quit program
        quitall()

class MainFrame(urwid.Frame):
    """Main console frame that mimic the vim interface.

    You shall *register_commands* at startup then *unregister_commands* at end.

    """
    def __init__(self, name, body, *args, **kwargs):
        footer = Footer()
        self._bodies = {name:body}
        self._visible = name
        super(MainFrame, self).__init__(body=body, header=None, footer=footer,
                                        *args, **kwargs)
        connect_signal(footer, 'end command', 
                       lambda status: self.set_focus('body'))

    def register_commands(self):
        """Register specific command"""
        register_command(('quit','q'), 'Close the current pane.')
        register_command(('quitall', 'qa'), 'Quit the program.')
        register_command(('refresh', 'r'), 'Refresh the display')
        register_command(('help', 'h'), 'Show the help massage.',
                         CA('command', str,
                            ('command name for which to display the help. '
                             'Display the global help if omitted.')))

        connect_command('quit', close, args=(self,))
        connect_command('quitall', quitall)
        connect_command('help', self.show_command_help)
        self.body.register_commands()


    def unregister_commands(self):
        """unregister specific commands"""
        unregister_command('quit')
        unregister_command('q')
        unregister_command('quitall')
        unregister_command('qa')
        unregister_command('help')
        unregister_command('h')
        self.body.unregister_commands()

    def _get_visible(self):
        """return the name of the current visible body"""
        return self._visible
    def _set_visible(self, name):
        """modify the visible body giving its name"""
        self._visible = name
        self.body = self._bodies[self._visible]
    visible = property(_get_visible, _set_visible, None,
                       'name of the visible body')

    def add(self, name, body):
        """Add a body to the mainframe and focus on it"""
        self._bodies[name] = body
        self.visible = name

    def pop(self, name=None):
        """Remove and return a body (default to current). Then focus on the 
        last available or raise StopIteration."""
        if name is None:
            name = self.visible
        ret = self._bodies.pop(name)
        self.visible = self._bodies.__iter__().next()
        return ret

    def __contains__(self, name):
        """a.__contains__(b) <=> b in a

        Return True if `name` corresponds to a managed body
        """
        return name in self._bodies

    def keypress(self, size, key):
        """allow subclasses to intercept keystrokes"""
        key = super(MainFrame, self).keypress(size, key)
        if key is None:
            return
        if hg_command_map[key] == 'command key':
            emit_signal(self.footer, 'start command', key)
            self.set_focus('footer')
        elif hg_command_map[key] == 'close pane':
            emit_command('quit')
        else:
            cmd = hg_command_map[key]
            if cmd and cmd[0] == '@':
                emit_command(hg_command_map[key][1:])
            else:
                return key

    def show_command_help(self, command=None):
        """
        usage: edit [command]

        Show the help massage of the ``command``.

        :command: a command name for which to display the help.
                  If omited, the overall program help is displayed.
        """
        doc = None
        if command:
            logging.info(help_command(command))
        else:
            helpbody = helpviewer.HelpViewer(doc)
            helpbody.title = 'Main help'
            self.add('help', helpbody)
            logging.info('":q<CR>" to quit.')


    # better name for header as we use it as banner
    banner = property(urwid.Frame.get_header, urwid.Frame.set_header, None,
                      'banner widget')

class Footer(urwid.AttrWrap):
    """Footer widget used to display message and for inputs.
    """

    signals = ['start command', 'end command']

    def __init__(self, *args, **kwargs):
        super(Footer, self).__init__(
            urwid.Edit('type ":help<Enter>" for information'),
            'INFO', *args, **kwargs)
        connect_signal(self, 'start command', self.start_command)

    def start_command(self, key):
        """start looking for user's command"""
        # just for fun
        label = {'f5':'command: ', ':':':', 'meta x':'M-x '}[key]
        self.set('default', label, '')


    def keypress(self, size, key):
        "allow subclasses to intercept keystrokes"
        if hg_command_map[key] == 'validate':
            self.set('default')
            status = self.call_command()
            emit_signal(self, 'end command', not status)
        elif hg_command_map[key] == 'escape':
            self.set('default', '', '')
            emit_signal(self, 'end command', False)
        else:
            return super(Footer, self).keypress(size, key)

    def set(self, style=None, caption=None, edit=None):
        '''Set the footer content.

        :param style: a string that corresponds to a palette entry name
        :param caption: a string to display in caption
        :param edit: a string to display in the edit area
        '''
        if style is not None:
            self.set_attr(style)
        if caption is not None:
            self.set_caption(caption)
        if edit is not None:
            self.set_edit_text(edit)

    def call_command(self):
        '''
        Call the command that corresponds to the string given in the edit area
        '''
        cmdline = self.get_edit_text()
        if not cmdline:
            self.footer.set('default', '', '')
            return
        try:
            emit_command(cmdline)
            self.set('INFO')
        except urwid.ExitMainLoop: # exit, so do not catch this
            raise
        except Exception, err:
            logging.warn(err.__class__.__name__ + ': %s', str(err))
            logging.debug('Exception on: "%s"', cmdline, exc_info=True)

back to top