NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

toolUI.py (45989B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\fsd\diffMergeTool\toolUI.py
      2 from __future__ import print_function
      3 import sys
      4 from PySide import QtGui
      5 from PySide import QtCore
      6 from fsd.diffMergeTool.treeview import TreeViewWidget
      7 from fsd.common.fsdYamlExtensions import FsdYamlDumper
      8 import yaml
      9 import unicodedata
     10 import time
     11 import threading
     12 from fsd.diffMerge.frameworkInterface import FsdDiffMerger
     13 from fsd.diffMerge.frameworkInterface import InsertItemToDictionaryAtLocation
     14 from fsd.diffMerge.diffObjects import AreEqual
     15 DOES_NOT_EXIST_STRING = '(DOES NOT EXIST)'
     16 
     17 class DroppableQTextEdit(QtGui.QTextEdit):
     18 
     19     def __init__(self, parent = None):
     20         super(DroppableQTextEdit, self).__init__(parent)
     21 
     22     def dragEnterEvent(self, e):
     23         pass
     24 
     25     def dropEvent(self, e):
     26         self.setText(e.mimeData().text())
     27 
     28 
     29 class FsdDiffMergeUI(QtGui.QWidget):
     30 
     31     def LoadArgument(self, argument, target):
     32         with open(argument, 'rb') as f:
     33             target.setText(f.read().decode('utf-8'))
     34 
     35     def _HandleCommandLineArguments(self, arguments):
     36         self.resultFile = None
     37         if arguments:
     38             if len(arguments) == 2:
     39                 self.LoadArgument(arguments[0], self.top.mine)
     40                 self.LoadArgument(arguments[1], self.top.theirs)
     41                 self.top.hide()
     42                 self.bottomRight.tabs.removePage(self.bottomRight.changeTab)
     43                 self.bottomLeft.numberOfChanges.hide()
     44                 self.bottomLeft.numberOfAdds.hide()
     45                 self.SafeMerge()
     46             elif len(arguments) == 4:
     47                 self.LoadArgument(arguments[0], self.top.base)
     48                 self.LoadArgument(arguments[1], self.top.mine)
     49                 self.LoadArgument(arguments[2], self.top.theirs)
     50                 self.resultFile = arguments[3]
     51                 self.top.hide()
     52                 self.SafeMerge()
     53             else:
     54                 print('Unsupported argument count (%s), starting up normally' % str(len(arguments)))
     55 
     56     def __init__(self, arguments):
     57         super(FsdDiffMergeUI, self).__init__()
     58         self.setAcceptDrops(True)
     59         self.merger = None
     60         self.InitUI()
     61         self._HandleCommandLineArguments(arguments)
     62 
     63     def _SetupTop(self):
     64         self.top = QtGui.QFrame(self)
     65         self.top.setFrameShape(QtGui.QFrame.StyledPanel)
     66         self.top.grid = QtGui.QGridLayout()
     67         self.top.grid.setSpacing(10)
     68         baseLabel = QtGui.QLabel('Base')
     69         mineLabel = QtGui.QLabel('Mine')
     70         theirLabel = QtGui.QLabel('Theirs')
     71         loadMineButton = QtGui.QPushButton('Browse')
     72         loadMineButton.clicked.connect(self.LoadFileToMine)
     73         loadBaseButton = QtGui.QPushButton('Browse')
     74         loadBaseButton.clicked.connect(self.LoadFileToBase)
     75         loadTheirsButton = QtGui.QPushButton('Browse')
     76         loadTheirsButton.clicked.connect(self.LoadFileToTheirs)
     77         mergeButton = QtGui.QPushButton('Safe Merge', self)
     78         mergeButton.clicked.connect(self.SafeMerge)
     79         self.top.base = DroppableQTextEdit()
     80         self.top.mine = DroppableQTextEdit()
     81         self.top.theirs = DroppableQTextEdit()
     82         self.top.grid.addWidget(mineLabel, 0, 0, 1, 1)
     83         self.top.grid.addWidget(loadMineButton, 0, 1, 1, 1)
     84         self.top.grid.addWidget(baseLabel, 0, 2, 1, 1)
     85         self.top.grid.addWidget(loadBaseButton, 0, 3, 1, 1)
     86         self.top.grid.addWidget(theirLabel, 0, 4, 1, 1)
     87         self.top.grid.addWidget(loadTheirsButton, 0, 5, 1, 1)
     88         self.top.grid.addWidget(self.top.mine, 1, 0, 1, 2)
     89         self.top.grid.addWidget(self.top.base, 1, 2, 1, 2)
     90         self.top.grid.addWidget(self.top.theirs, 1, 4, 1, 2)
     91         self.top.grid.addWidget(mergeButton, 2, 2, 1, 2)
     92         self.top.setLayout(self.top.grid)
     93 
     94     def _SetupBottomLeft(self):
     95         self.bottomLeft = QtGui.QFrame(self)
     96         self.bottomLeft.setFrameShape(QtGui.QFrame.StyledPanel)
     97         conflictLabel = QtGui.QLabel("<font color='#888888'>Conflicts</font>")
     98         changeLabel = QtGui.QLabel("<font color='#888888'>Changes</font>")
     99         addLabel = QtGui.QLabel("<font color='#888888'>Adds</font>")
    100         self.bottomLeft.numberOfAdds = QtGui.QLabel('')
    101         self.bottomLeft.numberOfChanges = QtGui.QLabel('')
    102         self.bottomLeft.numberOfConflicts = QtGui.QLabel('')
    103         self.bottomLeft.undoButton = QtGui.QPushButton('Undo', self)
    104         self.bottomLeft.undoButton.setEnabled(False)
    105         self.bottomLeft.undoList = []
    106         self.bottomLeft.undoButton.clicked.connect(self.Undo)
    107         self.bottomLeft.tree = TreeViewWidget()
    108         self.bottomLeft.saveButton = QtGui.QPushButton('Save', self)
    109         self.bottomLeft.saveButton.clicked.connect(self.Save)
    110         self.bottomLeft.statusBar = QtGui.QStatusBar(self)
    111         self.bottomLeft.statusBar.setSizeGripEnabled(False)
    112         self.bottomLeft.grid = QtGui.QGridLayout()
    113         self.bottomLeft.grid.setSpacing(10)
    114         self.bottomLeft.grid.addWidget(conflictLabel, 0, 0, 1, 1)
    115         self.bottomLeft.grid.addWidget(changeLabel, 0, 1, 1, 1)
    116         self.bottomLeft.grid.addWidget(addLabel, 0, 2, 1, 1)
    117         self.bottomLeft.grid.addWidget(self.bottomLeft.numberOfConflicts, 1, 0, 1, 1)
    118         self.bottomLeft.grid.addWidget(self.bottomLeft.numberOfChanges, 1, 1, 1, 1)
    119         self.bottomLeft.grid.addWidget(self.bottomLeft.numberOfAdds, 1, 2, 1, 1)
    120         self.bottomLeft.grid.addWidget(self.bottomLeft.undoButton, 1, 4, 1, 1)
    121         self.bottomLeft.grid.addWidget(self.bottomLeft.tree.GetWidget(), 2, 0, 1, 5)
    122         self.bottomLeft.grid.addWidget(self.bottomLeft.saveButton, 3, 0, 1, 2)
    123         self.bottomLeft.grid.addWidget(self.bottomLeft.statusBar, 3, 2, 1, 3)
    124         self.bottomLeft.setLayout(self.bottomLeft.grid)
    125         self.bottomLeft.tree.GetWidget().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    126         self.bottomLeft.tree.GetWidget().customContextMenuRequested.connect(self._RightClickOnResultTree)
    127         self.bottomLeft.tree.GetWidget().clicked.connect(self._ItemSelectedFromResultTree)
    128 
    129     def _SetupConflictTab(self):
    130         tabHeader = QtGui.QLabel('Conflict Locations')
    131         self.bottomRight.numberOfConflictsLabel = QtGui.QLabel('')
    132         tabHeader.setToolTip('Select a conflict to move to location in tree')
    133         self.bottomRight.useMineButton = QtGui.QPushButton('Use mine')
    134         self.bottomRight.useMineButton.clicked.connect(self._UseMineButtonClicked)
    135         self.bottomRight.useBaseButton = QtGui.QPushButton('Use base')
    136         self.bottomRight.useBaseButton.clicked.connect(self._UseBaseButtonClicked)
    137         self.bottomRight.useTheirsButton = QtGui.QPushButton('Use theirs')
    138         self.bottomRight.useTheirsButton.clicked.connect(self._UseTheirsButtonClicked)
    139         self.bottomRight.conflictList = QtGui.QListWidget()
    140         self.bottomRight.conflictList.clicked.connect(self._ConflictSelectedFromList)
    141         self.bottomRight.myConflictTree = TreeViewWidget()
    142         self.bottomRight.baseConflictTree = TreeViewWidget()
    143         self.bottomRight.theirConflictTree = TreeViewWidget()
    144         conflictTabGrid = QtGui.QGridLayout()
    145         conflictTabGrid.addWidget(tabHeader, 0, 0, 1, 4)
    146         conflictTabGrid.addWidget(self.bottomRight.numberOfConflictsLabel, 0, 4, 1, 2)
    147         conflictTabGrid.addWidget(self.bottomRight.conflictList, 1, 0, 1, 6)
    148         conflictTabGrid.addWidget(self.bottomRight.useMineButton, 2, 0, 1, 2)
    149         conflictTabGrid.addWidget(self.bottomRight.useBaseButton, 2, 2, 1, 2)
    150         conflictTabGrid.addWidget(self.bottomRight.useTheirsButton, 2, 4, 1, 2)
    151         conflictTabGrid.addWidget(self.bottomRight.myConflictTree.GetWidget(), 3, 0, 1, 2)
    152         conflictTabGrid.addWidget(self.bottomRight.baseConflictTree.GetWidget(), 3, 2, 1, 2)
    153         conflictTabGrid.addWidget(self.bottomRight.theirConflictTree.GetWidget(), 3, 4, 1, 2)
    154         self.bottomRight.currentConflict = None
    155         self._HideSolveConflictButtons()
    156         return conflictTabGrid
    157 
    158     def _SetupChangesTab(self):
    159         tabHeader = QtGui.QLabel('Change Locations')
    160         tabHeader.setToolTip('Select a change to move to location in tree')
    161         tabDescription = QtGui.QLabel("These are changes that were safely merged and you shouldn't worry about them, but you can review them here")
    162         self.bottomRight.changeList = QtGui.QListWidget()
    163         self.bottomRight.changeList.clicked.connect(self._ChangeSelectedFromList)
    164         self.bottomRight.currentDataTree = TreeViewWidget()
    165         self.bottomRight.baseDataTree = TreeViewWidget()
    166         self.bottomRight.currentDataLabel = QtGui.QLabel('')
    167         baseDataLabel = QtGui.QLabel('Base:')
    168         changeTabGrid = QtGui.QGridLayout()
    169         changeTabGrid.addWidget(tabHeader, 0, 0, 1, 2)
    170         changeTabGrid.addWidget(tabDescription, 1, 0, 1, 2)
    171         changeTabGrid.addWidget(self.bottomRight.changeList, 2, 0, 1, 2)
    172         changeTabGrid.addWidget(self.bottomRight.currentDataLabel, 3, 0, 1, 1)
    173         changeTabGrid.addWidget(baseDataLabel, 3, 1, 1, 1)
    174         changeTabGrid.addWidget(self.bottomRight.currentDataTree.GetWidget(), 4, 0, 1, 1)
    175         changeTabGrid.addWidget(self.bottomRight.baseDataTree.GetWidget(), 4, 1, 1, 1)
    176         return changeTabGrid
    177 
    178     def _SetupBottomRight(self):
    179         self.bottomRight = QtGui.QFrame(self)
    180         self.bottomRight.setFrameShape(QtGui.QFrame.StyledPanel)
    181         self.bottomRight.tabs = QtGui.QTabWidget()
    182         self.bottomRight.conflictTab = QtGui.QWidget()
    183         self.bottomRight.changeTab = QtGui.QWidget()
    184         self.bottomRight.tabs.addTab(self.bottomRight.conflictTab, 'Conflicts')
    185         self.bottomRight.tabs.addTab(self.bottomRight.changeTab, 'Safe Merge Changes')
    186         conflictTabGrid = self._SetupConflictTab()
    187         changeTabGrid = self._SetupChangesTab()
    188         self.bottomRight.conflictTab.setLayout(conflictTabGrid)
    189         self.bottomRight.changeTab.setLayout(changeTabGrid)
    190         self.bottomRight.grid = QtGui.QGridLayout()
    191         self.bottomRight.grid.addWidget(self.bottomRight.tabs)
    192         self.bottomRight.setLayout(self.bottomRight.grid)
    193         self.bottomRight.conflictList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    194         self.bottomRight.conflictList.customContextMenuRequested.connect(self._RightClickOnConflictList)
    195         self.bottomRight.changeList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    196         self.bottomRight.changeList.customContextMenuRequested.connect(self._RightClickOnChangeList)
    197 
    198     def _SetupSplitterBetweenBottomLeftAndBottomRight(self):
    199         self.bottomSplitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
    200         self.bottomSplitter.addWidget(self.bottomLeft)
    201         self.bottomSplitter.addWidget(self.bottomRight)
    202 
    203     def _SetupSplitterBetweenTopAndBottom(self):
    204         self.topBottomSplitter = QtGui.QSplitter(QtCore.Qt.Vertical)
    205         self.topBottomSplitter.addWidget(self.top)
    206         self.topBottomSplitter.addWidget(self.bottomSplitter)
    207 
    208     def _SetupConflictMenu(self):
    209         self.conflictMenu = QtGui.QMenu()
    210         self.mergeMineAction = self.conflictMenu.addAction('Use Mine')
    211         self.mergeBaseAction = self.conflictMenu.addAction('Use Base')
    212         self.mergeTheirsAction = self.conflictMenu.addAction('Use Theirs')
    213 
    214     def _SetupChangeMenu(self):
    215         self.changeMenu = QtGui.QMenu()
    216         self.revertBaseAction = self.changeMenu.addAction('Revert to base')
    217         self.markAcceptedAction = self.changeMenu.addAction('Mark as accepted')
    218 
    219     def _FinalizeWindowLayout(self):
    220         self.hBox.addWidget(self.topBottomSplitter)
    221         self.setLayout(self.hBox)
    222 
    223     def _SetWindowDetails(self):
    224         self.setGeometry(50, 50, 1200, 800)
    225         self.setWindowTitle('FSD-DiffMerge')
    226         self.setWindowIcon(QtGui.QIcon('img/octo_logo.png'))
    227         self.show()
    228 
    229     def InitUI(self):
    230         self.hBox = QtGui.QHBoxLayout(self)
    231         QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('plastique'))
    232         self.setStyleSheet(APP_STYLE)
    233         self._SetupTop()
    234         self._SetupBottomLeft()
    235         self._SetupBottomRight()
    236         self._SetupSplitterBetweenBottomLeftAndBottomRight()
    237         self._SetupSplitterBetweenTopAndBottom()
    238         self._SetupConflictMenu()
    239         self._SetupChangeMenu()
    240         self._FinalizeWindowLayout()
    241         self._SetWindowDetails()
    242 
    243     def _LogTime(self, message, start):
    244         print(message, time.time() - start, 'seconds')
    245 
    246     def _UpdateAddsLabel(self):
    247         if len(self.diffs['added']) > 0:
    248             self.bottomLeft.numberOfAdds.setText("<font color='#66CC00'>(%s)</font>" % len(self.diffs['added']))
    249         else:
    250             self.bottomLeft.numberOfAdds.setText('')
    251 
    252     def SafeMerge(self):
    253         start = time.time()
    254         self.uiBase = yaml.load(self.top.base.toPlainText(), Loader=yaml.CSafeLoader)
    255         self.uiMine = yaml.load(self.top.mine.toPlainText(), Loader=yaml.CSafeLoader)
    256         self.uiTheirs = yaml.load(self.top.theirs.toPlainText(), Loader=yaml.CSafeLoader)
    257         self._LogTime('Time spent at yaml.load():', start)
    258         if self.uiMine is not None and self.uiTheirs is not None:
    259             if self.uiBase is None:
    260                 self.uiBase = {}
    261             start = time.time()
    262             self.merger = FsdDiffMerger(self.uiBase, self.uiMine, self.uiTheirs)
    263             self.merger.PerformConflictSearch()
    264             self.merger.PerformSafeMerge()
    265             self.merger.PerformDifferenceCheck()
    266             self.conflicts = self.merger.GetConflicts()
    267             self.mergedDict = self.merger.GetResultData()
    268             self.diffs = self.merger.GetDifferences()
    269             self.bottomLeft.undoList = []
    270             self.UpdateUndoButton()
    271             self._LogTime('Time spent processing by framework:', start)
    272             self._ClearConflictTabDiffTrees()
    273             self.UpdateUI()
    274             self._UpdateAddsLabel()
    275             self.top.hide()
    276 
    277     def _ClearConflictTabDiffTrees(self):
    278         self.bottomRight.myConflictTree.BuildTree({})
    279         self.bottomRight.baseConflictTree.BuildTree({})
    280         self.bottomRight.theirConflictTree.BuildTree({})
    281         self._HideSolveConflictButtons()
    282         self.bottomRight.currentConflict = None
    283 
    284     def _ClearChangeTabDiffTrees(self):
    285         self.bottomRight.currentDataTree.BuildTree({})
    286         self.bottomRight.baseDataTree.BuildTree({})
    287         self.bottomRight.currentDataLabel.setText('')
    288 
    289     def _FetchNewData(self):
    290         self.mergedDict = self.merger.GetResultData()
    291         self._ClearConflictTabDiffTrees()
    292         self._ClearChangeTabDiffTrees()
    293 
    294     def UpdateUndoButton(self):
    295         numberOfUndosLeft = len(self.bottomLeft.undoList)
    296         self.bottomLeft.undoButton.setEnabled(numberOfUndosLeft != 0)
    297         undoButtonValue = 'Undo'
    298         if numberOfUndosLeft != 0:
    299             undoButtonValue += ' (%s)' % numberOfUndosLeft
    300         self.bottomLeft.undoButton.setText(undoButtonValue)
    301 
    302     def AddToUndoList(self, location, data, originType):
    303         self.bottomLeft.undoList.append(UndoData(location, data, originType))
    304         self.UpdateUndoButton()
    305 
    306     def Undo(self):
    307         if len(self.bottomLeft.undoList) > 0:
    308             item = self.bottomLeft.undoList.pop(len(self.bottomLeft.undoList) - 1)
    309             InsertItemToDictionaryAtLocation(self.mergedDict, item.location, item.data)
    310             if item.IsConflict():
    311                 self.conflicts.append(item.location)
    312             elif item.IsChange():
    313                 self.diffs['changed'].append(item.location)
    314             self.GetNewDataAndRefreshUI(item.location)
    315             self.UpdateUndoButton()
    316             if item.IsConflict():
    317                 self._DisplayDiffTreesForConflictTab(item.location)
    318             elif item.IsChange():
    319                 self._DisplayDiffTreesForChangeTab(item.location)
    320 
    321     def AddConflictToUndoList(self, index):
    322         location = self.conflicts.pop(index)
    323         data = self.merger.GetObjectAtLocation(self.mergedDict, location)
    324         self.AddToUndoList(location, data, 'conflict')
    325 
    326     def SolveConflictUsingCorrectVersion(self, conflict, useVersion):
    327         if useVersion == 0:
    328             self.merger.SolveConflictUsingMine(conflict)
    329         elif useVersion == 1:
    330             self.merger.SolveConflictUsingBase(conflict)
    331         elif useVersion == 2:
    332             self.merger.SolveConflictUsingTheirs(conflict)
    333 
    334     def SolveConflict(self, conflict, useVersion):
    335         index = self._FindIndexOfConflict(conflict)
    336         if index != -1:
    337             self.AddConflictToUndoList(index)
    338             self.SolveConflictUsingCorrectVersion(conflict, useVersion)
    339             self.UpdateUIForSingleObject(conflict)
    340 
    341     def _RemoveChangeFromChangeList(self, changeLocationKeys):
    342         for index, diff in enumerate(self.diffs['changed']):
    343             if AreEqual(diff, changeLocationKeys):
    344                 self.diffs['changed'].pop(index)
    345                 break
    346 
    347     def GetNewDataAndRefreshUI(self, location):
    348         self._FetchNewData()
    349         self.UpdateUI()
    350         self.bottomLeft.tree.ExpandAndMoveTo(location)
    351 
    352     def _AddChangeDataToUndoList(self, location):
    353         data = self.merger.GetObjectAtLocation(self.mergedDict, location)
    354         self.AddToUndoList(location, data, 'change')
    355 
    356     def RevertChange(self, location):
    357         self._AddChangeDataToUndoList(location)
    358         self.merger.SolveConflictUsingBase(location)
    359         self._RemoveChangeFromChangeList(location)
    360         self.UpdateUIForSingleObject(location)
    361 
    362     def KeepChange(self, location):
    363         self._AddChangeDataToUndoList(location)
    364         self._RemoveChangeFromChangeList(location)
    365         self.UpdateUIForSingleObject(location)
    366 
    367     def _DisplayHowManyConflictsExist(self):
    368         value = '%s conflict%s' % (str(len(self.conflicts)), '' if len(self.conflicts) == 1 else 's')
    369         self.bottomRight.numberOfConflictsLabel.setText(value)
    370         if len(self.conflicts) > 0:
    371             self.bottomLeft.numberOfConflicts.setText("<font color='#ff0000'>(%s)</font>" % len(self.conflicts))
    372         else:
    373             self.bottomLeft.numberOfConflicts.setText("<font color='#787878'>(0)</font>")
    374 
    375     def _DisplayHowManyChangesExist(self):
    376         if len(self.diffs['changed']) > 0:
    377             self.bottomLeft.numberOfChanges.setText("<font color='#f9e9aa'>(%s)</font>" % len(self.diffs['changed']))
    378         else:
    379             self.bottomLeft.numberOfChanges.setText("<font color='#787878'>(0)</font>")
    380 
    381     def _ShowSolveConflictButtons(self):
    382         self.bottomRight.useMineButton.show()
    383         self.bottomRight.useBaseButton.show()
    384         self.bottomRight.useTheirsButton.show()
    385 
    386     def _HideSolveConflictButtons(self):
    387         self.bottomRight.useMineButton.hide()
    388         self.bottomRight.useBaseButton.hide()
    389         self.bottomRight.useTheirsButton.hide()
    390 
    391     def _BuildConflictLists(self):
    392         self._BuildConflictList()
    393         self._BuildChangeList()
    394         self._DisplayHowManyConflictsExist()
    395 
    396     def UpdateUI(self):
    397         start = time.time()
    398         self.bottomLeft.tree.BuildTree(self.mergedDict)
    399         if self.uiBase != {}:
    400             self.bottomLeft.tree.ColorAddedItems(self.diffs['added'])
    401         self.bottomLeft.tree.ColorChangedItems(self.diffs['changed'])
    402         if self.conflicts:
    403             self.bottomLeft.tree.ColorLocationsInTree(self.conflicts)
    404         self._BuildConflictLists()
    405         self._LogTime('Updating the UI took: ', start)
    406 
    407     def _PartsOfListsAreEqual(self, conflict, location):
    408         for index, key in enumerate(conflict):
    409             if str(key) != str(location[index]):
    410                 if index > 0:
    411                     return True
    412                 return False
    413 
    414         return True
    415 
    416     def FindSimilarLists(self, comparingList, searchList):
    417         itemsFound = []
    418         for item in searchList:
    419             if self._PartsOfListsAreEqual(item, comparingList):
    420                 itemsFound.append(item)
    421 
    422         return itemsFound
    423 
    424     def _ColorItemsInPathChanged(self, location):
    425         self.bottomLeft.tree.ClearPathToItem(location)
    426         addsFound = self.FindSimilarLists(location, self.diffs['added'])
    427         self.bottomLeft.tree.ColorAddedItems(addsFound)
    428         changesFound = self.FindSimilarLists(location, self.diffs['changed'])
    429         self.bottomLeft.tree.ColorChangedItems(changesFound)
    430         conflictsFound = self.FindSimilarLists(location, self.conflicts)
    431         self.bottomLeft.tree.ColorLocationsInTree(conflictsFound)
    432         self._BuildConflictLists()
    433 
    434     def UpdateUIForSingleObject(self, location):
    435         dataToInsert = self.merger.GetObjectAtLocation(self.mergedDict, location)
    436         self.bottomLeft.tree.UpdateSingleItem(dataToInsert)
    437         self._ColorItemsInPathChanged(location)
    438         self._ClearChangeTabDiffTrees()
    439         self._ClearConflictTabDiffTrees()
    440 
    441     def _BuildChangeList(self):
    442         self.bottomRight.changeList.clear()
    443         for keyList in self.diffs['changed']:
    444             item = QtGui.QListWidgetItem(','.join([ str(x) for x in keyList ]))
    445             self.bottomRight.changeList.addItem(item)
    446 
    447         self._DisplayHowManyChangesExist()
    448 
    449     def _BuildConflictList(self):
    450         self.bottomRight.conflictList.clear()
    451         for conflictKeys in self.conflicts:
    452             item = QtGui.QListWidgetItem(','.join([ str(x) for x in conflictKeys ]))
    453             self.bottomRight.conflictList.addItem(item)
    454 
    455     def _ColorConflictsInLists(self, diffTreeMerger, conflictBeingChecked, conflicts, otherConflict):
    456         conflictsToAdd = []
    457         for conflict in conflicts:
    458             lastItem = diffTreeMerger.GetObjectAtLocation(conflictBeingChecked, conflict)
    459             lastItemOther = diffTreeMerger.GetObjectAtLocation(otherConflict, conflict)
    460             if type(lastItem) is list and type(lastItemOther) is list:
    461                 for index, data in enumerate(lastItem):
    462                     if len(lastItemOther) > index:
    463                         if data != lastItemOther[index]:
    464                             conflictsToAdd.append(conflict + [index])
    465 
    466         return conflictsToAdd
    467 
    468     def _ColorConflictsInDiffTree(self, conflictBeingChecked, otherConflict, originTree):
    469         diffTreeMerger = FsdDiffMerger({}, conflictBeingChecked, otherConflict)
    470         diffTreeMerger.PerformConflictSearch()
    471         conflicts = diffTreeMerger.GetConflicts()
    472         if conflicts:
    473             conflictsToAdd = self._ColorConflictsInLists(diffTreeMerger, conflictBeingChecked, conflicts, otherConflict)
    474             originTree.ColorLocationsInTree(conflicts + conflictsToAdd)
    475             return conflicts + conflictsToAdd
    476         return []
    477 
    478     def _AddTooltipDiffs(self, baseConflict, conflicts, otherConflict, originTree):
    479         diffLocationsWithBaseData = []
    480         for location in conflicts:
    481             if baseConflict is None:
    482                 data = self.merger.GetObjectAtLocation(otherConflict, location)
    483             else:
    484                 data = self.merger.GetObjectAtLocation(baseConflict, location)
    485             diffLocationsWithBaseData.append((location, data))
    486 
    487         originTree.AddDiffTooltipToLocations(diffLocationsWithBaseData)
    488 
    489     def _ColorDiffTreeOfConflict(self, currentConflict, otherConflict, baseConflict, originTree):
    490         if type(currentConflict) in (list, dict):
    491             conflicts = []
    492             if baseConflict is None:
    493                 conflicts += self._ColorConflictsInDiffTree(currentConflict, otherConflict, originTree)
    494             elif baseConflict is not None:
    495                 conflicts += self._ColorConflictsInDiffTree(currentConflict, baseConflict, originTree)
    496             self._AddTooltipDiffs(baseConflict, conflicts, otherConflict, originTree)
    497         elif baseConflict is None:
    498             originTree.AddDiffTooltipToLocations([([], otherConflict)])
    499         else:
    500             originTree.AddDiffTooltipToLocations([([], baseConflict)])
    501 
    502     def _ColorDiffTreesIfThereAreConflicts(self, baseConflict, myConflict, theirConflict):
    503         self._ColorDiffTreeOfConflict(myConflict, theirConflict, baseConflict, self.bottomRight.myConflictTree)
    504         self._ColorDiffTreeOfConflict(theirConflict, myConflict, baseConflict, self.bottomRight.theirConflictTree)
    505         if type(baseConflict) in (list, dict):
    506             if myConflict is None:
    507                 self._ColorConflictsInDiffTree(baseConflict, theirConflict, self.bottomRight.baseConflictTree)
    508             if theirConflict is None:
    509                 self._ColorConflictsInDiffTree(baseConflict, myConflict, self.bottomRight.baseConflictTree)
    510 
    511     def _DisplayDiffInBaseIfItExists(self, baseConflict, columnWidth = -1):
    512         if baseConflict is not None:
    513             self.bottomRight.baseConflictTree.BuildTree(baseConflict, columnWidth=columnWidth)
    514         else:
    515             self.bottomRight.baseConflictTree.BuildTree(DOES_NOT_EXIST_STRING, columnWidth=columnWidth)
    516 
    517     def _DisplayDiffTreesForConflictTab(self, conflictLocation):
    518         self.bottomRight.currentConflict = conflictLocation
    519         self.bottomRight.tabs.setCurrentPage(0)
    520         myConflict = self.merger.GetObjectAtLocation(self.uiMine, conflictLocation)
    521         baseConflict = self.merger.GetObjectAtLocation(self.uiBase, conflictLocation)
    522         theirConflict = self.merger.GetObjectAtLocation(self.uiTheirs, conflictLocation)
    523         columnWidth = int(0.8 * (self.bottomRight.width() / 3))
    524         self.bottomRight.myConflictTree.BuildTree(myConflict, columnWidth=columnWidth)
    525         self._DisplayDiffInBaseIfItExists(baseConflict, columnWidth=columnWidth)
    526         self.bottomRight.theirConflictTree.BuildTree(theirConflict, columnWidth=columnWidth)
    527         self._ColorDiffTreesIfThereAreConflicts(baseConflict, myConflict, theirConflict)
    528         self._ShowSolveConflictButtons()
    529 
    530     def _DisplayDiffTreesForChangeTab(self, location):
    531         self.bottomRight.tabs.setCurrentPage(1)
    532         currentData = self.merger.GetObjectAtLocation(self.mergedDict, location)
    533         baseData = self.merger.GetObjectAtLocation(self.uiBase, location)
    534         if currentData == self.merger.GetObjectAtLocation(self.uiMine, location):
    535             self.bottomRight.currentDataLabel.setText('Mine:')
    536         else:
    537             self.bottomRight.currentDataLabel.setText('Theirs:')
    538         columnWidth = int(0.8 * (self.bottomRight.width() / 3))
    539         self.bottomRight.currentDataTree.BuildTree(currentData, columnWidth=columnWidth)
    540         self.bottomRight.baseDataTree.BuildTree(baseData, columnWidth=columnWidth)
    541         merger = FsdDiffMerger({}, currentData, baseData)
    542         merger.PerformDifferenceCheck(seeAddsInLists=True)
    543         diffs = merger.GetDifferences()
    544         self.bottomRight.currentDataTree.ColorAddedItems(diffs['added'])
    545         self.bottomRight.currentDataTree.ColorLocationsInTree([], baseData)
    546         self.bottomRight.currentDataTree.AddDiffTooltipToLocations([([], baseData)])
    547 
    548     def _GetChangeByLocation(self, location):
    549         locationAsStrList = [ str(x) for x in location ]
    550         for keyList in self.diffs['changed']:
    551             if AreEqual([ str(x) for x in keyList ], locationAsStrList):
    552                 return keyList
    553 
    554     def TryDisplayChangeByLocation(self, location):
    555         changeLocation = self._GetChangeByLocation(location)
    556         if changeLocation is not None:
    557             self.bottomLeft.tree.ExpandAndMoveTo(changeLocation)
    558             self._DisplayDiffTreesForChangeTab(changeLocation)
    559         else:
    560             self._ClearChangeTabDiffTrees()
    561 
    562     def _ChangeSelectedFromList(self, item):
    563         changeLocation = item.data(0).split(',')
    564         self.TryDisplayChangeByLocation(changeLocation)
    565         self._ClearConflictTabDiffTrees()
    566 
    567     def _ConflictSelectedFromList(self, item):
    568         conflictLocation = self._GetConflictIfConflicted(item.data(0).split(','))
    569         self.bottomLeft.tree.ExpandAndMoveTo(conflictLocation)
    570         self._DisplayDiffTreesForConflictTab(conflictLocation)
    571         self._ClearChangeTabDiffTrees()
    572 
    573     def _ItemSelectedFromResultTree(self, index):
    574         item = self.bottomLeft.tree.model.itemFromIndex(index)
    575         if item is not None:
    576             keyList = self._GetKeyPathToTreeItem(item)
    577             conflictLocation = self._GetConflictIfConflicted(keyList)
    578             if conflictLocation is not None:
    579                 self._DisplayDiffTreesForConflictTab(conflictLocation)
    580                 self._ClearChangeTabDiffTrees()
    581             else:
    582                 self._ClearConflictTabDiffTrees()
    583                 self.TryDisplayChangeByLocation(keyList)
    584 
    585     def _FindIndexOfConflict(self, conflictLocation):
    586         conflictAsStr = [ str(x) for x in conflictLocation ]
    587         for index, conflictKeys in enumerate(self.conflicts):
    588             currentConflictKeysAsStr = [ str(x) for x in conflictKeys ]
    589             if AreEqual(currentConflictKeysAsStr, conflictAsStr):
    590                 return index
    591 
    592         return -1
    593 
    594     def _GetConflictIfConflicted(self, keys):
    595         index = self._FindIndexOfConflict(keys)
    596         if index != -1:
    597             return self.conflicts[index]
    598 
    599     def _GetKeyPathToTreeItem(self, item):
    600         dataList = []
    601         traverseItem = item
    602         while traverseItem is not None:
    603             data = traverseItem.data(0).split(':')
    604             itemValue = unicodedata.normalize('NFKD', data[0]).encode('ascii', 'ignore')
    605             dataList.append(itemValue)
    606             traverseItem = traverseItem.parent()
    607 
    608         return [ x for x in reversed(dataList) ]
    609 
    610     def _SpawnConflictMenu(self, conflict, pos, origin):
    611         action = self.conflictMenu.exec_(origin.mapToGlobal(pos))
    612         if action == self.mergeMineAction:
    613             self.SolveConflict(conflict, 0)
    614         elif action == self.mergeBaseAction:
    615             self.SolveConflict(conflict, 1)
    616         elif action == self.mergeTheirsAction:
    617             self.SolveConflict(conflict, 2)
    618 
    619     def _SpawnChangeMenu(self, location, pos, origin):
    620         action = self.changeMenu.exec_(origin.mapToGlobal(pos))
    621         if action == self.revertBaseAction:
    622             self.RevertChange(location)
    623         elif action == self.markAcceptedAction:
    624             self.KeepChange(location)
    625 
    626     def _GetLocationAndSolveConflictBy(self, conflictLocation, version):
    627         self.SolveConflict(conflictLocation, version)
    628 
    629     def _UseMineButtonClicked(self):
    630         if self.bottomRight.currentConflict is not None:
    631             self._GetLocationAndSolveConflictBy(self.bottomRight.currentConflict, 0)
    632 
    633     def _UseBaseButtonClicked(self):
    634         if self.bottomRight.currentConflict is not None:
    635             self._GetLocationAndSolveConflictBy(self.bottomRight.currentConflict, 1)
    636 
    637     def _UseTheirsButtonClicked(self):
    638         if self.bottomRight.currentConflict is not None:
    639             self._GetLocationAndSolveConflictBy(self.bottomRight.currentConflict, 2)
    640 
    641     def _RightClickOnResultTree(self, pos):
    642         index = self.bottomLeft.tree.GetWidget().indexAt(pos)
    643         item = self.bottomLeft.tree.model.itemFromIndex(index)
    644         if item is not None:
    645             keyList = self._GetKeyPathToTreeItem(item)
    646             conflictLocation = self._GetConflictIfConflicted(keyList)
    647             if conflictLocation is not None:
    648                 self._SpawnConflictMenu(conflictLocation, pos, self.bottomLeft.tree.GetWidget())
    649             else:
    650                 changeLocation = self._GetChangeByLocation(keyList)
    651                 if changeLocation is not None:
    652                     self._SpawnChangeMenu(changeLocation, pos, self.bottomLeft.tree.GetWidget())
    653 
    654     def _RightClickOnConflictList(self, pos):
    655         item = self.bottomRight.conflictList.indexAt(pos)
    656         if item.data(0) is not None:
    657             conflictLocation = self._GetConflictIfConflicted(item.data(0).split(','))
    658             if conflictLocation is not None:
    659                 self._SpawnConflictMenu(conflictLocation, pos, self.bottomRight.conflictList)
    660 
    661     def _RightClickOnChangeList(self, pos):
    662         item = self.bottomRight.changeList.indexAt(pos)
    663         if item.data(0) is not None:
    664             changeLocation = self._GetChangeByLocation(item.data(0).split(','))
    665             if changeLocation is not None:
    666                 self._SpawnChangeMenu(changeLocation, pos, self.bottomRight.changeList)
    667 
    668     def _ClearStatusBar(self):
    669         self.bottomLeft.statusBar.showMessage('')
    670 
    671     def _DisplayStatusBarMessage(self, message, waitSeconds):
    672         self.bottomLeft.statusBar.showMessage(message)
    673         CountDownThread(waitSeconds, self._ClearStatusBar).start()
    674 
    675     def _SaveResultToFile(self):
    676         with open(self.resultFile, 'w') as f:
    677             yaml.dump(self.mergedDict, f, allow_unicode=True, Dumper=FsdYamlDumper, default_flow_style=False)
    678             self._DisplayStatusBarMessage('Saved successfully', 3)
    679 
    680     def _ResultFileStringIsEmpty(self):
    681         return self.resultFile == u''
    682 
    683     def LoadFileToMine(self):
    684         self.LoadFile(self.top.mine)
    685 
    686     def LoadFileToBase(self):
    687         self.LoadFile(self.top.base)
    688 
    689     def LoadFileToTheirs(self):
    690         self.LoadFile(self.top.theirs)
    691 
    692     def LoadFile(self, target):
    693         chosenFile = QtGui.QFileDialog.getOpenFileName(self)[0]
    694         if chosenFile != u'':
    695             self.LoadArgument(chosenFile, target)
    696 
    697     def Save(self):
    698         if self.resultFile is not None:
    699             if self._ResultFileStringIsEmpty():
    700                 self.resultFile = None
    701             else:
    702                 if len(self.conflicts) != 0:
    703                     message = 'There are still conflicts to be resolved, do you wish to save?'
    704                     reply = QtGui.QMessageBox.question(self, 'Message', message, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
    705                     if reply != QtGui.QMessageBox.Yes:
    706                         return
    707                 self._SaveResultToFile()
    708         elif self.resultFile is None:
    709             self.resultFile = QtGui.QFileDialog.getSaveFileName(self)[0]
    710             self.Save()
    711 
    712 
    713 class UndoData(object):
    714 
    715     def __init__(self, location, data, originType):
    716         self.location = location
    717         self.data = data
    718         self.originType = 0 if originType == 'conflict' else 1
    719 
    720     def IsConflict(self):
    721         return self.originType == 0
    722 
    723     def IsChange(self):
    724         return self.originType == 1
    725 
    726 
    727 class CountDownThread(threading.Thread):
    728 
    729     def __init__(self, durationInSeconds, endOfDurationCallback):
    730         threading.Thread.__init__(self)
    731         self.durationInSeconds = durationInSeconds
    732         self.callBack = endOfDurationCallback
    733         self.startTime = time.time()
    734 
    735     def run(self):
    736         while time.time() - self.startTime < self.durationInSeconds:
    737             pass
    738 
    739         self.callBack()
    740 
    741 
    742 APP_STYLE = '\nQToolTip {\n     border: 1px solid black;\n     background-color: #222;\n     border-radius: 3px;\n     color: #fff;\n     opacity: 100;\n}\n\nQWidget {\n    color: #b1b1b1;\n    background-color: #323232;\n}\n\nQWidget:pressed {\n    border: 1px solid #d7801a;\n    background: none;\n}\n\nQWidget:item:hover {\n    font-weight: bold;\n}\n\nQMenuBar::item {\n    background: transparent;\n}\n\nQMenuBar::item:selected {\n    background: transparent;\n    border: 1px solid #ffaa00;\n}\n\nQMenuBar::item:pressed {\n    background: #444;\n    border: 1px solid #000;\n    background-color: QLinearGradient(\n        x1:0, y1:0,\n        x2:0, y2:1,\n        stop:1 #212121,\n        stop:0.4 #343434/*,\n        stop:0.2 #343434,\n        stop:0.1 #ffaa00*/\n    );\n    margin-bottom:-1px;\n    padding-bottom:1px;\n}\n\nQMenu {\n    border: 1px solid #000;\n}\n\nQMenu::item {\n    padding: 2px 20px 2px 20px;\n}\n\nQMenu::item:selected {\n\n}\n\nQWidget:disabled {\n    color: #404040;\n    background-color: #323232;\n}\n\nQAbstractItemView {\n    background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #4d4d4d, stop: 0.1 #646464, stop: 1 #5d5d5d);\n}\n\nQWidget:focus {\n    /*border: 2px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);*/\n}\n\nQLineEdit {\n    background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #4d4d4d, stop: 0 #646464, stop: 1 #5d5d5d);\n    padding: 1px;\n    border-style: solid;\n    border: 1px solid #1e1e1e;\n    border-radius: 5;\n}\n\nQPushButton {\n    color: #b1b1b1;\n    background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #565656, stop: 0.1 #525252, stop: 0.5 #4e4e4e, stop: 0.9 #4a4a4a, stop: 1 #464646);\n    border-width: 1px;\n    border-color: #1e1e1e;\n    border-style: solid;\n    border-radius: 6;\n    padding: 3px;\n    font-size: 12px;\n    padding-left: 5px;\n    padding-right: 5px;\n}\n\nQPushButton:pressed {\n    background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2d2d2d, stop: 0.1 #2b2b2b, stop: 0.5 #292929, stop: 0.9 #282828, stop: 1 #252525);\n}\n\nQComboBox {\n    selection-background-color: #ffaa00;\n    background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #565656, stop: 0.1 #525252, stop: 0.5 #4e4e4e, stop: 0.9 #4a4a4a, stop: 1 #464646);\n    border-style: solid;\n    border: 1px solid #1e1e1e;\n    border-radius: 5;\n}\n\nQComboBox:hover,QPushButton:hover {\n    border: 2px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);\n}\n\n\nQComboBox:on {\n    padding-top: 3px;\n    padding-left: 4px;\n    background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2d2d2d, stop: 0.1 #2b2b2b, stop: 0.5 #292929, stop: 0.9 #282828, stop: 1 #252525);\n    selection-background-color: #ffaa00;\n}\n\nQComboBox QAbstractItemView {\n    border: 2px solid darkgray;\n    selection-background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);\n}\n\nQComboBox::drop-down {\n     subcontrol-origin: padding;\n     subcontrol-position: top right;\n     width: 15px;\n\n     border-left-width: 0px;\n     border-left-color: darkgray;\n     border-left-style: solid; /* just a single line */\n     border-top-right-radius: 3px; /* same radius as the QComboBox */\n     border-bottom-right-radius: 3px;\n }\n\nQComboBox::down-arrow {\n     image: url(:/down_arrow.png);\n}\n\nQGroupBox:focus {\nborder: 2px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);\n}\n\nQTextEdit:focus {\n    border: 2px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);\n}\n\nQScrollBar:horizontal {\n     border: 1px solid #222222;\n     background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0.0 #121212, stop: 0.2 #282828, stop: 1 #484848);\n     height: 7px;\n     margin: 0px 16px 0 16px;\n}\n\nQScrollBar::handle:horizontal {\n      background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #ffa02f, stop: 0.5 #d7801a, stop: 1 #ffa02f);\n      min-height: 20px;\n      border-radius: 2px;\n}\n\nQScrollBar::add-line:horizontal {\n      border: 1px solid #1b1b19;\n      border-radius: 2px;\n      background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #ffa02f, stop: 1 #d7801a);\n      width: 14px;\n      subcontrol-position: right;\n      subcontrol-origin: margin;\n}\n\nQScrollBar::sub-line:horizontal {\n      border: 1px solid #1b1b19;\n      border-radius: 2px;\n      background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #ffa02f, stop: 1 #d7801a);\n      width: 14px;\n     subcontrol-position: left;\n     subcontrol-origin: margin;\n}\n\nQScrollBar::right-arrow:horizontal, QScrollBar::left-arrow:horizontal {\n      border: 1px solid black;\n      width: 1px;\n      height: 1px;\n      background: white;\n}\n\nQScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {\n      background: none;\n}\n\nQScrollBar:vertical {\n      background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0.0 #121212, stop: 0.2 #282828, stop: 1 #484848);\n      width: 7px;\n      margin: 16px 0 16px 0;\n      border: 1px solid #222222;\n}\n\nQScrollBar::handle:vertical {\n      background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 0.5 #d7801a, stop: 1 #ffa02f);\n      min-height: 20px;\n      border-radius: 2px;\n}\n\nQScrollBar::add-line:vertical {\n      border: 1px solid #1b1b19;\n      border-radius: 2px;\n      background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);\n      height: 14px;\n      subcontrol-position: bottom;\n      subcontrol-origin: margin;\n}\n\nQScrollBar::sub-line:vertical {\n      border: 1px solid #1b1b19;\n      border-radius: 2px;\n      background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #d7801a, stop: 1 #ffa02f);\n      height: 14px;\n      subcontrol-position: top;\n      subcontrol-origin: margin;\n}\n\nQScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {\n      border: 1px solid black;\n      width: 1px;\n      height: 1px;\n      background: white;\n}\n\n\nQScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {\n      background: none;\n}\n\nQTextEdit {\n    background-color: #242424;\n}\n\nQPlainTextEdit {\n    background-color: #242424;\n}\n\nQHeaderView::section {\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #616161, stop: 0.5 #505050, stop: 0.6 #434343, stop:1 #656565);\n    color: white;\n    padding-left: 4px;\n    border: 1px solid #6c6c6c;\n}\n\nQCheckBox:disabled {\ncolor: #414141;\n}\n\nQDockWidget::title {\n    text-align: center;\n    spacing: 3px; /* spacing between items in the tool bar */\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #323232, stop: 0.5 #242424, stop:1 #323232);\n}\n\nQDockWidget::close-button, QDockWidget::float-button {\n    text-align: center;\n    spacing: 1px; /* spacing between items in the tool bar */\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #323232, stop: 0.5 #242424, stop:1 #323232);\n}\n\nQDockWidget::close-button:hover, QDockWidget::float-button:hover {\n    background: #242424;\n}\n\nQDockWidget::close-button:pressed, QDockWidget::float-button:pressed {\n    padding: 1px -1px -1px 1px;\n}\n\nQMainWindow::separator {\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #161616, stop: 0.5 #151515, stop: 0.6 #212121, stop:1 #343434);\n    color: white;\n    padding-left: 4px;\n    border: 1px solid #4c4c4c;\n    spacing: 3px; /* spacing between items in the tool bar */\n}\n\nQMainWindow::separator:hover {\n\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #d7801a, stop:0.5 #b56c17 stop:1 #ffa02f);\n    color: white;\n    padding-left: 4px;\n    border: 1px solid #6c6c6c;\n    spacing: 3px; /* spacing between items in the tool bar */\n}\n\nQToolBar::handle {\n     spacing: 3px; /* spacing between items in the tool bar */\n     background: url(:/images/handle.png);\n}\n\nQMenu::separator {\n    height: 2px;\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #161616, stop: 0.5 #151515, stop: 0.6 #212121, stop:1 #343434);\n    color: white;\n    padding-left: 4px;\n    margin-left: 10px;\n    margin-right: 5px;\n}\n\nQProgressBar {\n    border: 2px solid grey;\n    border-radius: 5px;\n    text-align: center;\n}\n\nQProgressBar::chunk {\n    background-color: #d7801a;\n    width: 2.15px;\n    margin: 0.5px;\n}\n\nQTabBar::tab {\n    color: #b1b1b1;\n    border: 1px solid #444;\n    border-bottom-style: none;\n    background-color: #323232;\n    padding-left: 10px;\n    padding-right: 10px;\n    padding-top: 3px;\n    padding-bottom: 2px;\n    margin-right: -1px;\n}\n\nQTabWidget::pane {\n    border: 1px solid #444;\n    top: 1px;\n}\n\nQTabBar::tab:last {\n    margin-right: 0; /* the last selected tab has nothing to overlap with on the right */\n    border-top-right-radius: 3px;\n}\n\nQTabBar::tab:first:!selected {\n margin-left: 0px; /* the last selected tab has nothing to overlap with on the right */\n\n\n    border-top-left-radius: 3px;\n}\n\nQTabBar::tab:!selected {\n    color: #b1b1b1;\n    border-bottom-style: solid;\n    margin-top: 3px;\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434);\n}\n\nQTabBar::tab:selected {\n    border-top-left-radius: 3px;\n    border-top-right-radius: 3px;\n    margin-bottom: 0px;\n}\n\nQTabBar::tab:!selected:hover {\n    /*border-top: 2px solid #ffaa00;\n    padding-bottom: 3px;*/\n    border-top-left-radius: 3px;\n    border-top-right-radius: 3px;\n    background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:0.4 #343434, stop:0.2 #343434, stop:0.1 #ffaa00);\n}\n\nQRadioButton::indicator:checked, QRadioButton::indicator:unchecked {\n    color: #b1b1b1;\n    background-color: #323232;\n    border: 1px solid #b1b1b1;\n    border-radius: 6px;\n}\n\nQRadioButton::indicator:checked {\n    background-color: qradialgradient(\n        cx: 0.5, cy: 0.5,\n        fx: 0.5, fy: 0.5,\n        radius: 1.0,\n        stop: 0.25 #ffaa00,\n        stop: 0.3 #323232\n    );\n}\n\nQCheckBox::indicator {\n    color: #b1b1b1;\n    background-color: #323232;\n    border: 1px solid #b1b1b1;\n    width: 9px;\n    height: 9px;\n}\n\nQRadioButton::indicator{\n    border-radius: 6px;\n}\n\nQRadioButton::indicator:hover, QCheckBox::indicator:hover{\n    border: 1px solid #ffaa00;\n}\n\nQCheckBox::indicator:checked{\n    image:url(:/images/checkbox.png);\n}\n\nQCheckBox::indicator:disabled, QRadioButton::indicator:disabled{\n    border: 1px solid #444;\n}\n\nQTreeView {\n\n}\nQTreeView::item {\n    background: none;\n}\nQTreeView::item:selected {\n    background-color: transparent;\n}\nQTreeView::item:selected:active {\n    background-color: transparent;\n    border: none;\n}\nQTreeView::item:focus {\n    border: none;\n    background-color: transparent;\n}\nQTreeView::branch {\n    background: none;\n}\nQListWidget::item:selected {\n    background-color: transparent;\n}\nQListWidget::item:selected:active {\n    background-color: transparent;\n}\nQWidget::item:selected {\n    background: none;\n}\n\n'
    743 
    744 def main():
    745     app = QtGui.QApplication(sys.argv)
    746     ex = FsdDiffMergeUI(sys.argv[1:])
    747     sys.exit(app.exec_())
    748 
    749 
    750 if __name__ == '__main__':
    751     main()