Staging
v0.8.1
v0.8.1
https://foss.heptapod.net/mercurial/hgview
Tip revision: 068bf73b24c4448f54b6636a9ecd03dc3a532f90 authored by Pierre-Yves David on 08 August 2012, 13:22:06 UTC
[setup] fix qt4 building process
[setup] fix qt4 building process
Tip revision: 068bf73
application.py
# -*- coding: utf-8 -*-
# Copyright (c) 2003-2012 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, see <http://www.gnu.org/licenses/>.
"""
Application utilities.
"""
import threading
import logging
import sys
from urwid import AttrWrap, MainLoop
from hgviewlib.application import HgViewApplication
from hgviewlib.curses.hgrepoviewer import RepoViewer
from hgviewlib.curses import MainFrame, emit_command, activate_delayed_signals
try:
import pygments
from pygments.token import Token, _TokenType
except ImportError:
# pylint: disable=C0103
pygments = None
# pylint: enable=C0103
# _________________________________________________________________ Applicaiton
class HgViewUrwidApplication(HgViewApplication):
"""
HgView application using urwid.
"""
HgRepoViewer = RepoViewer
def __init__(self, *args, **kwargs):
super(HgViewUrwidApplication, self).__init__(*args, **kwargs)
self.viewer = AttrWrap(self.viewer, 'body')
mainframe = MainFrame('repoviewer', self.viewer)
screen = self.get_screen()
self.mainloop = MainLoop(mainframe, PALETTE, screen)
connect_logging(self.mainloop, level=logging.DEBUG)
mainframe.register_commands()
self.enable_inotify()
activate_delayed_signals(self.mainloop)
self.mainframe = mainframe
# register_command('alarm', 'process callback in a given seconds',
def get_screen(self):
"""return the screen instance to use"""
if self.opts.interface == 'raw':
from urwid.raw_display import Screen
elif self.opts.interface == 'curses':
from urwid.curses_display import Screen
if pygments:
return patch_screen(Screen)()
return Screen()
def enable_inotify(self):
"""enable inotify watching"""
enable_inotify = True # XXX config
optimize_inotify = True # XXX config
if enable_inotify:
if optimize_inotify:
import ctypes.util
orig = ctypes.util.find_library
ctypes.util.find_library = lambda lib: None # durty optim
inotify(self.mainloop)
if optimize_inotify:
ctypes.util.find_library = orig
def exec_(self):
'''main entry point'''
if '--profile' in sys.argv or '--time' in sys.argv:
self.mainloop._run = self.mainloop.draw_screen
out = self.mainloop.run()
self.mainframe.unregister_commands()
return out
# _____________________________________________________________________ inotify
def inotify(mainloop):
"""add inotify watcher to the mainloop"""
try:
from hgviewlib.inotify import Inotify as Inotify
except ImportError:
return
class UrwidInotify(Inotify):
"""Inotify handler that can be connected to as urwid mainloop."""
def __init__(self, *args, **kwargs):
super(UrwidInotify, self).__init__(*args, **kwargs)
self._input_timeout = None
def process_finally(self):
"""Really process the inotify event"""
self._input_timeout = None
super(UrwidInotify, self).process()
def process_on_any_event(self):
"""Process all inotify events and prevent over-processing"""
# get all events on every files.
# Ignore files that shall be ignored be mercurial
# Also ignore hg-checkexec* files that are created by mercurial
# to check available file status.
for fname in self.read_events():
if fname.startswith(('hg-checkexec', 'hg-checklink')):
break
if self.repo.dirstate._dirignore(fname):
break
else:
# use the urwid mainloop to schedule the screen refreshing in 0.2s
# and ignore events received during this time.
# It prevents over-refreshing (See ../inotify.py comments).
if self._input_timeout is None:
self._input_timeout = mainloop.set_alarm_in(
0.2, lambda *args: self.process_finally())
try:
refresh = lambda: emit_command('refresh')
inot = UrwidInotify(mainloop.widget.get_body().repo, refresh)
except:
return
mainloop.event_loop.watch_file(inot.get_fd(), inot.process_on_any_event)
# add watchers thought a thread to reduce start duration with a big repo
threading.Thread(target=inot.update).start()
# ________________________________________________________________ patch screen
def patch_screen(screen_cls):
"""
Return a patched screen class that allows parent token inheritence in
the palette
"""
class Palette(dict):
"""Special dictionary that take into account parent token inheritence.
"""
def __contains__(self, key):
if super(Palette, self).__contains__(key):
return True
if (not isinstance(key, _TokenType)) or (key.parent is None):
return False
if key.parent in self: # fonction is now recursive
self[key] = self[key.parent] # cache + __getitem__ ok
return True
return False
has_key = __contains__
class PatchedScreen(screen_cls, object):
"""hack Screen to allow parent token inheritence in the palette"""
# Use a special container for storing style definition. This container
# take into accoutn parent token inheritence
# raw_display.Screen store the palette definition in the container
# ``_pal_escape``, web_display and curses display in ``palette`` and
# ``attrconv``
def __init__(self, *args):
self._hgview_palette = None
self._hgview_attrconv = None
# mro problem with web_display, so do not use super
screen_cls.__init__(self)
def _hgview_get_palette(self):
"""Return the palette"""
return self._hgview_palette
def _hgview_set_palette(self, value):
"""Set the palette"""
self._hgview_palette = Palette()
if value:
self._hgview_palette.update(value)
# pylint: disable=E0602
_pal_escape = property(_hgview_get_palette, _hgview_set_palette)
palette = _pal_escape
def _hgview_get_attrconv(self):
"""Return the palette"""
return self._hgview_attrconv
def _hgview_set_attrconv(self, value):
"""Set the palette"""
self._hgview_attrconv = Palette()
if value:
self._hgview_attrconv.update(value)
# pylint: disable=E0602
attrconv = property(_hgview_get_attrconv, _hgview_set_attrconv)
return PatchedScreen
# _____________________________________________________________________ logging
def connect_logging(mainloop, level=logging.INFO):
'''Connect logging to the hgview console application.
(The widget of the mainloop must be a ``MainFrame`` instance)
You may add 'DEBUG', 'WARNING', 'ERROR' and 'CRITICAL' styles in the
palette.
'''
class ConsoleHandler(logging.Handler):
'''Handler for logging to the footer of a ``MainFrame`` instance.
You shall prefer to link logging and you application by using the
``connect_logging(...)`` function.
'''
def __init__(self, callback, redraw, redraw_levelno=logging.CRITICAL):
"""
:param callback: A funtion called to display a message as
``callback(style, levelname, message)`` where:
* ``levelname`` is the name of the message level
* ``message`` is the message to display
Mostly, it is the ``set`` method of a ``Footer`` instance.
:param redraw: a function that performe the screen redrawing
"""
self.callback = callback
self.redraw = redraw
self.redraw_levelno = redraw_levelno
logging.Handler.__init__(self)
def emit(self, record):
"""emit a record"""
if isinstance(record.msg, list): # urwid style
name = 'default'
msg = record.msg
else:
name = record.levelname
msg = self.format(record)
self.callback(name, msg)
if record.levelno >= self.redraw_levelno:
self.flush()
def flush(self):
try:
self.redraw()
except AssertionError:
pass
logger = logging.getLogger()
logger.setLevel(level)
display = lambda style, msg: mainloop.widget.footer.set(style, msg, '')
handler = ConsoleHandler(display, mainloop.draw_screen)
logger.addHandler(handler)
# ________________________________________________________________ patch screen
PALETTE = [
('default','default','default'),
('body','default','default', 'standout'),
('banner','black','light gray', 'bold'),
('focus','black','dark cyan', 'bold'),
('focus.alternate','black','dark magenta', 'bold'),
('current', 'black', 'dark green', 'bold'),
('modified', 'black', 'brown', 'bold'),
# logging
('DEBUG', 'dark magenta', 'default'),
('INFO', 'dark gray', 'default'),
('WARNING', 'brown', 'default'),
('ERROR', 'dark red', 'default'),
('CRITICAL', 'light red', 'default'),
# graphlog
('ID', 'brown', 'default', 'standout'),
('Log', 'default', 'default'),
('GraphLog', 'default', 'default', 'bold'),
('GraphLog.node', 'default', 'default', 'bold'),
('Author', 'dark blue', 'default', 'bold'),
('Date', 'dark green', 'default', 'bold'),
('Tags', 'yellow', 'dark red', 'bold'),
('Bookmarks', 'default', 'dark blue'),
('Branch', 'yellow', 'default', 'bold'),
('Filename', 'white', 'default', 'bold'),
('obsolete', 'dark cyan', 'default'),
# filelist
('+', 'dark green', 'default'),
('-', 'dark red', 'default'),
('=', 'default', 'default'),
('?', 'brown', 'default'),
]
if pygments:
PALETTE += [
(Token, 'default', 'default'),
(Token.Text, 'default', 'default'),
(Token.Comment, 'dark gray', 'default'),
(Token.Punctuation, 'white', 'default', 'bold'),
(Token.Operator, 'light blue', 'default'),
(Token.Literal, 'dark magenta', 'default'),
(Token.Name, 'default', 'default'),
(Token.Name.Builtin, 'dark blue', 'default'),
(Token.Name.Namespace, 'dark blue', 'default'),
(Token.Name.Builtin.Pseudo, 'dark blue', 'default'),
(Token.Name.Exception, 'dark blue', 'default'),
(Token.Name.Decorator, 'dark blue', 'default'),
(Token.Name.Class, 'dark blue', 'default'),
(Token.Name.Function, 'dark blue', 'default'),
(Token.Keyword, 'light green', 'default'),
(Token.Generic.Deleted, 'dark red', 'default'),
(Token.Generic.Inserted, 'dark green', 'default'),
(Token.Generic.Subheading, 'dark magenta', 'default', 'bold'),
(Token.Generic.Heading, 'black', 'dark magenta'),
]