mirror of
				https://github.com/arsenetar/dupeguru.git
				synced 2025-09-11 17:58:17 +00:00 
			
		
		
		
	Add image comparison features to details dialog
* Add splitter in order to hide the details table. * Add a toolbar to the Details Dialog window to allow for better image comparisons: zoom in/out, swap pixmaps in place, best-fit-to-viewport. Scrollbars and viewports are synchronized.
This commit is contained in:
		
							parent
							
								
									092cf1471b
								
							
						
					
					
						commit
						4ee9479a5f
					
				
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -22,4 +22,6 @@ cocoa/autogen | ||||
| 
 | ||||
| *.pyd | ||||
| *.exe | ||||
| *.spec | ||||
| *.spec | ||||
| 
 | ||||
| .vscode | ||||
|  | ||||
| @ -7,12 +7,12 @@ | ||||
| # http://www.gnu.org/licenses/gpl-3.0.html | ||||
| 
 | ||||
| from PyQt5.QtCore import Qt | ||||
| from PyQt5.QtWidgets import QDialog | ||||
| from PyQt5.QtWidgets import QMainWindow | ||||
| 
 | ||||
| from .details_table import DetailsModel | ||||
| 
 | ||||
| 
 | ||||
| class DetailsDialog(QDialog): | ||||
| class DetailsDialog(QMainWindow): | ||||
|     def __init__(self, parent, app, **kwargs): | ||||
|         super().__init__(parent, Qt.Tool, **kwargs) | ||||
|         self.app = app | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
| # http://www.gnu.org/licenses/gpl-3.0.html | ||||
| 
 | ||||
| from PyQt5.QtCore import Qt, QAbstractTableModel | ||||
| from PyQt5.QtWidgets import QHeaderView, QTableView | ||||
| from PyQt5.QtWidgets import QHeaderView, QTableView, QAbstractItemView | ||||
| 
 | ||||
| from hscommon.trans import trget | ||||
| 
 | ||||
| @ -51,9 +51,11 @@ class DetailsTable(QTableView): | ||||
|         QTableView.__init__(self, *args) | ||||
|         self.setAlternatingRowColors(True) | ||||
|         self.setSelectionBehavior(QTableView.SelectRows) | ||||
|         self.setSelectionMode(QTableView.SingleSelection) | ||||
|         self.setShowGrid(False) | ||||
|         self.setWordWrap(False) | ||||
| 
 | ||||
| 
 | ||||
|     def setModel(self, model): | ||||
|         QTableView.setModel(self, model) | ||||
|         # The model needs to be set to set header stuff | ||||
| @ -61,7 +63,7 @@ class DetailsTable(QTableView): | ||||
|         hheader.setHighlightSections(False) | ||||
|         hheader.setStretchLastSection(False) | ||||
|         hheader.resizeSection(0, 100) | ||||
|         hheader.setSectionResizeMode(0, QHeaderView.Fixed) | ||||
|         hheader.setSectionResizeMode(0, QHeaderView.Interactive) | ||||
|         hheader.setSectionResizeMode(1, QHeaderView.Stretch) | ||||
|         hheader.setSectionResizeMode(2, QHeaderView.Stretch) | ||||
|         vheader = self.verticalHeader() | ||||
|  | ||||
| @ -5,109 +5,119 @@ | ||||
| # http://www.gnu.org/licenses/gpl-3.0.html | ||||
| 
 | ||||
| from PyQt5.QtCore import Qt, QSize | ||||
| from PyQt5.QtGui import QPixmap | ||||
| from PyQt5.QtWidgets import ( | ||||
|     QVBoxLayout, | ||||
|     QAbstractItemView, | ||||
|     QHBoxLayout, | ||||
|     QLabel, | ||||
|     QSizePolicy, | ||||
| ) | ||||
|     QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame) | ||||
| 
 | ||||
| from hscommon.trans import trget | ||||
| from ..details_dialog import DetailsDialog as DetailsDialogBase | ||||
| from ..details_table import DetailsTable | ||||
| 
 | ||||
| from .image_viewer import ( | ||||
|     ViewerToolBar, ScrollAreaImageViewer, ScrollAreaController) | ||||
| tr = trget("ui") | ||||
| 
 | ||||
| 
 | ||||
| class DetailsDialog(DetailsDialogBase): | ||||
|     def __init__(self, parent, app): | ||||
|         DetailsDialogBase.__init__(self, parent, app) | ||||
|         self.selectedPixmap = None | ||||
|         self.referencePixmap = None | ||||
|         self.vController = None | ||||
|         super().__init__(parent, app) | ||||
| 
 | ||||
|     def _setupUi(self): | ||||
|         self.setWindowTitle(tr("Details")) | ||||
|         self.resize(502, 295) | ||||
|         self.resize(502, 502) | ||||
|         self.setMinimumSize(QSize(250, 250)) | ||||
|         self.verticalLayout = QVBoxLayout(self) | ||||
|         self.verticalLayout.setSpacing(0) | ||||
|         self.verticalLayout.setContentsMargins(0, 0, 0, 0) | ||||
|         self.horizontalLayout = QHBoxLayout() | ||||
|         self.horizontalLayout.setSpacing(4) | ||||
|         self.selectedImage = QLabel(self) | ||||
|         sizePolicy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.selectedImage.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         self.selectedImage.setSizePolicy(sizePolicy) | ||||
|         self.selectedImage.setScaledContents(False) | ||||
|         self.selectedImage.setAlignment(Qt.AlignCenter) | ||||
|         self.horizontalLayout.addWidget(self.selectedImage) | ||||
|         self.referenceImage = QLabel(self) | ||||
|         sizePolicy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.referenceImage.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         self.referenceImage.setSizePolicy(sizePolicy) | ||||
|         self.referenceImage.setAlignment(Qt.AlignCenter) | ||||
|         self.horizontalLayout.addWidget(self.referenceImage) | ||||
|         self.verticalLayout.addLayout(self.horizontalLayout) | ||||
|         self.splitter = QSplitter(Qt.Vertical, self) | ||||
|         self.setCentralWidget(self.splitter) | ||||
|         self.topFrame = QFrame() | ||||
|         self.topFrame.setFrameShape(QFrame.StyledPanel) | ||||
|         self.horizontalLayout = QGridLayout() | ||||
|         # Minimum width for the toolbar in the middle: | ||||
|         self.horizontalLayout.setColumnMinimumWidth(1, 10) | ||||
|         self.horizontalLayout.setContentsMargins(0, 0, 0, 0) | ||||
|         self.horizontalLayout.setColumnStretch(0, 32) | ||||
|         # Smaller value for the toolbar in the middle to avoid excessive resize | ||||
|         self.horizontalLayout.setColumnStretch(1, 2) | ||||
|         self.horizontalLayout.setColumnStretch(2, 32) | ||||
|         # This avoids toolbar getting incorrectly partially hidden when window resizes | ||||
|         self.horizontalLayout.setRowStretch(0, 1) | ||||
|         self.horizontalLayout.setRowStretch(1, 24) | ||||
|         self.horizontalLayout.setRowStretch(2, 1) | ||||
|         self.horizontalLayout.setSpacing(1)  # probably not important | ||||
| 
 | ||||
|         self.selectedImageViewer = ScrollAreaImageViewer(self, "selectedImage") | ||||
|         self.horizontalLayout.addWidget(self.selectedImageViewer, 0, 0, 3, 1) | ||||
|         # Use a specific type of controller depending on the underlying viewer type | ||||
|         self.vController = ScrollAreaController(self) | ||||
| 
 | ||||
|         self.verticalToolBar = ViewerToolBar(self, self.vController) | ||||
|         self.verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical)) | ||||
|         self.horizontalLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter) | ||||
| 
 | ||||
|         self.referenceImageViewer = ScrollAreaImageViewer(self, "referenceImage") | ||||
|         self.horizontalLayout.addWidget(self.referenceImageViewer, 0, 2, 3, 1) | ||||
|         self.topFrame.setLayout(self.horizontalLayout) | ||||
|         self.splitter.addWidget(self.topFrame) | ||||
|         self.splitter.setStretchFactor(0, 8) | ||||
| 
 | ||||
|         self.tableView = DetailsTable(self) | ||||
|         sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) | ||||
|         sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth()) | ||||
|         self.tableView.setSizePolicy(sizePolicy) | ||||
|         self.tableView.setMinimumSize(QSize(0, 188)) | ||||
|         self.tableView.setMaximumSize(QSize(16777215, 190)) | ||||
|         # self.tableView.setMinimumSize(QSize(0, 190)) | ||||
|         # self.tableView.setMaximumSize(QSize(16777215, 190)) | ||||
|         self.tableView.setAlternatingRowColors(True) | ||||
|         self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) | ||||
|         self.tableView.setShowGrid(False) | ||||
|         self.verticalLayout.addWidget(self.tableView) | ||||
|         self.splitter.addWidget(self.tableView) | ||||
|         self.splitter.setStretchFactor(1, 1) | ||||
|         # Late population needed here for connections to the toolbar | ||||
|         self.vController.setupViewers( | ||||
|             self.selectedImageViewer, self.referenceImageViewer) | ||||
| 
 | ||||
|     def _update(self): | ||||
|         if self.vController is None:  # Not yet constructed! | ||||
|             return | ||||
|         if not self.app.model.selected_dupes: | ||||
|             # No item from the model, disable and clear everything. | ||||
|             self.vController.resetViewersState() | ||||
|             return | ||||
|         dupe = self.app.model.selected_dupes[0] | ||||
|         group = self.app.model.results.get_group_of_duplicate(dupe) | ||||
|         ref = group.ref | ||||
| 
 | ||||
|         self.selectedPixmap = QPixmap(str(dupe.path)) | ||||
|         if ref is dupe: | ||||
|             self.referencePixmap = None | ||||
|         else: | ||||
|             self.referencePixmap = QPixmap(str(ref.path)) | ||||
|         self._updateImages() | ||||
| 
 | ||||
|     def _updateImages(self): | ||||
|         if self.selectedPixmap is not None: | ||||
|             target_size = self.selectedImage.size() | ||||
|             scaledPixmap = self.selectedPixmap.scaled( | ||||
|                 target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation | ||||
|             ) | ||||
|             self.selectedImage.setPixmap(scaledPixmap) | ||||
|         else: | ||||
|             self.selectedImage.setPixmap(QPixmap()) | ||||
|         if self.referencePixmap is not None: | ||||
|             target_size = self.referenceImage.size() | ||||
|             scaledPixmap = self.referencePixmap.scaled( | ||||
|                 target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation | ||||
|             ) | ||||
|             self.referenceImage.setPixmap(scaledPixmap) | ||||
|         else: | ||||
|             self.referenceImage.setPixmap(QPixmap()) | ||||
|         self.vController.updateView(ref, dupe, group) | ||||
| 
 | ||||
|     # --- Override | ||||
|     def resizeEvent(self, event): | ||||
|         self._updateImages() | ||||
|         # HACK referenceViewer might be 1 pixel shorter in width | ||||
|         # due to the toolbar in the middle keeping the same width, | ||||
|         # so resizing in the GridLayout's engine leads to not enough space | ||||
|         # left for the panel on the right. | ||||
|         # This ensures same size while shrinking at least: | ||||
|         self.horizontalLayout.setColumnMinimumWidth( | ||||
|             0, self.selectedImageViewer.size().width()) | ||||
|         self.horizontalLayout.setColumnMinimumWidth( | ||||
|             2, self.selectedImageViewer.size().width()) | ||||
|         # This works when expanding but it's ugly: | ||||
|         if self.selectedImageViewer.size().width() > self.referenceImageViewer.size().width(): | ||||
|             # print(f"""Before selected size: {self.selectedImageViewer.size()}\n""", | ||||
|             #       f"""Before reference size: {self.referenceImageViewer.size()}""") | ||||
|             self.selectedImageViewer.resize(self.referenceImageViewer.size()) | ||||
|             # print(f"""After selected size: {self.selectedImageViewer.size()}\n""", | ||||
|             #       f"""After reference size: {self.referenceImageViewer.size()}""") | ||||
| 
 | ||||
|         if self.vController is None or not self.vController.bestFit: | ||||
|             return | ||||
|         # Only update the scaled down pixmaps | ||||
|         self.vController.updateBothImages() | ||||
| 
 | ||||
|     def show(self): | ||||
|         # Compute the maximum size the table view can reach | ||||
|         # Assuming all rows below headers have the same height | ||||
|         self.tableView.setMaximumHeight( | ||||
|             self.tableView.rowHeight(1) | ||||
|             * self.tableModel.model.row_count() | ||||
|             + self.tableView.verticalHeader().sectionSize(0)) | ||||
|         DetailsDialogBase.show(self) | ||||
|         self._update() | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										1271
									
								
								qt/pe/image_viewer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1271
									
								
								qt/pe/image_viewer.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user