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()