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.
# thgmq.py - embeddable widget for MQ extension## Copyright 2009 Yuki KODAMA <endflow.net@gmail.com>## This software may be used and distributed according to the terms of the# GNU General Public License version 2, incorporated herein by reference.importosimportgtkimportgtk.keysymsimportgobjectimportpangofrommercurialimporterrorfromtortoisehg.util.i18nimport_fromtortoisehg.utilimporthglibfromtortoisehg.hgtkimportgdialog,gtklib,hgcmd# MQ patches row enumerationsMQ_INDEX=0MQ_STATUS=1MQ_NAME=2MQ_SUMMARY=3MQ_ESCAPED=4# Special patch indicesINDEX_SEPARATOR=-1INDEX_QPARENT=-2# Move patch operationsMOVE_TOP=1MOVE_UP=2MOVE_DOWN=3MOVE_BOTTOM=4# DnD target constansMQ_DND_URI_LIST=1024classMQWidget(gtk.VBox):__gproperties__={'index-column-visible':(gobject.TYPE_BOOLEAN,'Index','Show index column',False,gobject.PARAM_READWRITE),'status-column-visible':(gobject.TYPE_BOOLEAN,'Status','Show status column',False,gobject.PARAM_READWRITE),'name-column-visible':(gobject.TYPE_BOOLEAN,'Name','Show name column',False,gobject.PARAM_READWRITE),'summary-column-visible':(gobject.TYPE_BOOLEAN,'Summary','Show summary column',False,gobject.PARAM_READWRITE),'editable-cell':(gobject.TYPE_BOOLEAN,'EditableCell','Enable editable cells',False,gobject.PARAM_READWRITE),'show-qparent':(gobject.TYPE_BOOLEAN,'ShowQParent',"Show 'qparent'",False,gobject.PARAM_READWRITE)}__gsignals__={'repo-invalidated':(gobject.SIGNAL_RUN_FIRST,gobject.TYPE_NONE,()),'patch-selected':(gobject.SIGNAL_RUN_FIRST,gobject.TYPE_NONE,(int,# revision numberstr)),# patch name'files-dropped':(gobject.SIGNAL_RUN_FIRST,gobject.TYPE_NONE,(object,# list of dropped files/dirsstr))# raw string data}def__init__(self,repo,accelgroup=None,tooltips=None):gtk.VBox.__init__(self)self.repo=repoself.mqloaded=hasattr(repo,'mq')# top toolbartbar=gtklib.SlimToolbar(tooltips)## buttonsself.btn={}popallbtn=tbar.append_button(gtk.STOCK_GOTO_TOP,_('Unapply all patches'))popallbtn.connect('clicked',self.popall_clicked)self.btn['popall']=popallbtnpopbtn=tbar.append_button(gtk.STOCK_GO_UP,_('Unapply last patch'))popbtn.connect('clicked',self.pop_clicked)self.btn['pop']=popbtnpushbtn=tbar.append_button(gtk.STOCK_GO_DOWN,_('Apply next patch'))pushbtn.connect('clicked',self.push_clicked)self.btn['push']=pushbtnpushallbtn=tbar.append_button(gtk.STOCK_GOTO_BOTTOM,_('Apply all patches'))pushallbtn.connect('clicked',self.pushall_clicked)self.btn['pushall']=pushallbtn## separatortbar.append_space()## drop-down menumenubtn=gtk.MenuToolButton('')menubtn.set_menu(self.create_view_menu())tbar.append_widget(menubtn,padding=0)self.btn['menu']=menubtndefafter_init():menubtn.child.get_children()[0].hide()gtklib.idle_add_single_call(after_init)self.pack_start(tbar,False,False)# center panemainbox=gtk.VBox()self.pack_start(mainbox,True,True)## scrolled panepane=gtk.ScrolledWindow()pane.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)pane.set_shadow_type(gtk.SHADOW_IN)mainbox.pack_start(pane)### patch listself.model=gtk.ListStore(int,# patch indexstr,# patch statusstr,# patch namestr,# summary (utf-8)str)# escaped summary (utf-8)self.list=gtk.TreeView(self.model)self.list.set_row_separator_func(self.row_sep_func)# To support old PyGTK (<2.12)ifhasattr(self.list,'set_tooltip_column'):self.list.set_tooltip_column(MQ_ESCAPED)self.list.connect('cursor-changed',self.list_sel_changed)self.list.connect('button-press-event',self.list_pressed)self.list.connect('button-release-event',self.list_released)self.list.connect('row-activated',self.list_row_activated)self.list.connect('size-allocate',self.list_size_allocated)### dnd setup for patch listtargets=[('text/uri-list',0,MQ_DND_URI_LIST)]self.list.drag_dest_set(gtk.DEST_DEFAULT_MOTION| \
gtk.DEST_DEFAULT_DROP,targets,gtk.gdk.ACTION_MOVE)self.list.connect('drag-data-received',self.dnd_received)self.cols={}self.cells={}defaddcol(header,col_idx,right=False,resizable=False,editable=False,editfunc=None):header=(rightand'%s 'or' %s')%headercell=gtk.CellRendererText()ifeditfunc:cell.set_property('editable',editable)cell.connect('edited',editfunc)col=gtk.TreeViewColumn(header,cell)col.add_attribute(cell,'text',col_idx)col.set_cell_data_func(cell,self.cell_data_func)col.set_resizable(resizable)col.set_visible(self.get_property(self.col_to_prop(col_idx)))ifright:col.set_alignment(1)cell.set_property('xalign',1)self.list.append_column(col)self.cols[col_idx]=colself.cells[col_idx]=celldefcell_edited(cell,path,newname):row=self.model[path]ifrow[MQ_INDEX]<0:returnpatchname=row[MQ_NAME]ifnewname!=patchname:self.qrename(newname,patch=patchname)addcol(_('#'),MQ_INDEX,right=True)addcol(_('st'),MQ_STATUS)addcol(_('Patch'),MQ_NAME,editfunc=cell_edited)addcol(_('Summary'),MQ_SUMMARY,resizable=True)pane.add(self.list)## command widgetself.cmd=hgcmd.CmdWidget(style=hgcmd.STYLE_COMPACT,tooltips=tooltips)mainbox.pack_start(self.cmd,False,False)# acceleratorsifaccelgroup:key,mod=gtk.accelerator_parse('F2')self.list.add_accelerator('thg-rename',accelgroup,key,mod,gtk.ACCEL_VISIBLE)defthgrename(list):sel=self.list.get_selection()ifsel.count_selected_rows()==1:model,paths=sel.get_selected_rows()self.qrename_ui(model[paths[0]][MQ_NAME])self.list.connect('thg-rename',thgrename)mod=gtk.gdk.CONTROL_MASKdefadd(name,key,func,*args):self.list.add_accelerator(name,accelgroup,key,mod,0)self.list.connect(name,lambda*a:func(*args))add('mq-move-top',gtk.keysyms.Page_Up,self.qmove_ui,MOVE_TOP)add('mq-move-up',gtk.keysyms.Up,self.qmove_ui,MOVE_UP)add('mq-move-down',gtk.keysyms.Down,self.qmove_ui,MOVE_DOWN)add('mq-move-bottom',gtk.keysyms.Page_Down,self.qmove_ui,MOVE_BOTTOM)add('mq-pop',gtk.keysyms.Left,self.qpop)add('mq-push',gtk.keysyms.Right,self.qpush)### public functions ###defrefresh(self):""" Refresh the list of patches. This operation will try to keep selection state. """ifnotself.mqloaded:return# store selected patch nameselname=Nonemodel,paths=self.list.get_selection().get_selected_rows()iflen(paths)>0:selname=model[paths[0]][MQ_NAME]# clear model dataself.model.clear()# insert 'qparent' rowtop=Noneifself.get_property('show-qparent'):top=self.model.append((INDEX_QPARENT,None,None,None,None))# add patchesfromhgextimportmqq=self.repo.mqq.parse_series()applied=set([p.nameforpinq.applied])forindex,patchnameinenumerate(q.series):stat=patchnameinappliedand'A'or'U'try:msg=hglib.toutf(mq.patchheader(q.join(patchname)).message[0])msg_esc=gtklib.markup_escape_text(msg)exceptIndexError:msg=msg_esc=Noneiter=self.model.append((index,stat,patchname,msg,msg_esc))ifstat=='A':top=iter# insert separatoriftop:row=self.model.insert_after(top,(INDEX_SEPARATOR,None,None,None,None))self.separator_pos=self.model.get_path(row)[0]# restore patch selectionifselname:iter=self.get_iter_by_patchname(selname)ifiter:self.list.get_selection().select_iter(iter)# update UI sensitivesself.update_sensitives()defset_repo(self,repo):self.repo=repodefqgoto(self,patch):""" [MQ] Execute 'qgoto' command. patch: the patch name or an index to specify the patch. """ifnotself.is_operable():returncmdline=['hg','qgoto',patch]self.cmd.execute(cmdline,self.cmd_done)defqpop(self,all=False):""" [MQ] Execute 'qpop' command. all: if True, use '--all' option. (default: False) """ifnotself.is_operable():returncmdline=['hg','qpop']ifall:cmdline.append('--all')self.cmd.execute(cmdline,self.cmd_done)defqpush(self,all=False):""" [MQ] Execute 'qpush' command. all: if True, use '--all' option. (default: False) """ifnotself.is_operable():returncmdline=['hg','qpush']ifall:cmdline.append('--all')self.cmd.execute(cmdline,self.cmd_done)defqdelete(self,patch,keep=False):""" [MQ] Execute 'qdelete' command. patch: the patch name or an index to specify the patch. keep: if True, use '--keep' option. (default: False) """ifnotself.has_patch():returnifnotkeep:ret=gdialog.CustomPrompt(_('Confirm Delete'),_('Do you want to delete?'),None,(_('&Yes'),_('Yes (&keep)'),_('&Cancel')),default=2,esc=2).run()ifret==0:passelifret==1:keep=Trueelse:returncmdline=['hg','qdelete',patch]ifkeep:cmdline.append('--keep')self.cmd.execute(cmdline,self.cmd_done,noemit=True)defqrename(self,name,patch='qtip'):""" [MQ] Execute 'qrename' command. If 'patch' param isn't specified, renaming should be applied 'qtip' (current) patch. name: the new patch name for renaming. patch: the target patch name or index. (default: 'qtip') """ifnotnameornotself.has_patch():returncmdline=['hg','qrename',patch,name]self.cmd.execute(cmdline,self.cmd_done)defqrename_ui(self,patch='qtip'):""" Prepare the user interface for renaming the patch. If 'patch' param isn't specified, renaming should be started 'qtip' (current) patch. Return True if succeed to prepare; otherwise False. patch: the target patch name or index. (default: 'qtip') """ifnotself.mqloadedor \
patch=='qtip'and'qtip'inself.repo.tags():returnFalsetarget=self.repo.mq.lookup(patch)ifnottarget:returnFalsepath=self.get_path_by_patchname(target)ifnotpath:returnFalse# make the cell editablecell=self.cells[MQ_NAME]ifnotcell.get_property('editable'):cell.set_property('editable',True)defcanceled(cell,*arg):cell.disconnect(cancel_id)cell.disconnect(edited_id)cell.set_property('editable',False)cancel_id=cell.connect('editing-canceled',canceled)edited_id=cell.connect('edited',canceled)# start editing patchname cellself.list.set_cursor_on_cell(path,self.cols[MQ_NAME],None,True)returnTruedefqfinish(self,applied=False):""" [MQ] Execute 'qfinish' command. applied: if True, enable '--applied' option. (default: False) """ifnotself.has_applied():returncmdline=['hg','qfinish']ifapplied:cmdline.append('--applied')self.cmd.execute(cmdline,self.cmd_done)defqfold(self,patch):""" [MQ] Execute 'qfold' command. patch: the patch name or an index to specify the patch. """ifnotpatchornotself.has_applied():returndata=dict(target=patch,qtip=self.get_qtip_patchname())ret=gdialog.Confirm(_('Confirm Fold'),[],None,_("Do you want to fold un-applied patch '%(target)s'"" into current patch '%(qtip)s'?")%data).run()ifret!=gtk.RESPONSE_YES:returncmdline=['hg','qfold',patch]self.cmd.execute(cmdline,self.cmd_done)defqmove(self,patch,op):""" [MQ] Move patch. This is NOT standard API of MQ. patch: the patch name or an index to specify the patch. op: the operator for moving the patch: MOVE_TOP, MOVE_UP, MOVE_DOWN or MOVE_BOTTOM. """ifnotself.is_operable()orself.is_applied(patch):returnFalse# get current index in the listoldrow=self.get_row_by_patchname(patch)foriinrange(len(self.model)):ifself.model[i][MQ_NAME]==oldrow[MQ_NAME]:oldidx=ibreakelse:returnFalse# get new index in the listminval=self.separator_pos+1maxval=len(self.model)-1ifop==MOVE_TOP:newidx=minvalelifop==MOVE_UP:newidx=oldidx-1elifop==MOVE_DOWN:newidx=oldidx+1elifop==MOVE_BOTTOM:newidx=maxvalifnewidx==oldidxornewidx<minvalormaxval<newidx:returnFalse# Update seriesq=self.repo.mqoldrow=self.model[oldidx]newrow=self.model[newidx]oldpos=q.find_series(oldrow[MQ_NAME])newpos=q.find_series(newrow[MQ_NAME])olditem=q.full_series[oldpos]delq.full_series[oldpos]q.full_series.insert(newpos,olditem)q.series_dirty=Trueq.save_dirty()# Update TreeViewifnewidx<oldidx:self.model.move_before(oldrow.iter,newrow.iter)else:self.model.move_after(oldrow.iter,newrow.iter)begin=min(oldidx,newidx)offset=min(oldrow[MQ_INDEX],newrow[MQ_INDEX])-beginforiinxrange(begin,max(oldidx,newidx)+1):self.model[i][MQ_INDEX]=i+offsetdefqmove_ui(self,op):""" [MQ] Move selected patch in the list. Return True if succeed to move; otherwise False. op: the operator for moving the patch: MOVE_TOP, MOVE_UP, MOVE_DOWN or MOVE_BOTTOM. """sel=self.list.get_selection()ifsel.count_selected_rows()==1:model,paths=sel.get_selected_rows()patch=model[paths[0]][MQ_NAME]ifpatch:returnself.qmove(patch,op)returnFalsedefhas_mq(self):returnself.mqloadedandos.path.isdir(self.repo.mq.path)defhas_patch(self):""" return True if MQ has applicable patches """returnbool(self.get_num_patches())defhas_applied(self):""" return True if MQ has applied patches """returnbool(self.get_num_applied())defget_num_patches(self):""" return the number of patches in patch queue """ifself.mqloaded:returnlen(self.repo.mq.series)return0defget_num_applied(self):""" return the number of applied patches """ifself.mqloaded:returnlen(self.repo.mq.applied)return0defget_num_unapplied(self):""" return the number of unapplied patches """ifself.mqloaded:returnself.get_num_patches()-self.get_num_applied()return0defis_operable(self):""" return True if MQ is operable """ifself.mqloaded:repo=self.repoif'qtip'inself.repo.tags():returnrepo['.']==repo['qtip']returnlen(repo.mq.series)>0returnFalse def is_applied(self, name):
if self.mqloaded:
- return name in self.repo.mq.applied+ return self.repo.mq.isapplied(name) return False
def is_qtip(self, name):
ifname:returnname==self.get_qtip_patchname()returnFalse### internal functions ###defget_iter_by_patchname(self,name):""" return iter has specified patch name """ifname:forrowinself.model:ifrow[MQ_NAME]==name:returnrow.iterreturnNonedefget_path_by_patchname(self,name):""" return path has specified patch name """iter=self.get_iter_by_patchname(name)ifiter:returnself.model.get_path(iter)returnNonedefget_row_by_patchname(self,name):""" return row has specified patch name """path=self.get_path_by_patchname(name)ifpath:returnself.model[path]returnNonedefget_qtip_patchname(self):ifself.mqloadedandself.get_num_applied()>0 \
and'qtip'inself.repo.tags():returnself.repo.mq.applied[-1].namereturnNonedefupdate_sensitives(self):""" Update the sensitives of entire UI """defdisable_mqmoves():fornamein('popall','pop','push','pushall'):self.btn[name].set_sensitive(False)ifself.mqloaded:self.list.set_sensitive(True)self.btn['menu'].set_sensitive(True)ifself.is_operable():q=self.repo.mqin_bottom=len(q.applied)==0in_top=len(q.unapplied(self.repo))==0self.btn['popall'].set_sensitive(notin_bottom)self.btn['pop'].set_sensitive(notin_bottom)self.btn['push'].set_sensitive(notin_top)self.btn['pushall'].set_sensitive(notin_top)else:disable_mqmoves()else:self.list.set_sensitive(False)self.btn['menu'].set_sensitive(False)disable_mqmoves()defscroll_to_current(self):""" Scroll to current patch in the patch list. If the patch is selected, it will do nothing. """ifself.list.get_selection().count_selected_rows()>0:returnqtipname=self.get_qtip_patchname()ifnotqtipname:returnpath=self.get_path_by_patchname(qtipname)ifpath:self.list.scroll_to_cell(path)defcell_data_func(self,column,cell,model,iter):row=model[iter]ifrow[MQ_INDEX]==INDEX_QPARENT:ifcolumn==self.cols[MQ_INDEX]:cell.set_property('text','')elifcolumn==self.cols[MQ_NAME]:cell.set_property('text','[qparent]')stat=row[MQ_STATUS]ifstat=='A':cell.set_property('foreground','blue')elifstat=='U':cell.set_property('foreground','#909090')else:cell.set_property('foreground','black')patchname=row[MQ_NAME]ifself.is_qtip(patchname):cell.set_property('weight',pango.WEIGHT_BOLD)else:cell.set_property('weight',pango.WEIGHT_NORMAL)defrow_sep_func(self,model,iter,data=None):returnmodel[iter][MQ_INDEX]==INDEX_SEPARATORdefshow_patch_cmenu(self,path):row=self.model[path]ifrow[MQ_INDEX]==INDEX_SEPARATOR:returnm=gtklib.MenuBuilder()defappend(*args):m.append(*args,**dict(args=[row]))is_operable=self.is_operable()has_patch=self.has_patch()has_applied=self.has_applied()is_qtip=self.is_qtip(row[MQ_NAME])is_qparent=row[MQ_INDEX]==INDEX_QPARENTis_applied=row[MQ_STATUS]=='A'ifis_operableandnotis_qtipand(notis_qparentorhas_applied):append(_('_Goto'),self.goto_activated,gtk.STOCK_JUMP_TO)ifhas_patchandnotis_qparent:append(_('_Rename'),self.rename_activated,gtk.STOCK_EDIT)ifhas_appliedandnotis_qparent:append(_('_Finish Applied'),self.finish_activated,gtk.STOCK_APPLY)ifnotis_appliedandnotis_qparent:append(_('_Delete'),self.delete_activated,gtk.STOCK_DELETE)ifhas_appliedandnotis_qparent:append(_('F_old'),self.fold_activated,gtk.STOCK_DIRECTORY)ifself.get_num_unapplied()>1:sub=gtklib.MenuBuilder()sub.append(_('Top'),lambda*a:self.qmove_ui(MOVE_TOP),gtk.STOCK_GOTO_TOP,args=[row])sub.append(_('Up'),lambda*a:self.qmove_ui(MOVE_UP),gtk.STOCK_GO_UP,args=[row])sub.append(_('Down'),lambda*a:self.qmove_ui(MOVE_DOWN),gtk.STOCK_GO_DOWN,args=[row])sub.append(_('Bottom'),lambda*a:self.qmove_ui(MOVE_BOTTOM),gtk.STOCK_GOTO_BOTTOM,args=[row])m.append_submenu(_('Move'),sub.build(),gtk.STOCK_INDEX)menu=m.build()iflen(menu.get_children())>0:menu.show_all()menu.popup(None,None,None,0,0)defcreate_view_menu(self):self.vmenu={}m=gtklib.MenuBuilder()defcolappend(label,col_idx,active=True):defhandler(menuitem):col=self.cols[col_idx]col.set_visible(menuitem.get_active())propname=self.col_to_prop(col_idx)item=m.append(label,handler,ascheck=True,check=active)self.vmenu[propname]=itemcolappend(_('Show Index'),MQ_INDEX)colappend(_('Show Status'),MQ_STATUS,active=False)colappend(_('Show Summary'),MQ_SUMMARY,active=False)m.append_sep()defenable_editable(item):self.cells[MQ_NAME].set_property('editable',item.get_active())item=m.append(_('Enable editable cells'),enable_editable,ascheck=True,check=False)self.vmenu['editable-cell']=itemitem=m.append(_("Show 'qparent'"),lambdaitem:self.refresh(),ascheck=True,check=True)self.vmenu['show-qparent']=itemmenu=m.build()menu.show_all()returnmenudefqgoto_by_row(self,row):ifself.get_qtip_patchname()==row[MQ_NAME]:returnifrow[MQ_INDEX]==INDEX_QPARENT:self.qpop(all=True)else:self.qgoto(row[MQ_NAME])defcmd_done(self,returncode,useraborted,noemit=False):ifreturncode==0:ifself.cmd.get_pbar():self.cmd.set_result(_('Succeed'),style='ok')elifuseraborted:self.cmd.set_result(_('Canceled'),style='error')else:self.cmd.set_result(_('Failed'),style='error')hglib.invalidaterepo(self.repo)self.refresh()ifnotnoemit:self.emit('repo-invalidated')defdo_get_property(self,property):ifproperty.name=='name-column-visible':returnTruetry:returnself.vmenu[property.name].get_active()except:raiseAttributeError,'unknown property %s'%property.namedefdo_set_property(self,property,value):try:self.vmenu[property.name].set_active(value)except:raiseAttributeError,'unknown property %s'%property.namedefcol_to_prop(self,col_idx):ifcol_idx==MQ_INDEX:return'index-column-visible'ifcol_idx==MQ_STATUS:return'status-column-visible'elifcol_idx==MQ_NAME:return'name-column-visible'elifcol_idx==MQ_SUMMARY:return'summary-column-visible'return''### signal handlers ###deflist_pressed(self,list,event):x,y=int(event.x),int(event.y)pathinfo=list.get_path_at_pos(x,y)ifevent.button==1:ifnotpathinfo:# HACK: clear selection after this function calling,# against selection by getting focusdefunselect():selection=list.get_selection()selection.unselect_all()gtklib.idle_add_single_call(unselect)deflist_released(self,list,event):ifevent.button!=3:returnx,y=int(event.x),int(event.y)pathinfo=list.get_path_at_pos(x,y)ifpathinfo:self.show_patch_cmenu(pathinfo[0])deflist_sel_changed(self,list):path,focus=list.get_cursor()row=self.model[path]ifrow[MQ_INDEX]<0:returnpatchname=row[MQ_NAME]try:ctx=self.repo[patchname]revid=ctx.rev()except(error.RepoError,error.RepoLookupError):revid=-1self.emit('patch-selected',revid,patchname)deflist_row_activated(self,list,path,column):self.qgoto_by_row(self.model[path])deflist_size_allocated(self,list,req):ifself.mqloadedandself.has_applied():self.scroll_to_current()defpopall_clicked(self,toolbutton):self.qpop(all=True)defpop_clicked(self,toolbutton):self.qpop()defpush_clicked(self,toolbutton):self.qpush()defpushall_clicked(self,toolbutton):self.qpush(all=True)defdnd_received(self,widget,context,x,y,sel,target,*args):iftarget==MQ_DND_URI_LIST:paths=gtklib.normalize_dnd_paths(sel.data)ifpaths:self.emit('files-dropped',paths,sel.data)### context menu signal handlers ###defgoto_activated(self,menuitem,row):self.qgoto_by_row(row)defdelete_activated(self,menuitem,row):self.qdelete(row[MQ_NAME])defrename_activated(self,menuitem,row):self.qrename_ui(row[MQ_NAME])deffinish_activated(self,menuitem,row):self.qfinish(applied=True)deffold_activated(self,menuitem,row):self.qfold(row[MQ_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.