PageRenderTime 79ms CodeModel.GetById 32ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgqt/shelve.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 420 lines | 384 code | 30 blank | 6 comment | 17 complexity | 8d3cf8a7a6652c7db38f492bd66bc2e8 MD5 | raw file
  1# shelve.py - TortoiseHg shelve and patch tool
  2#
  3# Copyright 2011 Steve Borho <steve@borho.org>
  4#
  5# This software may be used and distributed according to the terms
  6# of the GNU General Public License, incorporated herein by reference.
  7
  8import os
  9import time
 10
 11from tortoisehg.util import hglib
 12from tortoisehg.util.patchctx import patchctx
 13from tortoisehg.hgqt.i18n import _
 14from tortoisehg.hgqt import qtlib, cmdui, chunks
 15
 16from PyQt4.QtCore import *
 17from PyQt4.QtGui import *
 18
 19class ShelveDialog(QDialog):
 20
 21    finished = pyqtSignal(int)
 22    wdir = _('Working Directory')
 23
 24    def __init__(self, repo, parent):
 25        QDialog.__init__(self, parent)
 26
 27        self.repo = repo
 28        self.shelves = []
 29        self.patches = []
 30
 31        layout = QVBoxLayout()
 32        layout.setContentsMargins(2, 2, 2, 2)
 33        layout.setSpacing(0)
 34        self.setLayout(layout)
 35
 36        self.tbarhbox = hbox = QHBoxLayout()
 37        hbox.setContentsMargins(0, 0, 0, 0)
 38        hbox.setSpacing(0)
 39        self.layout().addLayout(self.tbarhbox)
 40
 41        self.splitter = QSplitter(self)
 42        self.splitter.setOrientation(Qt.Horizontal)
 43        self.splitter.setChildrenCollapsible(False)
 44        self.splitter.setObjectName('splitter')
 45        self.layout().addWidget(self.splitter, 1)
 46
 47        aframe = QFrame(self.splitter)
 48        avbox = QVBoxLayout()
 49        avbox.setSpacing(2)
 50        avbox.setMargin(2)
 51        avbox.setContentsMargins(2, 2, 2, 2)
 52        aframe.setLayout(avbox)
 53        ahbox = QHBoxLayout()
 54        ahbox.setSpacing(2)
 55        ahbox.setMargin(2)
 56        ahbox.setContentsMargins(2, 2, 2, 2)
 57        avbox.addLayout(ahbox)
 58        self.comboa = QComboBox(self)
 59        self.comboa.currentIndexChanged.connect(self.comboAChanged)
 60        self.delShelfButtonA = QPushButton(_('Delete'))
 61        self.delShelfButtonA.setToolTip(_('Delete the current shelf file'))
 62        self.delShelfButtonA.clicked.connect(self.deleteShelfA)
 63        ahbox.addWidget(self.comboa, 1)
 64        ahbox.addWidget(self.delShelfButtonA)
 65
 66        self.browsea = chunks.ChunksWidget(repo, self)
 67        self.browsea.splitter.splitterMoved.connect(self.linkSplitters)
 68        self.browsea.linkActivated.connect(self.linkActivated)
 69        self.browsea.showMessage.connect(self.showMessage)
 70        avbox.addWidget(self.browsea)
 71
 72        bframe = QFrame(self.splitter)
 73        bvbox = QVBoxLayout()
 74        bvbox.setSpacing(2)
 75        bvbox.setMargin(2)
 76        bvbox.setContentsMargins(2, 2, 2, 2)
 77        bframe.setLayout(bvbox)
 78        bhbox = QHBoxLayout()
 79        bhbox.setSpacing(2)
 80        bhbox.setMargin(2)
 81        bhbox.setContentsMargins(2, 2, 2, 2)
 82        bvbox.addLayout(bhbox)
 83        self.combob = QComboBox(self)
 84        self.combob.currentIndexChanged.connect(self.comboBChanged)
 85        self.delShelfButtonB = QPushButton(_('Delete'))
 86        self.delShelfButtonB.setToolTip(_('Delete the current shelf file'))
 87        self.delShelfButtonB.clicked.connect(self.deleteShelfB)
 88        bhbox.addWidget(self.combob, 1)
 89        bhbox.addWidget(self.delShelfButtonB)
 90
 91        self.browseb = chunks.ChunksWidget(repo, self)
 92        self.browseb.splitter.splitterMoved.connect(self.linkSplitters)
 93        self.browseb.linkActivated.connect(self.linkActivated)
 94        self.browseb.showMessage.connect(self.showMessage)
 95        bvbox.addWidget(self.browseb)
 96
 97        self.lefttbar = QToolBar(_('Left Toolbar'), objectName='lefttbar')
 98        self.tbarhbox.addWidget(self.lefttbar)
 99        self.deletea = a = QAction(_('Deleted selected chunks'), self)
100        self.deletea.triggered.connect(self.browsea.deleteSelectedChunks)
101        a.setIcon(qtlib.geticon('delfilesleft'))
102        self.lefttbar.addAction(self.deletea)
103        self.allright = a = QAction(_('Move all files right'), self)
104        self.allright.triggered.connect(self.moveFilesRight)
105        a.setIcon(qtlib.geticon('all2right'))
106        self.lefttbar.addAction(self.allright)
107        self.fileright = a = QAction(_('Move selected file right'), self)
108        self.fileright.triggered.connect(self.moveFileRight)
109        a.setIcon(qtlib.geticon('file2right'))
110        self.lefttbar.addAction(self.fileright)
111        self.editfilea = a = QAction(_('Edit file'), self)
112        a.setIcon(qtlib.geticon('edit-find'))
113        self.lefttbar.addAction(self.editfilea)
114        self.chunksright = a = QAction(_('Move selected chunks right'), self)
115        self.chunksright.triggered.connect(self.moveChunksRight)
116        a.setIcon(qtlib.geticon('chunk2right'))
117        self.lefttbar.addAction(self.chunksright)
118
119        self.rbar = QToolBar(_('Refresh Toolbar'), objectName='rbar')
120        self.tbarhbox.addStretch(1)
121        self.tbarhbox.addWidget(self.rbar)
122        self.refreshAction = a = QAction(_('Refresh'), self)
123        a.setIcon(qtlib.geticon('reload'))
124        a.setShortcut(QKeySequence.Refresh)
125        a.triggered.connect(self.refreshCombos)
126        self.rbar.addAction(self.refreshAction)
127        self.actionNew = a = QAction(_('New Shelf'), self)
128        a.setIcon(qtlib.geticon('document-new'))
129        a.triggered.connect(self.newShelfPressed)
130        self.rbar.addAction(self.actionNew)
131
132        self.righttbar = QToolBar(_('Right Toolbar'), objectName='righttbar')
133        self.tbarhbox.addStretch(1)
134        self.tbarhbox.addWidget(self.righttbar)
135        self.chunksleft = a = QAction(_('Move selected chunks left'), self)
136        self.chunksleft.triggered.connect(self.moveChunksLeft)
137        a.setIcon(qtlib.geticon('chunk2left'))
138        self.righttbar.addAction(self.chunksleft)
139        self.editfileb = a = QAction(_('Edit file'), self)
140        a.setIcon(qtlib.geticon('edit-find'))
141        self.righttbar.addAction(self.editfileb)
142        self.fileleft = a = QAction(_('Move selected file left'), self)
143        self.fileleft.triggered.connect(self.moveFileLeft)
144        a.setIcon(qtlib.geticon('file2left'))
145        self.righttbar.addAction(self.fileleft)
146        self.allleft = a = QAction(_('Move all files left'), self)
147        self.allleft.triggered.connect(self.moveFilesLeft)
148        a.setIcon(qtlib.geticon('all2left'))
149        self.righttbar.addAction(self.allleft)
150        self.deleteb = a = QAction(_('Deleted selected chunks'), self)
151        self.deleteb.triggered.connect(self.browseb.deleteSelectedChunks)
152        a.setIcon(qtlib.geticon('delfilesright'))
153        self.righttbar.addAction(self.deleteb)
154
155        self.editfilea.triggered.connect(self.browsea.editCurrentFile)
156        self.editfileb.triggered.connect(self.browseb.editCurrentFile)
157
158        self.browsea.chunksSelected.connect(self.chunksright.setEnabled)
159        self.browsea.chunksSelected.connect(self.deletea.setEnabled)
160        self.browsea.fileSelected.connect(self.fileright.setEnabled)
161        self.browsea.fileSelected.connect(self.editfilea.setEnabled)
162        self.browsea.fileModified.connect(self.refreshCombos)
163        self.browsea.fileModelEmpty.connect(self.allright.setDisabled)
164        self.browseb.chunksSelected.connect(self.chunksleft.setEnabled)
165        self.browseb.chunksSelected.connect(self.deleteb.setEnabled)
166        self.browseb.fileSelected.connect(self.fileleft.setEnabled)
167        self.browseb.fileSelected.connect(self.editfileb.setEnabled)
168        self.browseb.fileModified.connect(self.refreshCombos)
169        self.browseb.fileModelEmpty.connect(self.allleft.setDisabled)
170
171        self.statusbar = cmdui.ThgStatusBar(self)
172        self.layout().addWidget(self.statusbar)
173
174        self.refreshCombos()
175        repo.repositoryChanged.connect(self.refreshCombos)
176
177        self.setWindowTitle(_('TortoiseHg Shelve - %s') % repo.displayname)
178        self.restoreSettings()
179
180    @pyqtSlot()
181    def moveFileRight(self):
182        if self.combob.currentIndex() == -1:
183            self.newShelf(False)
184        file, _ = self.browsea.getSelectedFileAndChunks()
185        chunks = self.browsea.getChunksForFile(file)
186        if self.browseb.mergeChunks(file, chunks):
187            self.browsea.removeFile(file)
188
189    @pyqtSlot()
190    def moveFileLeft(self):
191        file, _ = self.browseb.getSelectedFileAndChunks()
192        chunks = self.browseb.getChunksForFile(file)
193        if self.browsea.mergeChunks(file, chunks):
194            self.browseb.removeFile(file)
195
196    @pyqtSlot()
197    def moveFilesRight(self):
198        if self.combob.currentIndex() == -1:
199            self.newShelf(False)
200        for file in self.browsea.getFileList():
201            chunks = self.browsea.getChunksForFile(file)
202            if self.browseb.mergeChunks(file, chunks):
203                self.browsea.removeFile(file)
204
205    @pyqtSlot()
206    def moveFilesLeft(self):
207        for file in self.browseb.getFileList():
208            chunks = self.browseb.getChunksForFile(file)
209            if self.browsea.mergeChunks(file, chunks):
210                self.browseb.removeFile(file)
211
212    @pyqtSlot()
213    def moveChunksRight(self):
214        if self.combob.currentIndex() == -1:
215            self.newShelf(False)
216        file, chunks = self.browsea.getSelectedFileAndChunks()
217        if self.browseb.mergeChunks(file, chunks):
218            self.browsea.deleteSelectedChunks()
219
220    @pyqtSlot()
221    def moveChunksLeft(self):
222        file, chunks = self.browseb.getSelectedFileAndChunks()
223        if self.browsea.mergeChunks(file, chunks):
224            self.browseb.deleteSelectedChunks()
225
226    @pyqtSlot()
227    def newShelfPressed(self):
228        self.newShelf(True)
229
230    def newShelf(self, interactive):
231        shelve = time.strftime('%Y-%m-%d_%H-%M-%S')
232        if interactive:
233            dlg = QInputDialog(self, Qt.Sheet)
234            dlg.setWindowModality(Qt.WindowModal)
235            dlg.setWindowTitle(_('TortoiseHg New Shelf Name'))
236            dlg.setLabelText(_('Specify name of new shelf'))
237            dlg.setTextValue(shelve)
238            if not dlg.exec_():
239                return
240            shelve = hglib.fromunicode(dlg.textValue())
241        try:
242            fn = os.path.join('shelves', shelve)
243            shelfpath = self.repo.join(fn)
244            if os.path.exists(shelfpath):
245                qtlib.ErrorMsgBox(_('File already exists'),
246                                  _('A shelf file of that name already exists'))
247                return
248            self.repo.opener(fn, 'wb').write('')
249            self.showMessage(_('New shelf created'))
250            self.refreshCombos()
251            if shelfpath in self.shelves:
252                self.combob.setCurrentIndex(self.shelves.index(shelfpath))
253        except EnvironmentError, e:
254            self.showMessage(hglib.tounicode(str(e)))
255
256    @pyqtSlot()
257    def deleteShelfA(self):
258        shelf = self.currentPatchA()
259        ushelf = hglib.tounicode(os.path.basename(shelf))
260        if not qtlib.QuestionMsgBox(_('Are you sure?'),
261                                    _('Delete shelf file %s?') % ushelf):
262            return
263        try:
264            os.unlink(shelf)
265            self.showMessage(_('Shelf deleted'))
266        except EnvironmentError, e:
267            self.showMessage(hglib.tounicode(str(e)))
268        self.refreshCombos()
269
270    @pyqtSlot()
271    def deleteShelfB(self):
272        shelf = self.currentPatchB()
273        ushelf = hglib.tounicode(os.path.basename(shelf))
274        if not qtlib.QuestionMsgBox(_('Are you sure?'),
275                                    _('Delete shelf file %s?') % ushelf):
276            return
277        try:
278            os.unlink(shelf)
279            self.showMessage(_('Shelf deleted'))
280        except EnvironmentError, e:
281            self.showMessage(hglib.tounicode(str(e)))
282        self.refreshCombos()
283
284    def currentPatchA(self):
285        idx = self.comboa.currentIndex()
286        if idx == -1:
287            return None
288        if idx == 0:
289            return self.wdir
290        idx -= 1
291        if idx < len(self.shelves):
292            return self.shelves[idx]
293        idx -= len(self.shelves)
294        if idx < len(self.patches):
295            return self.patches[idx]
296        return None
297
298    def currentPatchB(self):
299        idx = self.combob.currentIndex()
300        if idx == -1:
301            return None
302        if idx < len(self.shelves):
303            return self.shelves[idx]
304        idx -= len(self.shelves)
305        if idx < len(self.patches):
306            return self.patches[idx]
307        return None
308
309    @pyqtSlot()
310    def refreshCombos(self):
311        shelvea, shelveb = self.currentPatchA(), self.currentPatchB()
312
313        shelves = self.repo.thgshelves()
314        disp = [_('Shelf: %s') % hglib.tounicode(s) for s in shelves]
315
316        patches = self.repo.thgmqunappliedpatches
317        disp += [_('Patch: %s') % hglib.tounicode(p) for p in patches]
318
319        # store fully qualified paths
320        self.shelves = [os.path.join(self.repo.shelfdir, s) for s in shelves]
321        self.patches = [self.repo.mq.join(p) for p in patches]
322
323        self.comboRefreshInProgress = True
324        self.comboa.clear()
325        self.combob.clear()
326        self.comboa.addItems([self.wdir] + disp)
327        self.combob.addItems(disp)
328
329        # attempt to restore selection
330        if shelvea == self.wdir:
331            idxa = 0
332        elif shelvea in self.shelves:
333            idxa = self.shelves.index(shelvea) + 1
334        elif shelvea in self.patches:
335            idxa = len(self.shelves) + self.patches.index(shelvea) + 1
336        else:
337            idxa = 0
338        self.comboa.setCurrentIndex(idxa)
339
340        if shelveb in self.shelves:
341            idxb = self.shelves.index(shelveb)
342        elif shelveb in self.patches:
343            idxb = len(self.shelves) + self.patches.index(shelveb)
344        else:
345            idxb = 0
346        self.combob.setCurrentIndex(idxb)
347        self.comboRefreshInProgress = False
348
349        self.comboAChanged(idxa)
350        self.comboBChanged(idxb)
351        if not patches and not shelves:
352            self.delShelfButtonB.setEnabled(False)
353            self.browseb.setContext(patchctx('', self.repo, None))
354
355    @pyqtSlot(int)
356    def comboAChanged(self, index):
357        if self.comboRefreshInProgress:
358            return
359        if index == 0:
360            rev = None
361            self.delShelfButtonA.setEnabled(False)
362        else:
363            rev = self.currentPatchA()
364            self.delShelfButtonA.setEnabled(index <= len(self.shelves))
365        self.browsea.setContext(self.repo.changectx(rev))
366
367    @pyqtSlot(int)
368    def comboBChanged(self, index):
369        if self.comboRefreshInProgress:
370            return
371        rev = self.currentPatchB()
372        self.delShelfButtonB.setEnabled(index < len(self.shelves))
373        self.browseb.setContext(self.repo.changectx(rev))
374
375    @pyqtSlot(int, int)
376    def linkSplitters(self, pos, index):
377        if self.browsea.splitter.sizes()[0] != pos:
378            self.browsea.splitter.moveSplitter(pos, index)
379        if self.browseb.splitter.sizes()[0] != pos:
380            self.browseb.splitter.moveSplitter(pos, index)
381
382    @pyqtSlot(QString)
383    def linkActivated(self, linktext):
384        pass
385
386    @pyqtSlot(QString)
387    def showMessage(self, message):
388        self.statusbar.showMessage(message)
389
390    def storeSettings(self):
391        s = QSettings()
392        wb = "shelve/"
393        s.setValue(wb + 'geometry', self.saveGeometry())
394        s.setValue(wb + 'filesplitter', self.browsea.splitter.saveState())
395        self.browsea.saveSettings(s, wb + 'fileviewa')
396        self.browseb.saveSettings(s, wb + 'fileviewb')
397
398    def restoreSettings(self):
399        s = QSettings()
400        wb = "shelve/"
401        self.restoreGeometry(s.value(wb + 'geometry').toByteArray())
402        self.browsea.splitter.restoreState(
403                          s.value(wb + 'filesplitter').toByteArray())
404        self.browseb.splitter.restoreState(
405                          s.value(wb + 'filesplitter').toByteArray())
406        self.browsea.loadSettings(s, wb + 'fileviewa')
407        self.browseb.loadSettings(s, wb + 'fileviewb')
408
409    def closeEvent(self, event):
410        self.storeSettings()
411        super(ShelveDialog, self).closeEvent(event)
412
413def run(ui, *pats, **opts):
414    if 'repo' in opts:
415        repo = opts['repo']
416    else:
417        from tortoisehg.util import paths
418        from tortoisehg.hgqt import thgrepo
419        repo = thgrepo.repository(ui, path=paths.find_root())
420    return ShelveDialog(repo, None)