Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
# qtlib.py - Qt utility code## Copyright 2010 Steve Borho <steve@borho.org>## This software may be used and distributed according to the terms of the# GNU General Public License version 2 or any later version.importosimportatexitimportshutilimportstatimporttempfileimportrefromPyQt4.QtCoreimport*fromPyQt4.QtGuiimport*frommercurialimportextensionsfromtortoisehg.utilimporthglib,pathsfromhgext.colorimport_stylestmproot=Nonedefgettempdir():globaltmprootdefcleanup():defwriteable(arg,dirname,fnames):forfnameinfnames:os.chmod(os.path.join(dirname,fname),stat.S_IWUSR)try:os.path.walk(tmproot,writeable,None)shutil.rmtree(tmproot)except:passifnottmproot:tmproot=tempfile.mkdtemp(prefix='thg.')atexit.register(cleanup)returntmproot# _styles maps from ui labels to effects# _effects maps an effect to font style properties. We define a limited# set of _effects, since we convert color effect names to font style# effect programatically._effects={'bold':'font-weight: bold','italic':'font-style: italic','underline':'text-decoration: underline',}_thgstyles={# Styles defined by TortoiseHg'log.branch':'black #aaffaa_background','log.patch':'black #aaddff_background','log.unapplied_patch':'black #dddddd_background','log.tag':'black #ffffaa_background','log.bookmark':'blue #ffffaa_background','log.curbookmark':'black #ffdd77_background','log.modified':'black #ffddaa_background','log.added':'black #aaffaa_background','log.removed':'black #ffcccc_background','status.deleted':'red bold','ui.error':'red bold #ffcccc_background','control':'black bold #dddddd_background',}thgstylesheet='* { white-space: pre; font-family: monospace; font-size: 9pt; }'defconfigstyles(ui):# extensions may provide more labels and default effectsforname,extinextensions.extensions():_styles.update(getattr(ext,'colortable',{}))# tortoisehg defines a few labels and default effects_styles.update(_thgstyles)# allow the user to overrideforstatus,cfgeffectsinui.configitems('color'):if'.'notinstatus:continuecfgeffects=ui.configlist('color',status)_styles[status]=' '.join(cfgeffects)forstatus,cfgeffectsinui.configitems('thg-color'):if'.'notinstatus:continuecfgeffects=ui.configlist('thg-color',status)_styles[status]=' '.join(cfgeffects)# See http://doc.trolltech.com/4.2/richtext-html-subset.html# and http://www.w3.org/TR/SVG/types.html#ColorKeywordsdefgeteffect(labels):'map labels like "log.date" to Qt font styles'labels=str(labels)# Could be QStringeffects=[]# Multiple labels may be requestedforlinlabels.split():ifnotl:continue# Each label may request multiple effectses=_styles.get(l,'')foreines.split():ifein_effects:effects.append(_effects[e])elife.endswith('_background'):e=e[:-11]ife.startswith('#')oreinQColor.colorNames():effects.append('background-color: '+e)elife.startswith('#')oreinQColor.colorNames():# Accept any valid QColoreffects.append('color: '+e)return';'.join(effects)defapplyeffects(chars,effects):return'<span style="white-space: pre;%s">%s</span>'%(effects,chars)defgetbgcoloreffect(labels):"""Map labels like "log.date" to background color if available Returns QColor object. You may need to check validity by isValid(). """forlinstr(labels).split():ifnotl:continueforein_styles.get(l,'').split():ife.endswith('_background'):returnQColor(e[:-11])returnQColor()NAME_MAP={'fg':'color','bg':'background-color','family':'font-family','size':'font-size','weight':'font-weight','space':'white-space','style':'font-style','decoration':'text-decoration',}defmarkup(msg,**styles):style={'white-space':'pre'}forname,valueinstyles.items():ifnotvalue:continueifnameinNAME_MAP:name=NAME_MAP[name]style[name]=valuestyle=';'.join(['%s: %s'%tfortinstyle.items()])msg=hglib.tounicode(msg)msg=Qt.escape(msg)msg=msg.replace('\n','<br />')return'<span style="%s">%s</span>'%(style,msg)defdescriptionhtmlizer(ui):"""Return a function to mark up ctx.description() as an HTML >>> from mercurial import ui >>> u = ui.ui() >>> htmlize = descriptionhtmlizer(u) >>> htmlize('foo <bar> \\n& <baz>') u'foo <bar> \\n& <baz>' changeset hash link: >>> htmlize('foo af50a62e9c20 bar') u'foo <a href="cset:af50a62e9c20">af50a62e9c20</a> bar' >>> htmlize('af50a62e9c2040dcdaf61ba6a6400bb45ab56410') # doctest: +ELLIPSIS u'<a href="cset:af...10">af...10</a>' http/https links: >>> s = htmlize('foo http://example.com:8000/foo?bar=baz&bax#blah') >>> (s[:63], s[63:]) # doctest: +NORMALIZE_WHITESPACE (u'foo <a href="http://example.com:8000/foo?bar=baz&bax#blah">', u'http://example.com:8000/foo?bar=baz&bax#blah</a>') >>> htmlize('https://example/') u'<a href="https://example/">https://example/</a>' issue links: >>> u.setconfig('tortoisehg', 'issue.regex', r'#(\\d+)\\b') >>> u.setconfig('tortoisehg', 'issue.link', 'http://example/issue/{1}/') >>> htmlize = descriptionhtmlizer(u) >>> htmlize('foo #123') u'foo <a href="http://example/issue/123/">#123</a>' missing issue.link setting: >>> u.setconfig('tortoisehg', 'issue.link', '') >>> htmlize = descriptionhtmlizer(u) >>> htmlize('foo #123') u'foo #123' too many replacements in issue.link: >>> u.setconfig('tortoisehg', 'issue.link', 'http://example/issue/{1}/{2}') >>> htmlize = descriptionhtmlizer(u) >>> htmlize('foo #123') u'foo #123' invalid regexp in issue.regex: >>> u.setconfig('tortoisehg', 'issue.regex', '(') >>> htmlize = descriptionhtmlizer(u) >>> htmlize('foo #123') u'foo #123' >>> htmlize('http://example/') u'<a href="http://example/">http://example/</a>' """csmatch=r'(\b[0-9a-f]{12}(?:[0-9a-f]{28})?\b)'httpmatch=r'(\b(http|https)://([-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]))'regexp=r'%s|%s'%(csmatch,httpmatch)bodyre=re.compile(regexp)issuematch=ui.config('tortoisehg','issue.regex')issuerepl=ui.config('tortoisehg','issue.link')ifissuematchandissuerepl:regexp+='|(%s)'%issuematchtry:bodyre=re.compile(regexp)exceptre.error:passdefhtmlize(desc):"""Mark up ctx.description() [localstr] as an HTML [unicode]"""desc=unicode(Qt.escape(hglib.tounicode(desc)))buf=''pos=0forminbodyre.finditer(desc):a,b=m.span()ifa>=pos:buf+=desc[pos:a]pos=bgroups=m.groups()ifgroups[0]:cslink=groups[0]buf+='<a href="cset:%s">%s</a>'%(cslink,cslink)ifgroups[1]:urllink=groups[1]buf+='<a href="%s">%s</a>'%(urllink,urllink)iflen(groups)>4andgroups[4]:issue=groups[4]issueparams=groups[4:]try:link=re.sub(r'\{(\d+)\}',lambdam:issueparams[int(m.group(1))],issuerepl)buf+='<a href="%s">%s</a>'%(link,issue)exceptIndexError:buf+=issueifpos<len(desc):buf+=desc[pos:]returnbufreturnhtmlize_iconcache={}def_findicon(name):# TODO: icons should be placed at single location before releaseforpfxin(':/icons',os.path.join(paths.get_icon_path(),'svg'),paths.get_icon_path()):forextin('svg','png','ico'):path='%s/%s.%s'%(pfx,name,ext)ifQFile.exists(path):returnQIcon(path)returnNone# http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html_SCALABLE_ICON_PATHS=[(QSize(),'scalable/actions','.svg'),(QSize(),'scalable/apps','.svg'),(QSize(),'scalable/status','.svg'),(QSize(16,16),'16x16/apps','.png'),(QSize(22,22),'22x22/actions','.png'),(QSize(32,32),'32x32/actions','.png'),(QSize(24,24),'24x24/actions','.png')]def_findscalableicon(name):"""Find icon from qrc by using freedesktop-like icon lookup"""o=QIcon()forsize,subdir,sfxin_SCALABLE_ICON_PATHS:path=':/icons/%s/%s%s'%(subdir,name,sfx)ifQFile.exists(path):formodein(QIcon.Normal,QIcon.Active):o.addFile(path,size,mode)ifnoto.isNull():returnodefgeticon(name):""" Return a QIcon for the specified name. (the given 'name' parameter must *not* provide the extension). This searches for the icon from theme, Qt resource or icons directory, named as 'name.(svg|png|ico)'. """ifQIcon.hasThemeIcon(name):returnQIcon.fromTheme(name)try:return_iconcache[name]exceptKeyError:_iconcache[name]=(_findscalableicon(name)or_findicon(name)orQIcon(':/icons/fallback.svg'))return_iconcache[name]_pixmapcache={}defgetpixmap(name,width=16,height=16):key='%s_%sx%s'%(name,width,height)try:return_pixmapcache[key]exceptKeyError:pixmap=geticon(name).pixmap(width,height)_pixmapcache[key]=pixmapreturnpixmapclassThgFont(QObject):changed=pyqtSignal(QFont)def__init__(self,name):QObject.__init__(self)self.myfont=QFont()self.myfont.fromString(name)deffont(self):returnself.myfontdefsetFont(self,f):self.myfont=fself.changed.emit(f)_fontdefaults={'fontcomment':'monospace,10','fontdiff':'monospace,10','fontlist':'sans,9','fontlog':'monospace,10','fontoutputlog':'sans,8'}_fontcache={}definitfontcache(ui):fornamein_fontdefaults:fname=ui.config('tortoisehg',name,_fontdefaults[name])_fontcache[name]=ThgFont(fname)defgetfont(name):assertnamein_fontdefaultsreturn_fontcache[name]defCommonMsgBox(icon,title,main,text='',buttons=QMessageBox.Ok,labels=[],parent=None,defaultbutton=None):msg=QMessageBox(parent)msg.setIcon(icon)msg.setWindowTitle(title)msg.setStandardButtons(buttons)forbutton_id,labelinlabels:msg.setButtonText(button_id,label)ifdefaultbutton:msg.setDefaultButton(defaultbutton)msg.setText('<b>%s</b>'%main)info=''forlineintext.split('\n'):info+='<nobr>%s</nobr><br />'%linemsg.setInformativeText(info)returnmsg.exec_()defInfoMsgBox(*args,**kargs):returnCommonMsgBox(QMessageBox.Information,*args,**kargs)defWarningMsgBox(*args,**kargs):returnCommonMsgBox(QMessageBox.Warning,*args,**kargs)defErrorMsgBox(*args,**kargs):returnCommonMsgBox(QMessageBox.Critical,*args,**kargs)defQuestionMsgBox(*args,**kargs):btn=QMessageBox.Yes|QMessageBox.Nores=CommonMsgBox(QMessageBox.Question,buttons=btn,*args,**kargs)returnres==QMessageBox.YesclassCustomPrompt(QMessageBox):def__init__(self,title,message,parent,choices,default=None,esc=None,files=None):QMessageBox.__init__(self,parent)self.setWindowTitle(hglib.tounicode(title))self.setText(hglib.tounicode(message))iffiles:self.setDetailedText(hglib.tounicode('\n'.join(files)))self.hotkeys={}fori,sinenumerate(choices):btn=self.addButton(s,QMessageBox.AcceptRole)try:char=s[s.index('&')+1].lower()self.hotkeys[char]=btnifdefault==i:self.setDefaultButton(btn)ifesc==i:self.setEscapeButton(btn)except(ValueError,IndexError):passdefrun(self):returnself.exec_()defkeyPressEvent(self,event):fork,btninself.hotkeys.iteritems():ifevent.text()==k:btn.clicked.emit()super(CustomPrompt,self).keyPressEvent(event)defsetup_font_substitutions():QFont.insertSubstitutions('monospace',['monaco','courier new'])deffix_application_font():ifos.name!='nt':returntry:importwin32gui,win32conexceptImportError:return# On Windows, the font for main window seems to be determined by "icon"# font, but Qt chooses uncustomizable system font.lf=win32gui.SystemParametersInfo(win32con.SPI_GETICONTITLELOGFONT)f=QFont(hglib.tounicode(lf.lfFaceName))f.setItalic(lf.lfItalic)iflf.lfWeight!=win32con.FW_DONTCARE:weights=[(0,QFont.Light),(400,QFont.Normal),(600,QFont.DemiBold),(700,QFont.Bold),(800,QFont.Black)]n,w=filter(lambdae:e[0]<=lf.lfWeight,weights)[-1]f.setWeight(w)f.setPixelSize(abs(lf.lfHeight))QApplication.setFont(f,'QMainWindow')classPMButton(QPushButton):"""Toggle button with plus/minus icon images"""def__init__(self,expanded=True,parent=None):QPushButton.__init__(self,parent)size=QSize(11,11)self.setIconSize(size)self.setMaximumSize(size) self.setFlat(True)
self.setAutoDefault(False)
- self.plus = geticon('plus')
- self.minus = geticon('minus')
+ self.plus = geticon('expander-open')
+ self.minus = geticon('expander-close')
icon = expanded and self.minus or self.plus
self.setIcon(icon)
defclicked():icon=self.is_expanded()andself.plusorself.minusself.setIcon(icon)self.clicked.connect(clicked)defset_expanded(self,state=True):icon=stateandself.minusorself.plusself.setIcon(icon)defset_collapsed(self,state=True):icon=stateandself.plusorself.minusself.setIcon(icon)defis_expanded(self):returnself.icon().serialNumber()==self.minus.serialNumber()defis_collapsed(self):returnnotself.is_expanded()classClickableLabel(QLabel):clicked=pyqtSignal()def__init__(self,label,parent=None):QLabel.__init__(self,parent)self.setText(label)defmouseReleaseEvent(self,event):self.clicked.emit()classExpanderLabel(QWidget):expanded=pyqtSignal(bool)def__init__(self,label,expanded=True,stretch=True,parent=None):QWidget.__init__(self,parent)box=QHBoxLayout()box.setSpacing(4)box.setContentsMargins(*(0,)*4)self.button=PMButton(expanded,self)self.button.clicked.connect(self.pm_clicked)box.addWidget(self.button)self.label=ClickableLabel(label,self)self.label.clicked.connect(lambda:self.button.click())box.addWidget(self.label)ifnotstretch:box.addStretch(0)self.setLayout(box)defpm_clicked(self):self.expanded.emit(self.button.is_expanded())defset_expanded(self,state=True):ifnotself.button.is_expanded()==state:self.button.set_expanded(state)self.expanded.emit(state)defis_expanded(self):returnself.button.is_expanded()classStatusLabel(QWidget):def__init__(self,parent=None):QWidget.__init__(self,parent)box=QHBoxLayout()box.setContentsMargins(*(0,)*4)self.status_icon=QLabel()self.status_icon.setMaximumSize(16,16)self.status_icon.setAlignment(Qt.AlignCenter)box.addWidget(self.status_icon)self.status_text=QLabel()self.status_text.setAlignment(Qt.AlignVCenter|Qt.AlignLeft)box.addWidget(self.status_text)box.addStretch(0)self.setLayout(box)defset_status(self,text,icon=None):self.set_text(text)self.set_icon(icon)defclear_status(self):self.clear_text()self.clear_icon()defset_text(self,text=''):iftextisNone:text=''self.status_text.setText(text)defclear_text(self):self.set_text()defset_icon(self,icon=None):ificonisNone:self.clear_icon()else:ifisinstance(icon,bool):icon=geticon(iconand'thg-success'or'thg-error')elifisinstance(icon,basestring):icon=geticon(icon)elifnotisinstance(icon,QIcon):raiseTypeError,'%s: bool, str or QIcon'%type(icon)self.status_icon.setShown(True)self.status_icon.setPixmap(icon.pixmap(16,16))defclear_icon(self):self.status_icon.setHidden(True)classLabeledSeparator(QWidget):def__init__(self,label=None,parent=None):QWidget.__init__(self,parent)box=QHBoxLayout()box.setContentsMargins(*(0,)*4)iflabel:ifisinstance(label,basestring):label=QLabel(label)box.addWidget(label)sep=QFrame()sep.setFrameShadow(QFrame.Sunken)sep.setFrameShape(QFrame.HLine)box.addWidget(sep,1,Qt.AlignVCenter)self.setLayout(box)classWidgetGroups(object):""" Support for bulk-updating properties of Qt widgets """def__init__(self):object.__init__(self)self.clear(all=True)### Public Methods ###defadd(self,widget,group='default'):ifgroupnotinself.groups:self.groups[group]=[]widgets=self.groups[group]ifwidgetnotinwidgets:widgets.append(widget)defremove(self,widget,group='default'):ifgroupnotinself.groups:returnwidgets=self.groups[group]ifwidgetinwidgets:widgets.remove(widget)defclear(self,group='default',all=True):ifall:self.groups={}else:delself.groups[group]defset_prop(self,prop,value,group='default',cond=None):ifgroupnotinself.groups:returnwidgets=self.groups[group]ifcallable(cond):widgets=[wforwinwidgetsifcond(w)]forwidgetinwidgets:getattr(widget,prop)(value)defset_visible(self,*args,**kargs):self.set_prop('setVisible',*args,**kargs)defset_enable(self,*args,**kargs):self.set_prop('setEnabled',*args,**kargs)classSharedWidget(QWidget):"""Share a single widget by many parents It makes a widget sharable by many parent widgets. When the user shows the widget (showEvent occured), it reparents the stored widget to the latest active widget. Any signals connected via SharedWidget are disconnected/connected automatically when owner changes. """def__init__(self,widget,parent=None):super(SharedWidget,self).__init__(parent)self._widget=widgetself._fakesignals={}vbox=QVBoxLayout()vbox.setContentsMargins(*(0,)*4)self.setLayout(vbox)defshowEvent(self,event):"""Change the parent of the stored widget if necessary"""ifself._widget.parent()!=self:self._reconnectfakesignals(self._widget.parent())self.layout().addWidget(self._widget)super(SharedWidget,self).showEvent(event)defget(self):"""Returns the stored widget"""returnself._widgetdef__getattr__(self,name):try:ifisinstance(getattr(self._widget.__class__,name),pyqtSignal):returnself._fakesignal(name)exceptAttributeError:passreturngetattr(self._widget,name)class_FakeSignal(object):"""Imitate pyqtSignal object to hook signal connection"""def__init__(self):self._connections=[]defconnect(self,slot):self._connections.append(slot)returnTruedefdisconnect(self,slot):try:self._connections.remove(slot)returnTrueexceptValueError:returnFalsedef_fakesignal(self,name):ifnamenotinself._fakesignals:self._fakesignals[name]=self._FakeSignal()returnself._fakesignals[name]def_iterfakeconnections(self):forname,fakesiginself._fakesignals.iteritems():forslotinfakesig._connections:yieldname,slotdef_connectfakesignals(self):forname,slotinself._iterfakeconnections():getattr(self._widget,name).connect(slot)def_disconnectfakesignals(self):forname,slotinself._iterfakeconnections():getattr(self._widget,name).disconnect(slot)def_reconnectfakesignals(self,curowner):ifisinstance(curowner,self.__class__):curowner._disconnectfakesignals()self._connectfakesignals()classDemandWidget(QWidget):'Create a widget the first time it is shown'def__init__(self,createfunc,parent=None):super(DemandWidget,self).__init__(parent)self._createfunc=createfuncself._widget=Nonevbox=QVBoxLayout()vbox.setContentsMargins(*(0,)*4)self.setLayout(vbox)defshowEvent(self,event):"""create the widget if necessary"""self.get()super(DemandWidget,self).showEvent(event)defforward(self,funcname,*args,**opts):ifself._widget:returngetattr(self._widget,funcname)(*args)returnopts.get('default')defget(self):"""Returns the stored widget"""ifself._widgetisNone:self._widget=self._createfunc()self.layout().addWidget(self._widget)returnself._widgetdef__getattr__(self,name):returngetattr(self._widget,name)
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.