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.importosimportsysimportatexitimportshutilimportstatimportsubprocessimporttempfileimportreimportweakreffrommercurialimportextensions,error,utilfromtortoisehg.utilimporthglib,pathsfromtortoisehg.hgqt.i18nimport_fromhgext.colorimport_stylesfromPyQt4.QtCoreimport*fromPyQt4.QtGuiimport*ifPYQT_VERSION_STR.split('.')<['4','7']or \
QT_VERSION_STR.split('.')<['4','6']:sys.stderr.write('TortoiseHg requires Qt 4.6 and PyQt 4.7\n')sys.stderr.write('You have Qt %s and PyQt %s\n'%(QT_VERSION_STR,PYQT_VERSION_STR))sys.exit()try:importwin32conopenflags=win32con.CREATE_NO_WINDOWexceptImportError:openflags=0tmproot=Nonedefgettempdir():globaltmprootdefcleanup():defwriteable(arg,dirname,names):fornameinnames:fullname=os.path.join(dirname,name)os.chmod(fullname,os.stat(fullname).st_mode|stat.S_IWUSR)try:os.path.walk(tmproot,writeable,None)shutil.rmtree(tmproot)except:passifnottmproot:tmproot=tempfile.mkdtemp(prefix='thg.')atexit.register(cleanup)returntmprootdefopenhelpcontents(url):'Open online help, use local CHM file if available'ifnoturl.startswith('http'):fullurl='http://tortoisehg.org/manual/2.1/'+url# Use local CHM file if it can be foundifos.name=='nt'andpaths.bin_path:chm=os.path.join(paths.bin_path,'doc','TortoiseHg.chm')ifos.path.exists(chm):fullurl=(r'mk:@MSITStore:%s::/'%chm)+urlQDesktopServices.openUrl(QUrl.fromLocalFile(fullurl))returnQDesktopServices.openUrl(QUrl(fullurl))defeditfiles(repo,files,lineno=None,search=None,parent=None):iflen(files)==1:path=repo.wjoin(files[0])cwd=os.path.dirname(path)files=[os.path.basename(path)]else:cwd=repo.rootfiles=[util.shellquote(util.localpath(f))forfinfiles]editor=repo.ui.config('tortoisehg','editor')assertlen(files)==1orlineno==Noneifeditor:try:regexp=re.compile('\[([^\]]*)\]')expanded=[]pos=0forminregexp.finditer(editor):expanded.append(editor[pos:m.start()-1])phrase=editor[m.start()+1:m.end()-1]pos=m.end()+1if'$LINENUM'inphrase:iflinenoisNone:# throw away phrasecontinuephrase=phrase.replace('$LINENUM',str(lineno))elif'$SEARCH'inphrase:ifsearchisNone:# throw away phrasecontinuephrase=phrase.replace('$SEARCH',search)if'$FILE'inphrase:phrase=phrase.replace('$FILE',files[0])files=[]expanded.append(phrase)expanded.append(editor[pos:])cmdline=' '.join(expanded+files)exceptValueError,e:# '[' or ']' not foundcmdline=' '.join([editor]+files)exceptTypeError,e:# variable expansion failedcmdline=' '.join([editor]+files)else:editor=os.environ.get('HGEDITOR')orrepo.ui.config('ui','editor') \
oros.environ.get('EDITOR','vi')cmdline=' '.join([editor]+files)ifos.path.basename(editor)in('vi','vim','hgeditor'):res=QMessageBox.critical(parent,_('No visual editor configured'),_('Please configure a visual editor.'))fromtortoisehg.hgqt.settingsimportSettingsDialogdlg=SettingsDialog(False,focus='tortoisehg.editor')dlg.exec_()returncmdline=util.quotecommand(cmdline)try:subprocess.Popen(cmdline,shell=True,creationflags=openflags,stderr=None,stdout=None,stdin=None,cwd=cwd)except(OSError,EnvironmentError),e:QMessageBox.warning(parent,_('Editor launch failure'),u'%s : %s'%(hglib.tounicode(cmdline),hglib.tounicode(str(e))))returnFalse_user_shell=Nonedefopenshell(root,reponame):ifnotos.path.exists(root):WarningMsgBox(_('Failed to open path in terminal'),_('"%s" is not a valid directory')%hglib.tounicode(root))returnif_user_shell:cwd=os.getcwd()try:shellcmd=_user_shell%{'reponame':reponame}os.chdir(root)QProcess.startDetached(shellcmd)finally:os.chdir(cwd)else:InfoMsgBox(_('No shell configured'),_('A terminal shell must be configured'))defconfigureshell(ui):global_user_shell_user_shell=ui.config('tortoisehg','shell')if_user_shell:returnifsys.platform=='darwin':return# Terminal.App does not support open-to-folderelifos.name=='nt':_user_shell='cmd.exe /K title %(reponame)s'else:_user_shell='xterm -T "%(reponame)s"'# _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; }'tbstylesheet='QToolBar { border: 0px }'defconfigstyles(ui):configureshell(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 />')returnu'<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+&@#/%?=~_()|!:,.;]*' \
r'[-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]ifsys.platform=='darwin':# On Mac OS X, we do not want icons on menusdefgetmenuicon(name):returnQIcon()else:getmenuicon=geticondefgetoverlaidicon(base,overlay):"""Generate an overlaid icon"""pixmap=base.pixmap(16,16)painter=QPainter(pixmap)painter.setCompositionMode(QPainter.CompositionMode_SourceOver)painter.drawPixmap(0,0,overlay.pixmap(16,16))delpainterreturnQIcon(pixmap)_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'}ifsys.platform=='darwin':_fontdefaults['fontoutputlog']='sans,10'_fontcache={}definitfontcache(ui):fornamein_fontdefaults:fname=ui.config('tortoisehg',name,_fontdefaults[name])_fontcache[name]=ThgFont(fname)defgetfont(name):assertnamein_fontdefaultsreturn_fontcache[name]defgettranslationpath():"""Return path to Qt's translation file (.qm)"""ifgetattr(sys,'frozen',False):return':/translations'else:returnQLibraryInfo.location(QLibraryInfo.TranslationsPath)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] = btn
- if default == i:- self.setDefaultButton(btn)- if esc == i:- self.setEscapeButton(btn) except (ValueError, IndexError):
pass
+ if default == i:+ self.setDefaultButton(btn)+ if esc == i:+ self.setEscapeButton(btn) def run(self):
return self.exec_()
defkeyPressEvent(self,event):fork,btninself.hotkeys.iteritems():ifevent.text()==k:btn.clicked.emit(False)super(CustomPrompt,self).keyPressEvent(event)defsetup_font_substitutions():QFont.insertSubstitutions('monospace',['monaco','courier new'])deffix_application_font():ifos.name!='nt':returntry:importwin32gui,win32conexceptImportError:return# use configurable font like GTK, Mozilla XUL or Eclipse SWTncm=win32gui.SystemParametersInfo(win32con.SPI_GETNONCLIENTMETRICS)lf=ncm['lfMessageFont']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,'QWidget')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('expander-open')self.minus=geticon('expander-close')icon=expandedandself.minusorself.plusself.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)classInfoBar(QFrame):"""Non-modal confirmation/alert (like web flash or Chrome's InfoBar) You shouldn't reuse InfoBar object after close(). It is automatically deleted. Layout:: |widgets ... |right widgets ...|x| """linkActivated=pyqtSignal(unicode)# type of InfoBar (the number denotes its priority)INFO=1ERROR=2CONFIRM=3infobartype=INFO_colormap={INFO:'#e7f9e0',ERROR:'#f9d8d8',CONFIRM:'#fae9b3',}def__init__(self,parent=None):super(InfoBar,self).__init__(parent,frameShape=QFrame.StyledPanel,frameShadow=QFrame.Plain)self.setAttribute(Qt.WA_DeleteOnClose)self.setAutoFillBackground(True)p=self.palette()p.setColor(QPalette.Window,QColor(self._colormap[self.infobartype]))p.setColor(QPalette.WindowText,QColor("black"))self.setPalette(p)self.setLayout(QHBoxLayout())self.layout().setContentsMargins(2,2,2,2)self.layout().addStretch()self._closebutton=QPushButton(self,flat=True,autoDefault=False,icon=self.style().standardIcon(QStyle.SP_DockWidgetCloseButton))self._closebutton.clicked.connect(self.close)self.layout().addWidget(self._closebutton)defaddWidget(self,w,stretch=0):self.layout().insertWidget(self.layout().count()-2,w,stretch)defaddRightWidget(self,w):self.layout().insertWidget(self.layout().count()-1,w)defkeyPressEvent(self,event):ifevent.key()==Qt.Key_Escape:self.close()super(InfoBar,self).keyPressEvent(event)classStatusInfoBar(InfoBar):"""Show status message"""def__init__(self,message,parent=None):super(StatusInfoBar,self).__init__(parent)self._msglabel=QLabel(message,self,wordWrap=True,textInteractionFlags=Qt.TextSelectableByMouse)self.addWidget(self._msglabel,stretch=1)classCommandErrorInfoBar(InfoBar):"""Show command execution failure (with link to open log window)"""infobartype=InfoBar.ERRORdef__init__(self,message,parent=None):super(CommandErrorInfoBar,self).__init__(parent)self._msglabel=QLabel(message,self,wordWrap=True,textInteractionFlags=Qt.TextSelectableByMouse)self.addWidget(self._msglabel,stretch=1)self._loglabel=QLabel('<a href="log:">%s</a>'%_('Show Log'))self._loglabel.linkActivated.connect(self.linkActivated)self.addRightWidget(self._loglabel)classConfirmInfoBar(InfoBar):"""Show confirmation message with accept/reject buttons"""accepted=pyqtSignal()rejected=pyqtSignal()infobartype=InfoBar.CONFIRMdef__init__(self,message,parent=None):super(ConfirmInfoBar,self).__init__(parent)# no wordWrap=True and stretch=1, which inserts unwanted space# between _msglabel and _buttons.self._msglabel=QLabel(message,self,textInteractionFlags=Qt.TextSelectableByMouse)self.addWidget(self._msglabel)self._buttons=QDialogButtonBox(self)self._buttons.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)self.acceptButton=self._buttons.addButton(QDialogButtonBox.Ok)self.rejectButton=self._buttons.addButton(QDialogButtonBox.Cancel)self._buttons.accepted.connect(self._accept)self._buttons.rejected.connect(self._reject)self.addWidget(self._buttons)# so that acceptButton gets focus by defaultself.setFocusProxy(self._buttons)defcloseEvent(self,event):ifself.isVisible():self.rejected.emit()super(ConfirmInfoBar,self).closeEvent(event)@pyqtSlot()def_accept(self):self.accepted.emit()self.hide()self.close()@pyqtSlot()def_reject(self):self.rejected.emit()self.hide()self.close()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)classTaskWidget(object):defcanswitch(self):"""Return True if the widget allows to switch away from it"""returnTruedefcanExit(self):returnTrueclassDemandWidget(QWidget):'Create a widget the first time it is shown'def__init__(self,createfuncname,createinst,parent=None):super(DemandWidget,self).__init__(parent)# We store a reference to the create function name to avoid having a# hard reference to the bound function, which prevents it being# disposed. Weak references to bound functions don't work.self._createfuncname=createfuncnameself._createinst=weakref.ref(createinst)self._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,**opts)returnNonedefget(self):"""Returns the stored widget"""ifself._widgetisNone:func=getattr(self._createinst(),self._createfuncname,None)self._widget=func()self.layout().addWidget(self._widget)returnself._widgetdefcanswitch(self):"""Return True if the widget allows to switch away from it"""ifself._widgetisNone:returnTruereturnself._widget.canswitch()defcanExit(self):ifself._widgetisNone:returnTruereturnself._widget.canExit()def__getattr__(self,name):returngetattr(self._widget,name)classSpacer(QWidget):"""Spacer to separate controls in a toolbar"""def__init__(self,width,height,parent=None):QWidget.__init__(self,parent)self.width=widthself.height=heightdefsizeHint(self):returnQSize(self.width,self.height)defgetCurrentUsername(widget,repo,opts=None):ifopts:# 1. Override has highest priorityuser=opts.get('user')ifuser:returnuser# 2. Read from repositorytry:returnrepo.ui.username()excepterror.Abort:pass# 3. Get a username from the userQMessageBox.information(widget,_('Please enter a username'),_('You must identify yourself to Mercurial'),QMessageBox.Ok)fromtortoisehg.hgqt.settingsimportSettingsDialogdlg=SettingsDialog(False,focus='ui.username')dlg.exec_()repo.invalidateui()try:returnrepo.ui.username()excepterror.Abort:returnNone
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.