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.
stable
gtklib: make both dlg wrappers return False on cancel
If user cancels file saving, native dialog returns False, while Windows dialog returns None. This changeset changes Windows dialog to return False, and tests the dlg return value before attempting to confirm a file overwrite.
# gtklib.py - miscellaneous PyGTK classes and functions for TortoiseHg## Copyright 2008 TK Soh <teekaysoh@gmail.com># Copyright 2009 Steve Borho <steve@borho.org>## This software may be used and distributed according to the terms of the# GNU General Public License version 2, incorporated herein by reference.importosimportsysimportgtkimportgobjectimportpangoimportQueuefromtortoisehg.util.i18nimport_fromtortoisehg.utilimportpaths,hglib,thread2fromtortoisehg.hgtkimporthgtk,gdialogifgtk.gtk_version<(2,14,0):# at least on 2.12.12, gtk widgets can be confused by control# char markups (like ""), so use cgi.escape insteadfromcgiimportescapeasmarkup_escape_textelse:fromgobjectimportmarkup_escape_textifgobject.pygobject_version<=(2,12,1):# http://www.mail-archive.com/tortoisehg-develop@lists.sourceforge.net/msg06900.htmlraiseException('incompatible version of gobject')defset_tortoise_icon(window,thgicon):ico=paths.get_tortoise_icon(thgicon)ifico:window.set_icon_from_file(ico)defget_thg_modifier():ifsys.platform=='darwin':return'<Mod1>'else:return'<Control>'defset_tortoise_keys(window):'Set default TortoiseHg keyboard accelerators'ifsys.platform=='darwin':mask=gtk.accelerator_get_default_mod_mask()mask|=gtk.gdk.MOD1_MASK;gtk.accelerator_set_default_mod_mask(mask)mod=get_thg_modifier()accelgroup=gtk.AccelGroup()window.add_accel_group(accelgroup)key,modifier=gtk.accelerator_parse(mod+'w')window.add_accelerator('thg-close',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)key,modifier=gtk.accelerator_parse(mod+'q')window.add_accelerator('thg-exit',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)key,modifier=gtk.accelerator_parse('F5')window.add_accelerator('thg-refresh',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)key,modifier=gtk.accelerator_parse(mod+'r')window.add_accelerator('thg-refresh',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)key,modifier=gtk.accelerator_parse(mod+'Return')window.add_accelerator('thg-accept',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)# connect ctrl-w and ctrl-q to every windowwindow.connect('thg-close',thgclose)window.connect('thg-exit',thgexit)defthgexit(window):ifthgclose(window):gobject.idle_add(hgtk.thgexit,window)defthgclose(window):ifhasattr(window,'should_live'):ifwindow.should_live():returnFalsewindow.destroy()returnTrueclassMessageDialog(gtk.Dialog):button_map={gtk.BUTTONS_NONE:None,gtk.BUTTONS_OK:(gtk.STOCK_OK,gtk.RESPONSE_OK),gtk.BUTTONS_CLOSE:(gtk.STOCK_CLOSE,gtk.RESPONSE_CLOSE),gtk.BUTTONS_CANCEL:(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL),gtk.BUTTONS_YES_NO:(gtk.STOCK_YES,gtk.RESPONSE_YES,gtk.STOCK_NO,gtk.RESPONSE_NO),gtk.BUTTONS_OK_CANCEL:(gtk.STOCK_OK,gtk.RESPONSE_OK,gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL),}image_map={gtk.MESSAGE_INFO:gtk.STOCK_DIALOG_INFO,gtk.MESSAGE_WARNING:gtk.STOCK_DIALOG_WARNING,gtk.MESSAGE_QUESTION:gtk.STOCK_DIALOG_QUESTION,gtk.MESSAGE_ERROR:gtk.STOCK_DIALOG_ERROR,}def__init__(self,parent=None,flags=0,type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_NONE,message_format=None):gtk.Dialog.__init__(self,parent=parent,flags=flags|gtk.DIALOG_NO_SEPARATOR,buttons=MessageDialog.button_map[buttons])self.set_resizable(False)hbox=gtk.HBox()self._image_frame=gtk.Frame()self._image_frame.set_shadow_type(gtk.SHADOW_NONE)self._image=gtk.Image()self._image.set_from_stock(MessageDialog.image_map[type],gtk.ICON_SIZE_DIALOG)self._image_frame.add(self._image)hbox.pack_start(self._image_frame,padding=5)lblbox=gtk.VBox(spacing=10)self._primary=gtk.Label("")self._primary.set_alignment(0.0,0.5)self._primary.set_line_wrap(True)lblbox.pack_start(self._primary)self._secondary=gtk.Label()lblbox.pack_end(self._secondary)self._secondary.set_line_wrap(True)hbox.pack_start(lblbox,padding=5)self.vbox.pack_start(hbox,False,False,10)self.show_all()defset_markup(self,s):self._primary.set_markup(s)defformat_secondary_markup(self,message_format):self._secondary.set_markup(message_format)defformat_secondary_text(self,message_format):self._secondary.set_text(message_format)defset_image(self,image):self._image_frame.remove(self._image)self._image=imageself._image_frame.add(self._image)self._image.show()classNativeSaveFileDialogWrapper:"""Wrap the windows file dialog, or display default gtk dialog if that isn't available"""def__init__(self,initial=None,title=_('Save File'),filter=((_('All files'),'*.*'),),filterindex=1,filename='',open=False):ifinitialisNone:initial=os.path.expanduser("~")self.initial=initialself.filename=filenameself.title=titleself.filter=filterself.filterindex=filterindexself.open=opendefrun(self):"""run the file dialog, either return a file name, or False if the user aborted the dialog"""try:importwin32gui,win32con,pywintypesfilepath=self.runWindows()exceptImportError:filepath=self.runCompatible()iffilepath:returnself.overwriteConfirmation(filepath)else:returnFalsedefrunWindows(self):defrundlg(q):importwin32gui,win32con,pywintypescwd=os.getcwd()fname=Nonetry:f=''forname,maskinself.filter:f+='\0'.join([name,mask,''])opts=dict(InitialDir=self.initial,Flags=win32con.OFN_EXPLORER,File=self.filename,DefExt=None,Title=hglib.fromutf(self.title),Filter=hglib.fromutf(f),CustomFilter=None,FilterIndex=self.filterindex)ifself.open:fname,_,_=win32gui.GetOpenFileNameW(**opts)else:fname,_,_=win32gui.GetSaveFileNameW(**opts)iffname:fname=os.path.abspath(fname)exceptpywintypes.error:passos.chdir(cwd)q.put(fname)q=Queue.Queue()thread=thread2.Thread(target=rundlg,args=(q,))thread.start() while thread.isAlive():
# let gtk process events while we wait for rundlg finishing
gtk.main_iteration(block=True)
- fname = None+ fname = False if q.qsize():
fname = q.get(0)
return fname
defrunCompatible(self):ifself.open:action=gtk.FILE_CHOOSER_ACTION_OPENbuttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)else:action=gtk.FILE_CHOOSER_ACTION_SAVEbuttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)dlg=gtk.FileChooserDialog(self.title,None,action,buttons)dlg.set_default_response(gtk.RESPONSE_OK)dlg.set_current_folder(self.initial)ifnotself.open:dlg.set_current_name(self.filename)forname,patterninself.filter:fi=gtk.FileFilter()fi.set_name(name)fi.add_pattern(pattern)dlg.add_filter(fi)ifdlg.run()==gtk.RESPONSE_OK:result=dlg.get_filename();else:result=Falsedlg.destroy()returnresultdefoverwriteConfirmation(self,filepath):result=filepathifos.path.exists(filepath):res=gdialog.Confirm(_('Confirm Overwrite'),[],None,_('The file "%s" already exists!\n\n''Do you want to overwrite it?')%filepath).run()ifres==gtk.RESPONSE_YES:os.remove(filepath)else:result=FalsereturnresultclassNativeFolderSelectDialog:"""Wrap the windows folder dialog, or display default gtk dialog if that isn't available"""def__init__(self,initial=None,title=_('Select Folder')):self.initial=initialoros.getcwd()self.title=titledefrun(self):"""run the file dialog, either return a file name, or False if the user aborted the dialog"""try:importwin32com,win32gui,pywintypesreturnself.runWindows()exceptImportError,e:returnself.runCompatible()defrunWindows(self):defrundlg(q):fromwin32com.shellimportshell,shellconimportwin32gui,pywintypesdefBrowseCallbackProc(hwnd,msg,lp,data):ifmsg==shellcon.BFFM_INITIALIZED:win32gui.SendMessage(hwnd,shellcon.BFFM_SETSELECTION,1,data)elifmsg==shellcon.BFFM_SELCHANGED:# Set the status text of the# For this message, 'lp' is the address of the PIDL.pidl=shell.AddressAsPIDL(lp)try:path=shell.SHGetPathFromIDList(pidl)win32gui.SendMessage(hwnd,shellcon.BFFM_SETSTATUSTEXT,0,path)exceptshell.error:# No path for this PIDLpassfname=Nonetry:flags=shellcon.BIF_EDITBOX|0x40#shellcon.BIF_NEWDIALOGSTYLEpidl,_,_=shell.SHBrowseForFolder(0,None,hglib.fromutf(self.title),flags,BrowseCallbackProc,# callback functionself.initial)# 'data' param for the callbackifpidl:fname=hglib.toutf(shell.SHGetPathFromIDList(pidl))except(pywintypes.error,pywintypes.com_error):passq.put(fname)q=Queue.Queue()thread=thread2.Thread(target=rundlg,args=(q,))thread.start()whilethread.isAlive():# let gtk process events while we wait for rundlg finishinggtk.main_iteration(block=True)fname=Noneifq.qsize():fname=q.get(0)returnfnamedefrunCompatible(self):dialog=gtk.FileChooserDialog(title=None,action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))dialog.set_default_response(gtk.RESPONSE_OK)response=dialog.run()fname=dialog.get_filename()dialog.destroy()ifresponse==gtk.RESPONSE_OK:returnfnamereturnNoneclassNativeFileManager:""" Wrapper for opening the specific file manager; Explorer on Windows, Nautilus File Manager on Linux. """def__init__(self,path):self.path=pathdefrun(self):try:importpywintypesself.runExplorer()exceptImportError:self.runNautilus()defrunExplorer(self):importsubprocesssubprocess.Popen('explorer "%s"'%self.path)defrunNautilus(self):# TODO implement me!passdefmarkup(text,**kargs):""" A wrapper function for Pango Markup Language. All options must be passed as keywork arguments. """iflen(kargs)==0:returnmarkup_escape_text(str(text))attr=''forname,valueinkargs.items():attr+=' %s="%s"'%(name,value)text=markup_escape_text(text)return'<span%s>%s</span>'%(attr,text)classLayoutGroup(object):def__init__(self,width=0):self.width=widthself.tables=[]defadd(self,*tables,**kargs):self.tables.extend(tables)ifkargs.get('adjust',True):self.adjust(**kargs)defadjust(self,force=False):defrealized():'''check all tables realized or not'''fortableinself.tables:iftuple(table.allocation)==(-1,-1,1,1):returnFalsereturnTruedeftrylater():'''retry when occurred "size-allocate" signal'''adjusted=[False]defallocated(table,rect,hid):table.disconnect(hid[0])ifnotadjusted[0]andrealized():adjusted[0]=Trueself.adjust()fortableinself.tables:hid=[None]hid[0]=table.connect('size-allocate',allocated,hid)# check all realizedifnotforceandnotrealized():trylater()return# find out max widthmax=self.widthfortableinself.tables:first=table.get_first_header()w=first.allocation.widthmax=w>maxandwormax# apply widthfortableinself.tables:first=table.get_first_header()first.set_size_request(max,-1)first.size_request()classLayoutTable(gtk.VBox):""" Provide 2 columns layout table. This table has 2 columns; first column is used for header, second is used for body. In default, the header will be aligned right and the body will be aligned left with expanded padding. """def__init__(self,**kargs):gtk.VBox.__init__(self)self.table=gtk.Table(1,2)self.pack_start(self.table)self.headers=[]self.set_default_paddings(kargs.get('xpad',-1),kargs.get('ypad',-1))self.set_default_options(kargs.get('headopts',None),kargs.get('bodyopts',None))defset_default_paddings(self,xpad=None,ypad=None):""" Set default paddings between cells. LayoutTable has xpad=4, ypad=2 as preset padding values. xpad: Number. Pixcel value of padding for x-axis. Use -1 to reset padding to preset value. Default: None (no change). ypad: Number. Pixcel value of padding for y-axis. Use -1 to reset padding to preset value. Default: None (no change). """ifxpadisnotNone:self.xpad=xpad>=0andxpador4ifypadisnotNone:self.ypad=ypad>=0andypador2defset_default_options(self,headopts=None,bodyopts=None):""" Set default options for markups of label. In default, LayoutTable doesn't use any markups and set the test as plane text. See markup()'s description for more details of option parameters. Note that if called add_row() with just one widget, it will be tried to apply 'bodyopts', not 'headopts'. headopts: Dictionary. Options used for markups of gtk.Label. This option is only availabled for the label. The text will be escaped automatically. Default: None. bodyopts: [same as 'headopts'] """self.headopts=headoptsself.bodyopts=bodyoptsdefget_first_header(self):""" Return the cell at top-left corner if exists. """iflen(self.headers)>0:returnself.headers[0]returnNonedefclear_rows(self):forchildinself.table.get_children():self.table.remove(child)defadd_row(self,*widgets,**kargs):""" Append a new row to the table. widgets: mixed list of widget, string, number or None; i.e. ['host:', gtk.Entry(), 20, 'port:', gtk.Entry()] First item will be header, and the rest will be body after packed into a gtk.HBox. widget: Standard GTK+ widget. string: Label text, will be converted gtk.Label. number: Fixed width padding. None: Flexible padding. kargs: 'padding', 'expand', 'xpad' and 'ypad' are availabled. padding: Boolean. If False, the padding won't append the end of body. Default: True. expand: Number. Position of body element to expand. If you specify this option, 'padding' option will be changed to False automatically. Default: -1 (last element). xpad: Number. Override default 'xpad' value. ypad: [same as 'xpad'] headopts: Dictionary. Override default 'headopts' value. bodyopts: [same as 'headopts'] """iflen(widgets)==0:returnt=self.tableFLAG=gtk.FILL|gtk.EXPANDrows=t.get_property('n-rows')t.set_property('n-rows',rows+1)xpad=kargs.get('xpad',self.xpad)ypad=kargs.get('ypad',self.ypad)hopts=kargs.get('headopts',self.headopts)bopts=kargs.get('bodyopts',self.bodyopts)defgetwidget(obj,opts=None):'''element converter'''ifobj==None:returngtk.Label('')elifisinstance(obj,(int,long)):lbl=gtk.Label('')lbl.set_size_request(obj,-1)lbl.size_request()returnlblelifisinstance(obj,basestring):ifoptsisNone:lbl=gtk.Label(obj)else:obj=markup(obj,**opts)lbl=gtk.Label()lbl.set_markup(obj)returnlblreturnobjdefpack(*widgets,**kargs):'''pack some of widgets and return HBox'''expand=kargs.get('expand',-1)iflen(widgets)<=expand:expand=-1padding=kargs.get('padding',expand==-1)ifpaddingisTrue:widgets+=(None,)expmap=[wisNoneforwinwidgets]expmap[expand]=Truewidgets=[getwidget(w,bopts)forwinwidgets]hbox=gtk.HBox()fori,objinenumerate(widgets):widget=getwidget(obj,bopts)pad=i!=0and2or0hbox.pack_start(widget,expmap[i],expmap[i],pad)returnhboxiflen(widgets)==1:cols=t.get_property('n-columns')widget=pack(*widgets,**kargs)t.attach(widget,0,cols,rows,rows+1,FLAG,0,xpad,ypad)else:first=getwidget(widgets[0],hopts)ifisinstance(first,gtk.Label):first.set_alignment(1,0.5)t.attach(first,0,1,rows,rows+1,gtk.FILL,0,xpad,ypad)self.headers.append(first)rest=pack(*(widgets[1:]),**kargs)t.attach(rest,1,2,rows,rows+1,FLAG,0,xpad,ypad)classSlimToolbar(gtk.HBox):""" Slim Toolbar, allows to add the buttons of menu size. """def__init__(self,tooltips=None):gtk.HBox.__init__(self)self.tooltips=tooltipsdefappend_stock(self,stock_id,tooltip=None,toggle=False):icon=gtk.image_new_from_stock(stock_id,gtk.ICON_SIZE_MENU)iftoggle:button=gtk.ToggleButton()else:button=gtk.Button()button.set_image(icon)button.set_relief(gtk.RELIEF_NONE)button.set_focus_on_click(False)ifself.tooltipsandtooltip:self.tooltips.set_tip(button,tooltip)self.append_widget(button,padding=0)returnbuttondefappend_widget(self,widget,expand=False,padding=2):self.pack_start(widget,expand,expand,padding)defappend_space(self):self.append_widget(gtk.Label(),expand=True,padding=0)classMenuItems(object):'''controls creation of menus by ignoring separators at odd places'''def__init__(self):self.reset()defreset(self):self.childs=[]self.sep=Nonedefappend(self,child):'''appends the child menu item, but ignores odd separators'''ifisinstance(child,gtk.SeparatorMenuItem):iflen(self.childs)>0:self.sep=childelse:ifself.sep:self.childs.append(self.sep)self.sep=Noneself.childs.append(child)defappend_sep(self):self.append(gtk.SeparatorMenuItem())defcreate_menu(self):'''creates the menu by ignoring any extra separator'''res=gtk.Menu()forcinself.childs:res.append(c)self.reset()returnresdefaddspellcheck(textview,ui=None):lang=Noneifui:lang=ui.config('tortoisehg','spellcheck',None)try:importgtkspellgtkspell.Spell(textview,lang)exceptImportError:passexceptException,e:printeelse:defselectlang(senderitem):fromtortoisehg.hgtkimportdialogspell=gtkspell.get_from_text_view(textview)lang=''whileTrue:msg=_('Select language for spell checking.\n\n''Empty is for the default language.\n''When all text is highlited, the dictionary\n''is probably not installed.\n\n''examples: en, en_GB, en_US')iflang:msg=_('Lang "%s" can not be set.\n')%lang+msglang=dialog.entry_dialog(None,msg)iflangisNone:# cancelreturnlang=lang.strip()ifnotlang:lang=None# set default language from $LANGtry:spell.set_language(lang)returnexceptException,e:passdeflangmenu(textview,menu):item=gtk.MenuItem(_('Spell Check Language'))item.connect('activate',selectlang)menuitems=menu.get_children()[:2]x=menuitems[0].get_submenu()iflen(menuitems)>=2andmenuitems[1].get_child()isNoneandmenuitems[0].get_submenu():# the spellcheck language menu seems to be at the topmenu.insert(item,1)else:sep=gtk.SeparatorMenuItem()sep.show()menu.append(sep)menu.append(item)item.show()textview.connect('populate-popup',langmenu)defhasspellcheck():try:importgtkspellgtkspell.SpellreturnTrueexceptImportError:returnFalsedefidle_add_single_call(f,*args):'''wrap function f for gobject.idle_add, so that f is guaranteed to be called only once, independent of its return value'''classsingle_call(object):def__init__(self,f,args):self.f=fself.args=argsdef__call__(self):self.f(*args)# ignore return value of freturnFalse# return False to signal: don't call me again# functions passed to gobject.idle_add must return False, or they# will be called repeatedly. The single_call object wraps f and always# returns False when called. So the return value of f doesn't matter,# it can even return True (which would lead to gobject.idle_add# calling the function again, if used without single_call).gobject.idle_add(single_call(f,args))
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.