これはTortoiseHg Advent Calender 2012 8日目の記事です
昨日は、 @cointossさんの
TortoiseHgを使ってファイル登録しコミットしよう - cointoss.log でした
Mercurialでは、エクステンションを導入することによって新たにコマンドを追加したり、動作をカスタマイズしたりすることができます。
これは本来Mercurial本体を対象とした仕組みなんですが、TortoiseHg実行時にもエクステンションは読み込まれるので、実はTortoiseHgの動作をカスタマイズすることも出来るんですね。
もちろん、明確なカスタマイズインフラが用意されている訳ではないのですが、そこは独自言語パイソンさんの気安さで、読み込まれてしまえば既存のクラスを書き換えたり追加したり、たいていのことは出来るわけです。
という訳で、例えばこんなことができるよ、というご紹介も兼ねて、
##thg-itarize エクステンション
ってのを作ってみました。
面倒なのでソースそのまま貼ります。
# -*- coding: utf-8 -*-
import os
import sys
def uisetup(ui):
if not os.path.basename(sys.argv[0]).startswith('thg'):
# TortoiseHg以外から読み込まれた場合は何もしない
return
from mercurial import util
from PyQt4 import QtGui, QtCore
from types import MethodType
class Config(object):
"""設定値を保持する。画像の読み込みも受け持つ"""
def __init__(self, ui):
self.ui = ui
@util.propertycache
def default_image(self):
path = self.ui.config('itarize', 'image.default')
return self.get_pixmap(path, "white")
@util.propertycache
def clicked_image(self):
path = self.ui.config('itarize', 'image.clicked')
return self.get_pixmap(path, "white") or self.default_image
@util.propertycache
def opacity(self):
try:
val = float(self.ui.config('itarize', 'opacity', "1"))
return min(max(val, 0), 1)
except ValueError:
return 0.5
@util.propertycache
def autoscale(self):
val = self.ui.config('itarize', 'autoscale', 'true').lower()
return val in ('1', 'yes', 'true')
def get_pixmap(self, path, mask_color):
if not path:
return None
img = QtGui.QImage(path)
p = QtGui.QPixmap.fromImage(img)
return p
def Itarate(cls):
"""指定されたクラスに装飾を施す。clsはQWidgetから派生している必要がある"""
conf = Config(ui)
def get_image_rect(widget, img):
vp = widget.viewport()
ww, wh = vp.width(), vp.height()
iw, ih = img.width(), img.height()
if (ww < iw or wh < ih) and conf.autoscale:
scale = min(float(ww) / iw, float(wh) / ih)
iw *= scale
ih *= scale
return QtCore.QRect(ww - iw, wh - ih, iw, ih)
cls_init = cls.__init__
def __init__(self, *args, **kwargs):
cls_init(self, *args, **kwargs)
self.img = conf.default_image
if hasattr(self, "verticalScrollBar"):
self.verticalScrollBar().valueChanged.connect(
self.viewport().update
)
if hasattr(self, "horizontalScrollBar"):
self.horizontalScrollBar().valueChanged.connect(
self.viewport().update
)
def paintEvent(self, event, org=cls.paintEvent):
org(self, event)
if not self.img:
return
vp = self.viewport()
p = QtGui.QPainter(vp)
p.setRenderHints(QtGui.QPainter.SmoothPixmapTransform
| QtGui.QPainter.Antialiasing)
p.setOpacity(conf.opacity)
rc = get_image_rect(self, self.img)
p.drawPixmap(rc, self.img)
p.end()
def mousePressEvent(self, event, org=cls.mousePressEvent):
org(self, event)
if event.buttons() == QtCore.Qt.LeftButton:
rc = get_image_rect(self, self.img)
if rc.left() <= event.x() and rc.top() <= event.y():
self.img = conf.clicked_image
self.viewport().update()
def mouseReleaseEvent(self, event, org=cls.mouseReleaseEvent):
org(self, event)
if getattr(self, "img", None) != conf.default_image:
self.img = conf.default_image
self.viewport().update()
cls.__init__ = MethodType(__init__, None, cls)
cls.paintEvent = MethodType(paintEvent, None, cls)
cls.mousePressEvent = MethodType(mousePressEvent, None, cls)
cls.mouseReleaseEvent = MethodType(mouseReleaseEvent, None, cls)
# AnnotateViewを装飾
from tortoisehg.hgqt.fileview import AnnotateView
Itarate(AnnotateView)
###何ができるの?
####こうなります
高階ことり [(c)mzp (CC-by-SA)]
これでつらい残業時間も頑張れますね :)
###使い方
- あなたの嫁画像を用意します。リアル嫁でも2次嫁でも猫でもお好きなものを。透過png形式がいいと思います。
- Mercurial.iniやhgrcに、設定を追加します。
[extensions]
itarize = itarize.pyのパス
[itarize]
image.default = 画像ファイルのパス
ファイルが見えねえよ!ジャマだよ!という愛の足らない人は、opacity = 0.5
とか指定するといいと思います。
image.clicked = 別の画像
という指定を追加すると、クリックしたときに別の画像が表示されるようになります。
クリックした座標によって違うリアクションを返せるようにとかすると実用度()が上がるんですが、変態Advent Calender行きになりかねないので自重しました。
ちなみに、最後の2行をちょっと書き換えると、別のところに出てきます。例えば
from tortoisehg.hgqt.repoview import HgRepoView
Itarate(HgRepoView)
###解説
といっても、解説するほどのことはやってないですが。
-
普通のエクステンションと違って、TortoiseHgに対してしか効果をもたないものなので、最初にTortoiseHgからの起動かどうかをチェックして、違ったら何もしないようにしています。
-
PyQtのウィジェットは、paintEventメソッドをオーバーライドすることによって描画処理をカスタマイズすることができます。
このエクステンションでは、TortoiseHgのファイル表示ウィジェットであるAnnotateViewのpaintEventの処理を置き換えて、画像のオーバーレイ処理を追加してる訳ですね。 -
このサンプルではあまり使ってませんが、シグナルハンドラ(いわゆるイベントハンドラのことです)をうまく使えば、工夫次第でいろいろなカスタマイズが比較的安全に実現できると思います。
##まとめ
- エクステンションでTortoiseHgに対して色々悪さするのも、なかなか楽しいです。
- ことりちゃんかわいいよことりちゃん
それでは、Have a happy Mercurial life !
※ことりちゃんって誰? って方は、こちらをどうぞ -> http://mzp.hatenablog.com/entry/2012/12/04/212043