butterfly_viewer.aux_interfaces
User interface widgets and their supporting subwidgets for Butterfly Viewer.
Not intended as a script.
Interface widgets are:
SplitViewCreator, for users to add images in a 2x2 drag-and-drop zone from which to create a sliding overlay. SplitViewManager, for shortcut buttons to position and lock the position of the split in a sliding overlay. SlidersOpacitySplitViews, for sliders to the transparencies of a SplitView's overlayed images.
1#!/usr/bin/env python3 2 3"""User interface widgets and their supporting subwidgets for Butterfly Viewer. 4 5Not intended as a script. 6 7Interface widgets are: 8 SplitViewCreator, for users to add images in a 2x2 drag-and-drop zone from which to create a sliding overlay. 9 SplitViewManager, for shortcut buttons to position and lock the position of the split in a sliding overlay. 10 SlidersOpacitySplitViews, for sliders to the transparencies of a SplitView's overlayed images. 11""" 12# SPDX-License-Identifier: GPL-3.0-or-later 13 14 15 16import time 17 18from PyQt5 import QtWidgets, QtCore, QtGui 19 20from aux_dragdrop import FourDragDropImageLabel 21 22 23 24class FourDragDropImageLabelForSplitView(FourDragDropImageLabel): 25 """Extends a 2x2 drag-and-drop zone for SplitViewCreator. 26 27 Requires a base image (main; top-left) to be given before other images of SplitView may be added. 28 29 Instantiate without input: 30 self.drag_drop_area = FourDragDropImageLabelForSplitView() 31 """ 32 33 main_became_occupied = QtCore.pyqtSignal(bool) 34 35 def __init__(self): 36 super().__init__() 37 self.set_addable_all_except_main(False) 38 self.app_main_topleft.became_occupied.connect(self.on_main_topleft_occupied) 39 self.app_main_topleft.became_occupied.connect(self.main_became_occupied) 40 41 42 # Set style and function of drag-and-drop zones 43 44 def set_addable_all_except_main(self, boolean): 45 """Set all overlay images to be (or not to be) addable via drag-and-drop. 46 47 Convenience for the SplitViewCreator. 48 49 Args: 50 boolean (bool): True to make the overlay images addable; False to make un-addable. 51 """ 52 self.app_topright.set_addable(boolean) 53 self.app_bottomright.set_addable(boolean) 54 self.app_bottomleft.set_addable(boolean) 55 56 def on_main_topleft_occupied(self, boolean): 57 """Set when base image becomes occupied or unoccupied to set whether overlay images can be added. 58 59 Args: 60 boolean (bool): True to indicate base image is occupied (and thus overlay images may be added); 61 False to indicate main image is unoccupied (and thus overlay images may not be added).""" 62 self.set_addable_all_except_main(boolean) 63 64 65 66class SplitViewCreator(QtWidgets.QFrame): 67 """Interface for users to add images from which to create a SplitView. 68 69 Users can add local image files via drag-and-drop and "Select image..." dialogs. 70 71 Instantiate without input. See Butterfly Viewer for implementation. 72 """ 73 74 clicked_create_splitview_pushbutton = QtCore.pyqtSignal() 75 76 def __init__(self): 77 super().__init__() 78 79 main_layout = QtWidgets.QGridLayout() 80 81 self.drag_drop_area = FourDragDropImageLabelForSplitView() 82 self.drag_drop_area.will_start_loading.connect(self.display_loading_grayout) 83 self.drag_drop_area.has_stopped_loading.connect(self.display_loading_grayout) 84 85 self.buttons_layout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight) 86 self.create_splitview_pushbutton = QtWidgets.QPushButton("Create") 87 self.create_splitview_pushbutton.setToolTip("Create a sliding overlay window with these images") 88 self.create_splitview_pushbutton.setStyleSheet("QPushButton { font-size: 10pt; }") 89 self.create_splitview_pushbutton.clicked.connect(self.clicked_create_splitview_pushbutton) 90 91 self.create_splitview_pushbutton.setEnabled(False) 92 self.drag_drop_area.main_became_occupied.connect(self.create_splitview_pushbutton.setEnabled) 93 94 self.buttons_layout.addWidget(self.create_splitview_pushbutton) 95 96 self.loading_grayout_label = QtWidgets.QLabel("Loading...") 97 self.loading_grayout_label.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter) 98 self.loading_grayout_label.setVisible(False) 99 self.loading_grayout_label.setStyleSheet(""" 100 QLabel { 101 color: white; 102 font-size: 7.5pt; 103 background-color: rgba(0,0,0,223); 104 } 105 """) 106 107 self.title_label = QtWidgets.QLabel("Sliding overlay creator") 108 self.title_label.setAlignment(QtCore.Qt.AlignLeft) 109 self.title_label.setStyleSheet(""" 110 QLabel { 111 font-size: 10pt; 112 } 113 """) 114 115 main_layout.addWidget(self.title_label,0,0) 116 main_layout.addWidget(self.drag_drop_area, 1, 0) 117 main_layout.addLayout(self.buttons_layout, 2, 0) 118 main_layout.addWidget(self.loading_grayout_label, 0, 0, 3, 1) 119 main_layout.setAlignment(QtCore.Qt.AlignTop) 120 main_layout.setContentsMargins(4,4,4,4) 121 122 # self.setMinimumWidth(250) 123 # self.setMinimumHeight(325) 124 125 self.setLayout(main_layout) 126 self.setContentsMargins(2,2,2,2) # As docked on left side 127 128 self.setStyleSheet("QFrame {background: palette(window); border-radius: 0.5em;}") 129 130 def setMouseTracking(self, flag): 131 """PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.""" 132 def recursive_set(parent): 133 for child in parent.findChildren(QtCore.QObject): # Needed to track the split in sliding overlays while hovering over interfaces and other widgets; prevents sudden stops and jumps of the split 134 try: 135 child.setMouseTracking(flag) 136 except: 137 pass 138 recursive_set(child) 139 QtWidgets.QWidget.setMouseTracking(self, flag) 140 recursive_set(self) 141 142 def display_loading_grayout(self, boolean, text="Loading...", pseudo_load_time=0.2): 143 """Show/hide grayout screen for loading sequences. 144 145 Args: 146 boolean (bool): True to show grayout; False to hide. 147 text (str): The text to show on the grayout. 148 pseudo_load_time (float): The delay (in seconds) to hide the grayout to give users a feeling of action. 149 """ 150 # Needed to give feedback to user that images are loading 151 if not boolean: 152 text = "Loading..." 153 self.loading_grayout_label.setText(text) 154 self.loading_grayout_label.setVisible(boolean) 155 if boolean: 156 self.loading_grayout_label.repaint() 157 if not boolean: 158 time.sleep(pseudo_load_time) 159 160 161 162class SliderDeluxe(QtWidgets.QWidget): 163 """Custom slider for setting and indicating the transparencies of overlay images in SplitView. 164 165 Used in SlidersOpacitySplitViews. 166 167 Args: 168 name (str): Text for slider label. 169 pixmap_preview_position (str): The position of the preview icon for indicating opacity ("Full", "Top right", "Bottom right", "Bottom left"). 170 """ 171 172 was_changed_slider_value = QtCore.pyqtSignal(int) 173 174 def __init__(self, parent=None, name="Text", pixmap_preview_position="Full"): 175 super().__init__() 176 177 self.show_pixmap_preview = True 178 179 self.slider = QtWidgets.QSlider( 180 QtCore.Qt.Horizontal, 181 minimum=0, 182 maximum=100, 183 value=100, 184 valueChanged=self.on_slider_changed, 185 ) 186 187 # Updated valueChanged to actionTriggered to prevent unwanted recursive behavior. 188 # When a slider is set programmatically to refresh a UI, the value changing shouldn't trigger subsequent UI effects which think "Ah, the user has changed it" when that isn't the case. 189 # Example: The split is set temporarily to the window center when changing transparencies, but that split shouldn't move when refreshing the transparency sliders. 190 self.slider.actionTriggered.connect(self.on_slider_trigger) 191 192 self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove) 193 self.slider.setTickInterval(10) 194 self.slider.setSingleStep(5) 195 self.slider.setStyleSheet(""" 196 QSlider { 197 font-size: 10pt; 198 } 199 """) 200 201 self.spinbox = QtWidgets.QSpinBox() 202 self.spinbox.setMinimum(self.slider.minimum()) 203 self.spinbox.setMaximum(self.slider.maximum()) 204 self.spinbox.setSingleStep(1) 205 self.spinbox.setValue(self.slider.value()) 206 self.spinbox.setAlignment(QtCore.Qt.AlignRight) 207 self.spinbox.setStyleSheet(""" 208 QSpinBox { 209 font-size: 10pt; 210 } 211 """) 212 213 self.slider.valueChanged.connect(self.spinbox.setValue) 214 self.spinbox.valueChanged.connect(self.slider.setValue) 215 self.spinbox.valueChanged.connect(self.on_spinbox_change) 216 217 layout = QtWidgets.QHBoxLayout() 218 219 if name is not None: 220 self.label = QtWidgets.QLabel(name, alignment=QtCore.Qt.AlignCenter) 221 layout.addWidget(self.label) 222 223 layout.addWidget(self.slider) 224 layout.addWidget(self.spinbox) 225 layout.setContentsMargins(2,2,2,2) 226 layout.setSpacing(4) 227 228 if self.show_pixmap_preview: 229 230 checker_length = 10 231 232 self.pixmap_icon = QtGui.QPixmap(checker_length*2+2, checker_length*2+2) 233 self.pixmap_icon.fill(QtCore.Qt.transparent) 234 235 self.pixmap_outline = QtGui.QPixmap(self.pixmap_icon.size()) 236 self.pixmap_outline.fill(QtCore.Qt.transparent) 237 238 if pixmap_preview_position == "Top left": 239 x_0 = 0 240 y_0 = 0 241 x = 0.5*checker_length 242 y = 0.5*checker_length 243 244 elif pixmap_preview_position == "Top right": 245 x_0 = checker_length 246 y_0 = 0 247 x = 0.5*checker_length 248 y = 0.5*checker_length 249 250 elif pixmap_preview_position == "Bottom right": 251 x_0 = checker_length 252 y_0 = checker_length 253 x = 0.5*checker_length 254 y = 0.5*checker_length 255 256 elif pixmap_preview_position == "Bottom left": 257 x_0 = 0 258 y_0 = checker_length 259 x = 0.5*checker_length 260 y = 0.5*checker_length 261 262 else: 263 x_0 = 0 264 y_0 = 0 265 x = checker_length 266 y = checker_length 267 268 painter_icon = QtGui.QPainter(self.pixmap_icon) 269 270 painter_icon.setPen(QtCore.Qt.NoPen) 271 painter_icon.setBrush(QtCore.Qt.black) 272 painter_icon.drawRect(x_0, y_0, 2.0*x + 1, 2.0*y + 1) 273 274 painter_icon.setPen(QtCore.Qt.NoPen) 275 painter_icon.setBrush(QtCore.Qt.white) 276 painter_icon.drawRect(1 + x_0, 1 + y_0, x, y) 277 painter_icon.drawRect(1 + x_0 + x, 1 + y_0 + y, x, y) 278 279 painter_icon.end() 280 281 282 painter_outline = QtGui.QPainter(self.pixmap_outline) 283 284 painter_outline.setPen(QtCore.Qt.black) 285 painter_outline.setBrush(QtCore.Qt.NoBrush) 286 painter_outline.drawRect(x_0, y_0, 2.0*x + 1, 2.0*y + 1) 287 288 painter_outline.setPen(QtCore.Qt.black) 289 painter_outline.setBrush(QtCore.Qt.NoBrush) 290 painter_outline.drawRect(0, 0, 2*checker_length + 1, 2*checker_length + 1) 291 292 painter_outline.end() 293 294 295 pixmap = QtGui.QPixmap(self.pixmap_icon.size()) 296 pixmap.fill(QtCore.Qt.transparent) 297 painter = QtGui.QPainter(pixmap) 298 painter.drawPixmap(QtCore.QPoint(), self.pixmap_icon) 299 painter.drawPixmap(QtCore.QPoint(), self.pixmap_outline) 300 painter.end() 301 302 303 self.label_pixmap = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter) 304 self.label_pixmap.setPixmap(pixmap) 305 layout.insertWidget(0, self.label_pixmap) 306 307 self.setLayout(layout) 308 309 @QtCore.pyqtSlot(int) 310 def on_slider_changed(self, value): 311 """Set opacity of preview icon when slider is changed. 312 313 Triggered when the slider value changes. 314 315 Args: 316 value (int): Opacity, where 100 is opaque (not transparent) and 0 is transparent. 317 """ 318 if self.show_pixmap_preview: 319 new_pix = QtGui.QPixmap(self.pixmap_icon.size()) 320 new_pix.fill(QtCore.Qt.transparent) 321 painter = QtGui.QPainter(new_pix) 322 painter.setOpacity(value * 0.01) 323 painter.drawPixmap(QtCore.QPoint(), self.pixmap_icon) 324 painter.setOpacity(1) 325 painter.drawPixmap(QtCore.QPoint(), self.pixmap_outline) 326 painter.end() 327 self.label_pixmap.setPixmap(new_pix) 328 329 def on_slider_trigger(self, action): 330 """QAction: Signal the slider position when slider is triggered.""" 331 self.was_changed_slider_value.emit(self.slider.sliderPosition()) 332 333 def on_spinbox_change(self, value): 334 """int: Signal the slider position when spinbox is changed.""" 335 self.was_changed_slider_value.emit(self.slider.sliderPosition()) 336 337 def set_value(self, value): 338 """int (0-100): Set value of slider.""" 339 self.slider.setValue(value) 340 341 342 343class SlidersOpacitySplitViews(QtWidgets.QFrame): 344 """Interface for changing the transparency (opposite of opacity) of the overlay images of a SplitView. 345 346 Instantiate without input. See Butterfly Viewer for implementation. 347 """ 348 349 was_changed_slider_base_value = QtCore.pyqtSignal(int) 350 was_changed_slider_topright_value = QtCore.pyqtSignal(int) 351 was_changed_slider_bottomright_value = QtCore.pyqtSignal(int) 352 was_changed_slider_bottomleft_value = QtCore.pyqtSignal(int) 353 354 def __init__(self, parent=None): 355 356 super().__init__() 357 358 self.slider_base = SliderDeluxe(name=None, pixmap_preview_position="Base") 359 self.slider_base.setToolTip("Adjust transparency of base image (0% = transparent; 100% = opaque)") 360 self.slider_topright = SliderDeluxe(name=None, pixmap_preview_position="Top right") 361 self.slider_topright.setToolTip("Adjust transparency of top right of sliding overlay (0% = transparent; 100% = opaque)") 362 self.slider_bottomright = SliderDeluxe(name=None, pixmap_preview_position="Bottom right") 363 self.slider_bottomright.setToolTip("Adjust transparency of bottom right of sliding overlay (0% = transparent; 100% = opaque)") 364 self.slider_bottomleft = SliderDeluxe(name=None, pixmap_preview_position="Bottom left") 365 self.slider_bottomleft.setToolTip("Adjust transparency of bottom left of sliding overlay (0% = transparent; 100% = opaque)") 366 367 self.slider_base.was_changed_slider_value.connect(self.was_changed_slider_base_value) 368 self.slider_topright.was_changed_slider_value.connect(self.was_changed_slider_topright_value) 369 self.slider_bottomright.was_changed_slider_value.connect(self.was_changed_slider_bottomright_value) 370 self.slider_bottomleft.was_changed_slider_value.connect(self.was_changed_slider_bottomleft_value) 371 372 layout_sliders = QtWidgets.QGridLayout() 373 374 layout_sliders.addWidget(self.slider_base, 0, 1) 375 layout_sliders.addWidget(self.slider_topright, 1, 1) 376 layout_sliders.addWidget(self.slider_bottomright, 2, 1) 377 layout_sliders.addWidget(self.slider_bottomleft, 3, 1) 378 379 self.label_base = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 380 self.label_topright = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 381 self.label_bottomright = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 382 self.label_bottomleft = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 383 384 self.title_label = QtWidgets.QLabel("Opacity of active image window") 385 self.title_label.setAlignment(QtCore.Qt.AlignLeft) 386 self.title_label.setStyleSheet(""" 387 QLabel { 388 font-size: 10pt; 389 } 390 """) 391 392 layout_sliders.addWidget(self.label_base, 0, 0) 393 layout_sliders.addWidget(self.label_topright, 1, 0) 394 layout_sliders.addWidget(self.label_bottomright, 2, 0) 395 layout_sliders.addWidget(self.label_bottomleft, 3, 0) 396 397 layout_sliders.setContentsMargins(0,0,0,0) 398 layout_sliders.setSpacing(0) 399 400 layout = QtWidgets.QGridLayout() 401 layout.addWidget(self.title_label,0,0) 402 layout.addLayout(layout_sliders,1,0) 403 404 contents_margins_w = 4 405 layout.setContentsMargins(contents_margins_w, contents_margins_w, contents_margins_w, contents_margins_w) 406 layout.setSpacing(2*contents_margins_w) 407 408 self.setLayout(layout) 409 #self.setFrameStyle(QtWidgets.QFrame.Box | QtWidgets.QFrame.Sunken) 410 self.setStyleSheet("QFrame {background: palette(window); border-radius: 0.5em;}") 411 self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) 412 self.setContentsMargins(2,2,2,2) 413 414 self.setMinimumWidth(220) 415 416 self.set_enabled_base(False) 417 self.set_enabled_topright(False) 418 self.set_enabled_bottomright(False) 419 self.set_enabled_bottomleft(False) 420 421 422 def update_sliders(self, opacity_base, opacity_topright, opacity_bottomright, opacity_bottomleft): 423 """Update the values of the opacity sliders. 424 425 Arg: 426 opacity_topright (int): Set the value of the slider for top-right of SplitView (0-100). 427 opacity_bottomright (int): Set the value of the slider for bottom-right slider of SplitView (0-100). 428 opacity_bottomleft (int): Set the value of the slider for bottom-left slider of SplitView (0-100). 429 """ 430 self.slider_base.set_value(opacity_base) 431 self.slider_topright.set_value(opacity_topright) 432 self.slider_bottomright.set_value(opacity_bottomright) 433 self.slider_bottomleft.set_value(opacity_bottomleft) 434 435 def reset_sliders(self): 436 """Reset all sliders to 100 and disable them (convenience).""" 437 self.update_sliders(100, 100, 100, 100) 438 self.set_enabled(False, False, False, False) 439 440 def set_enabled(self, boolean_base, boolean_topright, boolean_bottomright, boolean_bottomleft): 441 """Enable/disable opacity sliders (convenience). 442 443 Arg: 444 boolean_base (bool): True is enabled; False is disabled. 445 boolean_topright (bool): True is enabled; False is disabled. 446 boolean_bottomright (bool): True is enabled; False is disabled. 447 boolean_bottomleft (bool): True is enabled; False is disabled. 448 """ 449 self.set_enabled_base(boolean_base) 450 self.set_enabled_topright(boolean_topright) 451 self.set_enabled_bottomright(boolean_bottomright) 452 self.set_enabled_bottomleft(boolean_bottomleft) 453 454 def set_enabled_base(self, boolean): 455 """bool: Enable/disable opacity slider and label for base of SplitView.""" 456 self.slider_base.setEnabled(boolean) 457 self.label_base.setEnabled(boolean) 458 459 def set_enabled_topright(self, boolean): 460 """bool: Enable/disable opacity slider and label for top-right of SplitView.""" 461 self.slider_topright.setEnabled(boolean) 462 self.label_topright.setEnabled(boolean) 463 464 def set_enabled_bottomright(self, boolean): 465 """bool: Enable/disable opacity slider and label for bottom-right of SplitView.""" 466 self.slider_bottomright.setEnabled(boolean) 467 self.label_bottomright.setEnabled(boolean) 468 469 def set_enabled_bottomleft(self, boolean): 470 """bool: Enable/disable opacity slider and label for bottom-left of SplitView.""" 471 self.slider_bottomleft.setEnabled(boolean) 472 self.label_bottomleft.setEnabled(boolean) 473 474 def setMouseTracking(self, flag): 475 """PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.""" 476 def recursive_set(parent): 477 for child in parent.findChildren(QtCore.QObject): 478 try: 479 child.setMouseTracking(flag) 480 except: 481 pass 482 recursive_set(child) 483 QtWidgets.QWidget.setMouseTracking(self, flag) 484 recursive_set(self) 485 486 487 488class PushbuttonSplitViewSet(QtWidgets.QPushButton): 489 """Custom QPushButton for buttons in SplitViewManager to set the position of the split in a SplitView. 490 491 Args: 492 text (str): The text icon of the split shortcut. 493 x (float): The position of the split (0-1) of which is to be "shortcutted" as a proportion of the base image's horizontal resolution. 494 y (float): The position of the split (0-1) of which is to be "shortcutted" as a proportion of the base image's vertical resolution. 495 """ 496 497 hovered_xy = QtCore.pyqtSignal(float,float) 498 clicked_xy = QtCore.pyqtSignal(float,float) 499 500 def __init__(self, parent=None, text=None, x=None, y=None): 501 super().__init__() 502 self.setText(text) 503 self.x = x 504 self.y = y 505 self.clicked.connect(self.on_clicked) 506 507 self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) 508 self.setStyleSheet(""" 509 QPushButton { 510 width: 0.85em; 511 height: 0.85em; 512 color: white; 513 background-color: rgba(0, 0, 0, 63); 514 border: 0px black; 515 font-size: 20pt; 516 border-radius: 0.06em; 517 } 518 QPushButton:hover { 519 background-color: rgba(223, 166, 0, 223); 520 } 521 QPushButton:pressed { 522 color: white; 523 background-color: rgba(255, 181, 0, 255); 524 } 525 """) 526 527 def enterEvent(self, event): 528 """Override enterEvent to signal the shortcutted position of the button when hovered by the mouse. 529 530 Allows the user to preview the split shortcut without needing to click and lock. 531 532 Args: 533 event (PyQt event) 534 """ 535 self.hovered_xy.emit(self.x, self.y) 536 pass 537 538 def leaveEvent(self, event): 539 """event: Override leaveEvent to pass when mouse leaves area of the button.""" 540 pass 541 542 def on_clicked(self): 543 """Signal the shortcutted position of the button when clicked.""" 544 self.clicked_xy.emit(self.x, self.y) 545 546 547 548class SplitViewManager(QtWidgets.QWidget): 549 """Interface for shortcut buttons to position and lock the split in a sliding overlay. 550 551 Instantiate without input. See Butterfly Viewer for implementation. 552 553 Features: 554 Button to lock and unlock the split of a sliding overlay. 555 Arrow buttons position the split to shortcut parts of the image (center, top-right, bottom-left, etc.). 556 """ 557 558 hovered_xy = QtCore.pyqtSignal(float,float) 559 clicked_xy = QtCore.pyqtSignal(float,float) 560 lock_split_locked = QtCore.pyqtSignal() 561 lock_split_unlocked = QtCore.pyqtSignal() 562 563 def __init__(self, parent=None): 564 super().__init__() 565 566 self.lock_split_pushbutton = QtWidgets.QToolButton() 567 self.lock_split_pushbutton.setText("Lock Overlay (Shift·X)") 568 self.lock_split_pushbutton.setToolTip("Lock the split position of the active sliding overlay") 569 self.lock_split_pushbutton.setStyleSheet(""" 570 QToolButton { 571 color: white; 572 background-color: rgba(0, 0, 0, 91); 573 border-width: 0.06em; 574 border-style: solid; 575 border-color: white; 576 border-radius: 0.13em; 577 font-size: 11pt; 578 } 579 QToolButton:hover { 580 background-color: rgba(223, 166, 0, 223); 581 } 582 QToolButton:pressed { 583 color: white; 584 background-color: rgba(255, 181, 0, 255); 585 } 586 QToolButton:checked { 587 color: white; 588 background-color: rgba(191, 151, 0, 191); 589 border-color: rgba(255, 181, 0, 255); 590 } 591 QToolButton:checked:hover { 592 color: white; 593 background-color: rgba(223, 166, 0, 223); 594 border-color: rgba(255, 181, 0, 255); 595 } 596 QToolButton:checked:pressed { 597 color: white; 598 background-color: rgba(255, 181, 0, 255); 599 border-color: rgba(255, 181, 0, 255); 600 } 601 """) 602 self.lock_split_pushbutton.setCheckable(True) 603 self.lock_split_pushbutton.toggled.connect(self.on_toggle_lock_split_pushbutton) 604 lock_split_layout = QtWidgets.QGridLayout() 605 lock_split_layout.addWidget(self.lock_split_pushbutton) 606 lock_split_widget = QtWidgets.QWidget() 607 lock_split_widget.setLayout(lock_split_layout) 608 609 layout_buttons = QtWidgets.QGridLayout() 610 layout_buttons.setContentsMargins(0,0,0,0) 611 layout_buttons.setSpacing(0) 612 613 layout_buttons.setRowStretch(0,1) 614 layout_buttons.setColumnStretch(0,1) 615 616 self.topleft_pushbutton = PushbuttonSplitViewSet(text="⇖", x=0.0, y=0.0) 617 self.topleft_pushbutton.setToolTip("Top left of sliding overlay (move and lock)") 618 self.topleft_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 619 self.topleft_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 620 621 self.topcenter_pushbutton = PushbuttonSplitViewSet(text="⥣", x=0.5, y=0.0) 622 self.topcenter_pushbutton.setToolTip("Top center of sliding overlay (move and lock)") 623 self.topcenter_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 624 self.topcenter_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 625 626 self.topright_pushbutton = PushbuttonSplitViewSet(text="⇗", x=1.0, y=0.0) 627 self.topright_pushbutton.setToolTip("Top right of sliding overlay (move and lock)") 628 self.topright_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 629 self.topright_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 630 631 self.middleleft_pushbutton = PushbuttonSplitViewSet(text="⥢", x=0.0, y=0.5) 632 self.middleleft_pushbutton.setToolTip("Middle left of sliding overlay (move and lock)") 633 self.middleleft_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 634 self.middleleft_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 635 636 self.middlecenter_pushbutton = PushbuttonSplitViewSet(text="⧾", x=0.5, y=0.5) 637 self.middlecenter_pushbutton.setToolTip("Middle center of sliding overlay (move and lock)") 638 self.middlecenter_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 639 self.middlecenter_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 640 641 self.middleright_pushbutton = PushbuttonSplitViewSet(text="⥤", x=1.0, y=0.5) 642 self.middleright_pushbutton.setToolTip("Middle right of sliding overlay (move and lock)") 643 self.middleright_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 644 self.middleright_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 645 646 self.bottomleft_pushbutton = PushbuttonSplitViewSet(text="⇙", x=0.0, y=1.0) 647 self.bottomleft_pushbutton.setToolTip("Bottom left of sliding overlay (move and lock)") 648 self.bottomleft_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 649 self.bottomleft_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 650 651 self.bottomcenter_pushbutton = PushbuttonSplitViewSet(text="⥥", x=0.5, y=1.0) 652 self.bottomcenter_pushbutton.setToolTip("Bottom center of sliding overlay (move and lock)") 653 self.bottomcenter_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 654 self.bottomcenter_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 655 656 self.bottomright_pushbutton = PushbuttonSplitViewSet(text="⇘", x=1.0, y=1.0) 657 self.bottomright_pushbutton.setToolTip("Bottom right of sliding overlay (move and lock)") 658 self.bottomright_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 659 self.bottomright_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 660 661 662 layout_buttons.addWidget(self.topleft_pushbutton,1,1) 663 layout_buttons.addWidget(self.topcenter_pushbutton,1,2) 664 layout_buttons.addWidget(self.topright_pushbutton,1,3) 665 layout_buttons.addWidget(self.middleleft_pushbutton,2,1) 666 layout_buttons.addWidget(self.middlecenter_pushbutton,2,2) 667 layout_buttons.addWidget(self.middleright_pushbutton,2,3) 668 layout_buttons.addWidget(self.bottomleft_pushbutton,3,1) 669 layout_buttons.addWidget(self.bottomcenter_pushbutton,3,2) 670 layout_buttons.addWidget(self.bottomright_pushbutton,3,3) 671 672 layout_buttons.setRowStretch(layout_buttons.rowCount(),1) 673 layout_buttons.setColumnStretch(layout_buttons.columnCount(),1) 674 675 layout = QtWidgets.QGridLayout() 676 layout.addWidget(lock_split_widget, 0, 0) 677 layout.addLayout(layout_buttons, 1, 0) 678 679 contents_margins_w = 8 680 layout.setContentsMargins(contents_margins_w, 0, contents_margins_w, 0) 681 layout.setSpacing(0) 682 683 self.setLayout(layout) 684 self.setContentsMargins(0,0,0,0) 685 686 def on_toggle_lock_split_pushbutton(self, boolean): 687 """Signal the locking and unlocking of the split and set the text elements of the lock button. 688 689 Triggered when the lock button is toggled on and off (locked and unlocked). 690 691 Args: 692 boolean (bool): True for locking split; False for unlocking split. 693 """ 694 if boolean: 695 text = "Unlock Overlay (Shift·X)" 696 tooltip = "Unlock the split position of the active sliding overlay" 697 self.lock_split_locked.emit() 698 else: 699 text = "Lock Overlay (Shift·X)" 700 tooltip = "Lock the split position of the active sliding overlay" 701 self.lock_split_unlocked.emit() 702 self.lock_split_pushbutton.setText(text) 703 self.lock_split_pushbutton.setToolTip(tooltip) 704 705 def on_hovered_set_pushbutton(self, x_percent, y_percent): 706 """Signal the position of the split to temporarily move the split. 707 708 Triggered when the shortcut arrow buttons are hovered. 709 Needed to temporarily signal the moving of the split when the user hovers over a split shortcut button. 710 711 Args: 712 x_percent (float): The position of the split as a proportion (0-1) of the base image's horizontal resolution. 713 y_percent (float): The position of the split as a proportion (0-1) of the base image's vertical resolution. 714 """ 715 self.hovered_xy.emit(x_percent, y_percent) 716 717 def on_clicked_set_pushbutton(self, x_percent, y_percent): 718 """Signal the clicking of the lock button with a given split position. 719 720 Args: 721 x_percent (float): The position of the split as a proportion (0-1) of the base image's horizontal resolution. 722 y_percent (float): The position of the split as a proportion (0-1) of the base image's vertical resolution. 723 """ 724 self.clicked_xy.emit(x_percent, y_percent)
25class FourDragDropImageLabelForSplitView(FourDragDropImageLabel): 26 """Extends a 2x2 drag-and-drop zone for SplitViewCreator. 27 28 Requires a base image (main; top-left) to be given before other images of SplitView may be added. 29 30 Instantiate without input: 31 self.drag_drop_area = FourDragDropImageLabelForSplitView() 32 """ 33 34 main_became_occupied = QtCore.pyqtSignal(bool) 35 36 def __init__(self): 37 super().__init__() 38 self.set_addable_all_except_main(False) 39 self.app_main_topleft.became_occupied.connect(self.on_main_topleft_occupied) 40 self.app_main_topleft.became_occupied.connect(self.main_became_occupied) 41 42 43 # Set style and function of drag-and-drop zones 44 45 def set_addable_all_except_main(self, boolean): 46 """Set all overlay images to be (or not to be) addable via drag-and-drop. 47 48 Convenience for the SplitViewCreator. 49 50 Args: 51 boolean (bool): True to make the overlay images addable; False to make un-addable. 52 """ 53 self.app_topright.set_addable(boolean) 54 self.app_bottomright.set_addable(boolean) 55 self.app_bottomleft.set_addable(boolean) 56 57 def on_main_topleft_occupied(self, boolean): 58 """Set when base image becomes occupied or unoccupied to set whether overlay images can be added. 59 60 Args: 61 boolean (bool): True to indicate base image is occupied (and thus overlay images may be added); 62 False to indicate main image is unoccupied (and thus overlay images may not be added).""" 63 self.set_addable_all_except_main(boolean)
Extends a 2x2 drag-and-drop zone for SplitViewCreator.
Requires a base image (main; top-left) to be given before other images of SplitView may be added.
Instantiate without input:
self.drag_drop_area = FourDragDropImageLabelForSplitView()
45 def set_addable_all_except_main(self, boolean): 46 """Set all overlay images to be (or not to be) addable via drag-and-drop. 47 48 Convenience for the SplitViewCreator. 49 50 Args: 51 boolean (bool): True to make the overlay images addable; False to make un-addable. 52 """ 53 self.app_topright.set_addable(boolean) 54 self.app_bottomright.set_addable(boolean) 55 self.app_bottomleft.set_addable(boolean)
Set all overlay images to be (or not to be) addable via drag-and-drop.
Convenience for the SplitViewCreator.
Arguments:
- boolean (bool): True to make the overlay images addable; False to make un-addable.
57 def on_main_topleft_occupied(self, boolean): 58 """Set when base image becomes occupied or unoccupied to set whether overlay images can be added. 59 60 Args: 61 boolean (bool): True to indicate base image is occupied (and thus overlay images may be added); 62 False to indicate main image is unoccupied (and thus overlay images may not be added).""" 63 self.set_addable_all_except_main(boolean)
Set when base image becomes occupied or unoccupied to set whether overlay images can be added.
Arguments:
- boolean (bool): True to indicate base image is occupied (and thus overlay images may be added); False to indicate main image is unoccupied (and thus overlay images may not be added).
67class SplitViewCreator(QtWidgets.QFrame): 68 """Interface for users to add images from which to create a SplitView. 69 70 Users can add local image files via drag-and-drop and "Select image..." dialogs. 71 72 Instantiate without input. See Butterfly Viewer for implementation. 73 """ 74 75 clicked_create_splitview_pushbutton = QtCore.pyqtSignal() 76 77 def __init__(self): 78 super().__init__() 79 80 main_layout = QtWidgets.QGridLayout() 81 82 self.drag_drop_area = FourDragDropImageLabelForSplitView() 83 self.drag_drop_area.will_start_loading.connect(self.display_loading_grayout) 84 self.drag_drop_area.has_stopped_loading.connect(self.display_loading_grayout) 85 86 self.buttons_layout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight) 87 self.create_splitview_pushbutton = QtWidgets.QPushButton("Create") 88 self.create_splitview_pushbutton.setToolTip("Create a sliding overlay window with these images") 89 self.create_splitview_pushbutton.setStyleSheet("QPushButton { font-size: 10pt; }") 90 self.create_splitview_pushbutton.clicked.connect(self.clicked_create_splitview_pushbutton) 91 92 self.create_splitview_pushbutton.setEnabled(False) 93 self.drag_drop_area.main_became_occupied.connect(self.create_splitview_pushbutton.setEnabled) 94 95 self.buttons_layout.addWidget(self.create_splitview_pushbutton) 96 97 self.loading_grayout_label = QtWidgets.QLabel("Loading...") 98 self.loading_grayout_label.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter) 99 self.loading_grayout_label.setVisible(False) 100 self.loading_grayout_label.setStyleSheet(""" 101 QLabel { 102 color: white; 103 font-size: 7.5pt; 104 background-color: rgba(0,0,0,223); 105 } 106 """) 107 108 self.title_label = QtWidgets.QLabel("Sliding overlay creator") 109 self.title_label.setAlignment(QtCore.Qt.AlignLeft) 110 self.title_label.setStyleSheet(""" 111 QLabel { 112 font-size: 10pt; 113 } 114 """) 115 116 main_layout.addWidget(self.title_label,0,0) 117 main_layout.addWidget(self.drag_drop_area, 1, 0) 118 main_layout.addLayout(self.buttons_layout, 2, 0) 119 main_layout.addWidget(self.loading_grayout_label, 0, 0, 3, 1) 120 main_layout.setAlignment(QtCore.Qt.AlignTop) 121 main_layout.setContentsMargins(4,4,4,4) 122 123 # self.setMinimumWidth(250) 124 # self.setMinimumHeight(325) 125 126 self.setLayout(main_layout) 127 self.setContentsMargins(2,2,2,2) # As docked on left side 128 129 self.setStyleSheet("QFrame {background: palette(window); border-radius: 0.5em;}") 130 131 def setMouseTracking(self, flag): 132 """PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.""" 133 def recursive_set(parent): 134 for child in parent.findChildren(QtCore.QObject): # Needed to track the split in sliding overlays while hovering over interfaces and other widgets; prevents sudden stops and jumps of the split 135 try: 136 child.setMouseTracking(flag) 137 except: 138 pass 139 recursive_set(child) 140 QtWidgets.QWidget.setMouseTracking(self, flag) 141 recursive_set(self) 142 143 def display_loading_grayout(self, boolean, text="Loading...", pseudo_load_time=0.2): 144 """Show/hide grayout screen for loading sequences. 145 146 Args: 147 boolean (bool): True to show grayout; False to hide. 148 text (str): The text to show on the grayout. 149 pseudo_load_time (float): The delay (in seconds) to hide the grayout to give users a feeling of action. 150 """ 151 # Needed to give feedback to user that images are loading 152 if not boolean: 153 text = "Loading..." 154 self.loading_grayout_label.setText(text) 155 self.loading_grayout_label.setVisible(boolean) 156 if boolean: 157 self.loading_grayout_label.repaint() 158 if not boolean: 159 time.sleep(pseudo_load_time)
Interface for users to add images from which to create a SplitView.
Users can add local image files via drag-and-drop and "Select image..." dialogs.
Instantiate without input. See Butterfly Viewer for implementation.
131 def setMouseTracking(self, flag): 132 """PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.""" 133 def recursive_set(parent): 134 for child in parent.findChildren(QtCore.QObject): # Needed to track the split in sliding overlays while hovering over interfaces and other widgets; prevents sudden stops and jumps of the split 135 try: 136 child.setMouseTracking(flag) 137 except: 138 pass 139 recursive_set(child) 140 QtWidgets.QWidget.setMouseTracking(self, flag) 141 recursive_set(self)
PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.
143 def display_loading_grayout(self, boolean, text="Loading...", pseudo_load_time=0.2): 144 """Show/hide grayout screen for loading sequences. 145 146 Args: 147 boolean (bool): True to show grayout; False to hide. 148 text (str): The text to show on the grayout. 149 pseudo_load_time (float): The delay (in seconds) to hide the grayout to give users a feeling of action. 150 """ 151 # Needed to give feedback to user that images are loading 152 if not boolean: 153 text = "Loading..." 154 self.loading_grayout_label.setText(text) 155 self.loading_grayout_label.setVisible(boolean) 156 if boolean: 157 self.loading_grayout_label.repaint() 158 if not boolean: 159 time.sleep(pseudo_load_time)
Show/hide grayout screen for loading sequences.
Arguments:
- boolean (bool): True to show grayout; False to hide.
- text (str): The text to show on the grayout.
- pseudo_load_time (float): The delay (in seconds) to hide the grayout to give users a feeling of action.
163class SliderDeluxe(QtWidgets.QWidget): 164 """Custom slider for setting and indicating the transparencies of overlay images in SplitView. 165 166 Used in SlidersOpacitySplitViews. 167 168 Args: 169 name (str): Text for slider label. 170 pixmap_preview_position (str): The position of the preview icon for indicating opacity ("Full", "Top right", "Bottom right", "Bottom left"). 171 """ 172 173 was_changed_slider_value = QtCore.pyqtSignal(int) 174 175 def __init__(self, parent=None, name="Text", pixmap_preview_position="Full"): 176 super().__init__() 177 178 self.show_pixmap_preview = True 179 180 self.slider = QtWidgets.QSlider( 181 QtCore.Qt.Horizontal, 182 minimum=0, 183 maximum=100, 184 value=100, 185 valueChanged=self.on_slider_changed, 186 ) 187 188 # Updated valueChanged to actionTriggered to prevent unwanted recursive behavior. 189 # When a slider is set programmatically to refresh a UI, the value changing shouldn't trigger subsequent UI effects which think "Ah, the user has changed it" when that isn't the case. 190 # Example: The split is set temporarily to the window center when changing transparencies, but that split shouldn't move when refreshing the transparency sliders. 191 self.slider.actionTriggered.connect(self.on_slider_trigger) 192 193 self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove) 194 self.slider.setTickInterval(10) 195 self.slider.setSingleStep(5) 196 self.slider.setStyleSheet(""" 197 QSlider { 198 font-size: 10pt; 199 } 200 """) 201 202 self.spinbox = QtWidgets.QSpinBox() 203 self.spinbox.setMinimum(self.slider.minimum()) 204 self.spinbox.setMaximum(self.slider.maximum()) 205 self.spinbox.setSingleStep(1) 206 self.spinbox.setValue(self.slider.value()) 207 self.spinbox.setAlignment(QtCore.Qt.AlignRight) 208 self.spinbox.setStyleSheet(""" 209 QSpinBox { 210 font-size: 10pt; 211 } 212 """) 213 214 self.slider.valueChanged.connect(self.spinbox.setValue) 215 self.spinbox.valueChanged.connect(self.slider.setValue) 216 self.spinbox.valueChanged.connect(self.on_spinbox_change) 217 218 layout = QtWidgets.QHBoxLayout() 219 220 if name is not None: 221 self.label = QtWidgets.QLabel(name, alignment=QtCore.Qt.AlignCenter) 222 layout.addWidget(self.label) 223 224 layout.addWidget(self.slider) 225 layout.addWidget(self.spinbox) 226 layout.setContentsMargins(2,2,2,2) 227 layout.setSpacing(4) 228 229 if self.show_pixmap_preview: 230 231 checker_length = 10 232 233 self.pixmap_icon = QtGui.QPixmap(checker_length*2+2, checker_length*2+2) 234 self.pixmap_icon.fill(QtCore.Qt.transparent) 235 236 self.pixmap_outline = QtGui.QPixmap(self.pixmap_icon.size()) 237 self.pixmap_outline.fill(QtCore.Qt.transparent) 238 239 if pixmap_preview_position == "Top left": 240 x_0 = 0 241 y_0 = 0 242 x = 0.5*checker_length 243 y = 0.5*checker_length 244 245 elif pixmap_preview_position == "Top right": 246 x_0 = checker_length 247 y_0 = 0 248 x = 0.5*checker_length 249 y = 0.5*checker_length 250 251 elif pixmap_preview_position == "Bottom right": 252 x_0 = checker_length 253 y_0 = checker_length 254 x = 0.5*checker_length 255 y = 0.5*checker_length 256 257 elif pixmap_preview_position == "Bottom left": 258 x_0 = 0 259 y_0 = checker_length 260 x = 0.5*checker_length 261 y = 0.5*checker_length 262 263 else: 264 x_0 = 0 265 y_0 = 0 266 x = checker_length 267 y = checker_length 268 269 painter_icon = QtGui.QPainter(self.pixmap_icon) 270 271 painter_icon.setPen(QtCore.Qt.NoPen) 272 painter_icon.setBrush(QtCore.Qt.black) 273 painter_icon.drawRect(x_0, y_0, 2.0*x + 1, 2.0*y + 1) 274 275 painter_icon.setPen(QtCore.Qt.NoPen) 276 painter_icon.setBrush(QtCore.Qt.white) 277 painter_icon.drawRect(1 + x_0, 1 + y_0, x, y) 278 painter_icon.drawRect(1 + x_0 + x, 1 + y_0 + y, x, y) 279 280 painter_icon.end() 281 282 283 painter_outline = QtGui.QPainter(self.pixmap_outline) 284 285 painter_outline.setPen(QtCore.Qt.black) 286 painter_outline.setBrush(QtCore.Qt.NoBrush) 287 painter_outline.drawRect(x_0, y_0, 2.0*x + 1, 2.0*y + 1) 288 289 painter_outline.setPen(QtCore.Qt.black) 290 painter_outline.setBrush(QtCore.Qt.NoBrush) 291 painter_outline.drawRect(0, 0, 2*checker_length + 1, 2*checker_length + 1) 292 293 painter_outline.end() 294 295 296 pixmap = QtGui.QPixmap(self.pixmap_icon.size()) 297 pixmap.fill(QtCore.Qt.transparent) 298 painter = QtGui.QPainter(pixmap) 299 painter.drawPixmap(QtCore.QPoint(), self.pixmap_icon) 300 painter.drawPixmap(QtCore.QPoint(), self.pixmap_outline) 301 painter.end() 302 303 304 self.label_pixmap = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter) 305 self.label_pixmap.setPixmap(pixmap) 306 layout.insertWidget(0, self.label_pixmap) 307 308 self.setLayout(layout) 309 310 @QtCore.pyqtSlot(int) 311 def on_slider_changed(self, value): 312 """Set opacity of preview icon when slider is changed. 313 314 Triggered when the slider value changes. 315 316 Args: 317 value (int): Opacity, where 100 is opaque (not transparent) and 0 is transparent. 318 """ 319 if self.show_pixmap_preview: 320 new_pix = QtGui.QPixmap(self.pixmap_icon.size()) 321 new_pix.fill(QtCore.Qt.transparent) 322 painter = QtGui.QPainter(new_pix) 323 painter.setOpacity(value * 0.01) 324 painter.drawPixmap(QtCore.QPoint(), self.pixmap_icon) 325 painter.setOpacity(1) 326 painter.drawPixmap(QtCore.QPoint(), self.pixmap_outline) 327 painter.end() 328 self.label_pixmap.setPixmap(new_pix) 329 330 def on_slider_trigger(self, action): 331 """QAction: Signal the slider position when slider is triggered.""" 332 self.was_changed_slider_value.emit(self.slider.sliderPosition()) 333 334 def on_spinbox_change(self, value): 335 """int: Signal the slider position when spinbox is changed.""" 336 self.was_changed_slider_value.emit(self.slider.sliderPosition()) 337 338 def set_value(self, value): 339 """int (0-100): Set value of slider.""" 340 self.slider.setValue(value)
Custom slider for setting and indicating the transparencies of overlay images in SplitView.
Used in SlidersOpacitySplitViews.
Arguments:
- name (str): Text for slider label.
- pixmap_preview_position (str): The position of the preview icon for indicating opacity ("Full", "Top right", "Bottom right", "Bottom left").
310 @QtCore.pyqtSlot(int) 311 def on_slider_changed(self, value): 312 """Set opacity of preview icon when slider is changed. 313 314 Triggered when the slider value changes. 315 316 Args: 317 value (int): Opacity, where 100 is opaque (not transparent) and 0 is transparent. 318 """ 319 if self.show_pixmap_preview: 320 new_pix = QtGui.QPixmap(self.pixmap_icon.size()) 321 new_pix.fill(QtCore.Qt.transparent) 322 painter = QtGui.QPainter(new_pix) 323 painter.setOpacity(value * 0.01) 324 painter.drawPixmap(QtCore.QPoint(), self.pixmap_icon) 325 painter.setOpacity(1) 326 painter.drawPixmap(QtCore.QPoint(), self.pixmap_outline) 327 painter.end() 328 self.label_pixmap.setPixmap(new_pix)
Set opacity of preview icon when slider is changed.
Triggered when the slider value changes.
Arguments:
- value (int): Opacity, where 100 is opaque (not transparent) and 0 is transparent.
330 def on_slider_trigger(self, action): 331 """QAction: Signal the slider position when slider is triggered.""" 332 self.was_changed_slider_value.emit(self.slider.sliderPosition())
QAction: Signal the slider position when slider is triggered.
344class SlidersOpacitySplitViews(QtWidgets.QFrame): 345 """Interface for changing the transparency (opposite of opacity) of the overlay images of a SplitView. 346 347 Instantiate without input. See Butterfly Viewer for implementation. 348 """ 349 350 was_changed_slider_base_value = QtCore.pyqtSignal(int) 351 was_changed_slider_topright_value = QtCore.pyqtSignal(int) 352 was_changed_slider_bottomright_value = QtCore.pyqtSignal(int) 353 was_changed_slider_bottomleft_value = QtCore.pyqtSignal(int) 354 355 def __init__(self, parent=None): 356 357 super().__init__() 358 359 self.slider_base = SliderDeluxe(name=None, pixmap_preview_position="Base") 360 self.slider_base.setToolTip("Adjust transparency of base image (0% = transparent; 100% = opaque)") 361 self.slider_topright = SliderDeluxe(name=None, pixmap_preview_position="Top right") 362 self.slider_topright.setToolTip("Adjust transparency of top right of sliding overlay (0% = transparent; 100% = opaque)") 363 self.slider_bottomright = SliderDeluxe(name=None, pixmap_preview_position="Bottom right") 364 self.slider_bottomright.setToolTip("Adjust transparency of bottom right of sliding overlay (0% = transparent; 100% = opaque)") 365 self.slider_bottomleft = SliderDeluxe(name=None, pixmap_preview_position="Bottom left") 366 self.slider_bottomleft.setToolTip("Adjust transparency of bottom left of sliding overlay (0% = transparent; 100% = opaque)") 367 368 self.slider_base.was_changed_slider_value.connect(self.was_changed_slider_base_value) 369 self.slider_topright.was_changed_slider_value.connect(self.was_changed_slider_topright_value) 370 self.slider_bottomright.was_changed_slider_value.connect(self.was_changed_slider_bottomright_value) 371 self.slider_bottomleft.was_changed_slider_value.connect(self.was_changed_slider_bottomleft_value) 372 373 layout_sliders = QtWidgets.QGridLayout() 374 375 layout_sliders.addWidget(self.slider_base, 0, 1) 376 layout_sliders.addWidget(self.slider_topright, 1, 1) 377 layout_sliders.addWidget(self.slider_bottomright, 2, 1) 378 layout_sliders.addWidget(self.slider_bottomleft, 3, 1) 379 380 self.label_base = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 381 self.label_topright = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 382 self.label_bottomright = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 383 self.label_bottomleft = QtWidgets.QLabel("", alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) 384 385 self.title_label = QtWidgets.QLabel("Opacity of active image window") 386 self.title_label.setAlignment(QtCore.Qt.AlignLeft) 387 self.title_label.setStyleSheet(""" 388 QLabel { 389 font-size: 10pt; 390 } 391 """) 392 393 layout_sliders.addWidget(self.label_base, 0, 0) 394 layout_sliders.addWidget(self.label_topright, 1, 0) 395 layout_sliders.addWidget(self.label_bottomright, 2, 0) 396 layout_sliders.addWidget(self.label_bottomleft, 3, 0) 397 398 layout_sliders.setContentsMargins(0,0,0,0) 399 layout_sliders.setSpacing(0) 400 401 layout = QtWidgets.QGridLayout() 402 layout.addWidget(self.title_label,0,0) 403 layout.addLayout(layout_sliders,1,0) 404 405 contents_margins_w = 4 406 layout.setContentsMargins(contents_margins_w, contents_margins_w, contents_margins_w, contents_margins_w) 407 layout.setSpacing(2*contents_margins_w) 408 409 self.setLayout(layout) 410 #self.setFrameStyle(QtWidgets.QFrame.Box | QtWidgets.QFrame.Sunken) 411 self.setStyleSheet("QFrame {background: palette(window); border-radius: 0.5em;}") 412 self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) 413 self.setContentsMargins(2,2,2,2) 414 415 self.setMinimumWidth(220) 416 417 self.set_enabled_base(False) 418 self.set_enabled_topright(False) 419 self.set_enabled_bottomright(False) 420 self.set_enabled_bottomleft(False) 421 422 423 def update_sliders(self, opacity_base, opacity_topright, opacity_bottomright, opacity_bottomleft): 424 """Update the values of the opacity sliders. 425 426 Arg: 427 opacity_topright (int): Set the value of the slider for top-right of SplitView (0-100). 428 opacity_bottomright (int): Set the value of the slider for bottom-right slider of SplitView (0-100). 429 opacity_bottomleft (int): Set the value of the slider for bottom-left slider of SplitView (0-100). 430 """ 431 self.slider_base.set_value(opacity_base) 432 self.slider_topright.set_value(opacity_topright) 433 self.slider_bottomright.set_value(opacity_bottomright) 434 self.slider_bottomleft.set_value(opacity_bottomleft) 435 436 def reset_sliders(self): 437 """Reset all sliders to 100 and disable them (convenience).""" 438 self.update_sliders(100, 100, 100, 100) 439 self.set_enabled(False, False, False, False) 440 441 def set_enabled(self, boolean_base, boolean_topright, boolean_bottomright, boolean_bottomleft): 442 """Enable/disable opacity sliders (convenience). 443 444 Arg: 445 boolean_base (bool): True is enabled; False is disabled. 446 boolean_topright (bool): True is enabled; False is disabled. 447 boolean_bottomright (bool): True is enabled; False is disabled. 448 boolean_bottomleft (bool): True is enabled; False is disabled. 449 """ 450 self.set_enabled_base(boolean_base) 451 self.set_enabled_topright(boolean_topright) 452 self.set_enabled_bottomright(boolean_bottomright) 453 self.set_enabled_bottomleft(boolean_bottomleft) 454 455 def set_enabled_base(self, boolean): 456 """bool: Enable/disable opacity slider and label for base of SplitView.""" 457 self.slider_base.setEnabled(boolean) 458 self.label_base.setEnabled(boolean) 459 460 def set_enabled_topright(self, boolean): 461 """bool: Enable/disable opacity slider and label for top-right of SplitView.""" 462 self.slider_topright.setEnabled(boolean) 463 self.label_topright.setEnabled(boolean) 464 465 def set_enabled_bottomright(self, boolean): 466 """bool: Enable/disable opacity slider and label for bottom-right of SplitView.""" 467 self.slider_bottomright.setEnabled(boolean) 468 self.label_bottomright.setEnabled(boolean) 469 470 def set_enabled_bottomleft(self, boolean): 471 """bool: Enable/disable opacity slider and label for bottom-left of SplitView.""" 472 self.slider_bottomleft.setEnabled(boolean) 473 self.label_bottomleft.setEnabled(boolean) 474 475 def setMouseTracking(self, flag): 476 """PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.""" 477 def recursive_set(parent): 478 for child in parent.findChildren(QtCore.QObject): 479 try: 480 child.setMouseTracking(flag) 481 except: 482 pass 483 recursive_set(child) 484 QtWidgets.QWidget.setMouseTracking(self, flag) 485 recursive_set(self)
Interface for changing the transparency (opposite of opacity) of the overlay images of a SplitView.
Instantiate without input. See Butterfly Viewer for implementation.
423 def update_sliders(self, opacity_base, opacity_topright, opacity_bottomright, opacity_bottomleft): 424 """Update the values of the opacity sliders. 425 426 Arg: 427 opacity_topright (int): Set the value of the slider for top-right of SplitView (0-100). 428 opacity_bottomright (int): Set the value of the slider for bottom-right slider of SplitView (0-100). 429 opacity_bottomleft (int): Set the value of the slider for bottom-left slider of SplitView (0-100). 430 """ 431 self.slider_base.set_value(opacity_base) 432 self.slider_topright.set_value(opacity_topright) 433 self.slider_bottomright.set_value(opacity_bottomright) 434 self.slider_bottomleft.set_value(opacity_bottomleft)
Update the values of the opacity sliders.
Arg: opacity_topright (int): Set the value of the slider for top-right of SplitView (0-100). opacity_bottomright (int): Set the value of the slider for bottom-right slider of SplitView (0-100). opacity_bottomleft (int): Set the value of the slider for bottom-left slider of SplitView (0-100).
436 def reset_sliders(self): 437 """Reset all sliders to 100 and disable them (convenience).""" 438 self.update_sliders(100, 100, 100, 100) 439 self.set_enabled(False, False, False, False)
Reset all sliders to 100 and disable them (convenience).
441 def set_enabled(self, boolean_base, boolean_topright, boolean_bottomright, boolean_bottomleft): 442 """Enable/disable opacity sliders (convenience). 443 444 Arg: 445 boolean_base (bool): True is enabled; False is disabled. 446 boolean_topright (bool): True is enabled; False is disabled. 447 boolean_bottomright (bool): True is enabled; False is disabled. 448 boolean_bottomleft (bool): True is enabled; False is disabled. 449 """ 450 self.set_enabled_base(boolean_base) 451 self.set_enabled_topright(boolean_topright) 452 self.set_enabled_bottomright(boolean_bottomright) 453 self.set_enabled_bottomleft(boolean_bottomleft)
Enable/disable opacity sliders (convenience).
Arg: boolean_base (bool): True is enabled; False is disabled. boolean_topright (bool): True is enabled; False is disabled. boolean_bottomright (bool): True is enabled; False is disabled. boolean_bottomleft (bool): True is enabled; False is disabled.
455 def set_enabled_base(self, boolean): 456 """bool: Enable/disable opacity slider and label for base of SplitView.""" 457 self.slider_base.setEnabled(boolean) 458 self.label_base.setEnabled(boolean)
bool: Enable/disable opacity slider and label for base of SplitView.
460 def set_enabled_topright(self, boolean): 461 """bool: Enable/disable opacity slider and label for top-right of SplitView.""" 462 self.slider_topright.setEnabled(boolean) 463 self.label_topright.setEnabled(boolean)
bool: Enable/disable opacity slider and label for top-right of SplitView.
465 def set_enabled_bottomright(self, boolean): 466 """bool: Enable/disable opacity slider and label for bottom-right of SplitView.""" 467 self.slider_bottomright.setEnabled(boolean) 468 self.label_bottomright.setEnabled(boolean)
bool: Enable/disable opacity slider and label for bottom-right of SplitView.
470 def set_enabled_bottomleft(self, boolean): 471 """bool: Enable/disable opacity slider and label for bottom-left of SplitView.""" 472 self.slider_bottomleft.setEnabled(boolean) 473 self.label_bottomleft.setEnabled(boolean)
bool: Enable/disable opacity slider and label for bottom-left of SplitView.
475 def setMouseTracking(self, flag): 476 """PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.""" 477 def recursive_set(parent): 478 for child in parent.findChildren(QtCore.QObject): 479 try: 480 child.setMouseTracking(flag) 481 except: 482 pass 483 recursive_set(child) 484 QtWidgets.QWidget.setMouseTracking(self, flag) 485 recursive_set(self)
PyQt flag: Override mouse tracking to set mouse tracking for all children widgets.
549class SplitViewManager(QtWidgets.QWidget): 550 """Interface for shortcut buttons to position and lock the split in a sliding overlay. 551 552 Instantiate without input. See Butterfly Viewer for implementation. 553 554 Features: 555 Button to lock and unlock the split of a sliding overlay. 556 Arrow buttons position the split to shortcut parts of the image (center, top-right, bottom-left, etc.). 557 """ 558 559 hovered_xy = QtCore.pyqtSignal(float,float) 560 clicked_xy = QtCore.pyqtSignal(float,float) 561 lock_split_locked = QtCore.pyqtSignal() 562 lock_split_unlocked = QtCore.pyqtSignal() 563 564 def __init__(self, parent=None): 565 super().__init__() 566 567 self.lock_split_pushbutton = QtWidgets.QToolButton() 568 self.lock_split_pushbutton.setText("Lock Overlay (Shift·X)") 569 self.lock_split_pushbutton.setToolTip("Lock the split position of the active sliding overlay") 570 self.lock_split_pushbutton.setStyleSheet(""" 571 QToolButton { 572 color: white; 573 background-color: rgba(0, 0, 0, 91); 574 border-width: 0.06em; 575 border-style: solid; 576 border-color: white; 577 border-radius: 0.13em; 578 font-size: 11pt; 579 } 580 QToolButton:hover { 581 background-color: rgba(223, 166, 0, 223); 582 } 583 QToolButton:pressed { 584 color: white; 585 background-color: rgba(255, 181, 0, 255); 586 } 587 QToolButton:checked { 588 color: white; 589 background-color: rgba(191, 151, 0, 191); 590 border-color: rgba(255, 181, 0, 255); 591 } 592 QToolButton:checked:hover { 593 color: white; 594 background-color: rgba(223, 166, 0, 223); 595 border-color: rgba(255, 181, 0, 255); 596 } 597 QToolButton:checked:pressed { 598 color: white; 599 background-color: rgba(255, 181, 0, 255); 600 border-color: rgba(255, 181, 0, 255); 601 } 602 """) 603 self.lock_split_pushbutton.setCheckable(True) 604 self.lock_split_pushbutton.toggled.connect(self.on_toggle_lock_split_pushbutton) 605 lock_split_layout = QtWidgets.QGridLayout() 606 lock_split_layout.addWidget(self.lock_split_pushbutton) 607 lock_split_widget = QtWidgets.QWidget() 608 lock_split_widget.setLayout(lock_split_layout) 609 610 layout_buttons = QtWidgets.QGridLayout() 611 layout_buttons.setContentsMargins(0,0,0,0) 612 layout_buttons.setSpacing(0) 613 614 layout_buttons.setRowStretch(0,1) 615 layout_buttons.setColumnStretch(0,1) 616 617 self.topleft_pushbutton = PushbuttonSplitViewSet(text="⇖", x=0.0, y=0.0) 618 self.topleft_pushbutton.setToolTip("Top left of sliding overlay (move and lock)") 619 self.topleft_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 620 self.topleft_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 621 622 self.topcenter_pushbutton = PushbuttonSplitViewSet(text="⥣", x=0.5, y=0.0) 623 self.topcenter_pushbutton.setToolTip("Top center of sliding overlay (move and lock)") 624 self.topcenter_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 625 self.topcenter_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 626 627 self.topright_pushbutton = PushbuttonSplitViewSet(text="⇗", x=1.0, y=0.0) 628 self.topright_pushbutton.setToolTip("Top right of sliding overlay (move and lock)") 629 self.topright_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 630 self.topright_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 631 632 self.middleleft_pushbutton = PushbuttonSplitViewSet(text="⥢", x=0.0, y=0.5) 633 self.middleleft_pushbutton.setToolTip("Middle left of sliding overlay (move and lock)") 634 self.middleleft_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 635 self.middleleft_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 636 637 self.middlecenter_pushbutton = PushbuttonSplitViewSet(text="⧾", x=0.5, y=0.5) 638 self.middlecenter_pushbutton.setToolTip("Middle center of sliding overlay (move and lock)") 639 self.middlecenter_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 640 self.middlecenter_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 641 642 self.middleright_pushbutton = PushbuttonSplitViewSet(text="⥤", x=1.0, y=0.5) 643 self.middleright_pushbutton.setToolTip("Middle right of sliding overlay (move and lock)") 644 self.middleright_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 645 self.middleright_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 646 647 self.bottomleft_pushbutton = PushbuttonSplitViewSet(text="⇙", x=0.0, y=1.0) 648 self.bottomleft_pushbutton.setToolTip("Bottom left of sliding overlay (move and lock)") 649 self.bottomleft_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 650 self.bottomleft_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 651 652 self.bottomcenter_pushbutton = PushbuttonSplitViewSet(text="⥥", x=0.5, y=1.0) 653 self.bottomcenter_pushbutton.setToolTip("Bottom center of sliding overlay (move and lock)") 654 self.bottomcenter_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 655 self.bottomcenter_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 656 657 self.bottomright_pushbutton = PushbuttonSplitViewSet(text="⇘", x=1.0, y=1.0) 658 self.bottomright_pushbutton.setToolTip("Bottom right of sliding overlay (move and lock)") 659 self.bottomright_pushbutton.hovered_xy.connect(self.on_hovered_set_pushbutton) 660 self.bottomright_pushbutton.clicked_xy.connect(self.on_clicked_set_pushbutton) 661 662 663 layout_buttons.addWidget(self.topleft_pushbutton,1,1) 664 layout_buttons.addWidget(self.topcenter_pushbutton,1,2) 665 layout_buttons.addWidget(self.topright_pushbutton,1,3) 666 layout_buttons.addWidget(self.middleleft_pushbutton,2,1) 667 layout_buttons.addWidget(self.middlecenter_pushbutton,2,2) 668 layout_buttons.addWidget(self.middleright_pushbutton,2,3) 669 layout_buttons.addWidget(self.bottomleft_pushbutton,3,1) 670 layout_buttons.addWidget(self.bottomcenter_pushbutton,3,2) 671 layout_buttons.addWidget(self.bottomright_pushbutton,3,3) 672 673 layout_buttons.setRowStretch(layout_buttons.rowCount(),1) 674 layout_buttons.setColumnStretch(layout_buttons.columnCount(),1) 675 676 layout = QtWidgets.QGridLayout() 677 layout.addWidget(lock_split_widget, 0, 0) 678 layout.addLayout(layout_buttons, 1, 0) 679 680 contents_margins_w = 8 681 layout.setContentsMargins(contents_margins_w, 0, contents_margins_w, 0) 682 layout.setSpacing(0) 683 684 self.setLayout(layout) 685 self.setContentsMargins(0,0,0,0) 686 687 def on_toggle_lock_split_pushbutton(self, boolean): 688 """Signal the locking and unlocking of the split and set the text elements of the lock button. 689 690 Triggered when the lock button is toggled on and off (locked and unlocked). 691 692 Args: 693 boolean (bool): True for locking split; False for unlocking split. 694 """ 695 if boolean: 696 text = "Unlock Overlay (Shift·X)" 697 tooltip = "Unlock the split position of the active sliding overlay" 698 self.lock_split_locked.emit() 699 else: 700 text = "Lock Overlay (Shift·X)" 701 tooltip = "Lock the split position of the active sliding overlay" 702 self.lock_split_unlocked.emit() 703 self.lock_split_pushbutton.setText(text) 704 self.lock_split_pushbutton.setToolTip(tooltip) 705 706 def on_hovered_set_pushbutton(self, x_percent, y_percent): 707 """Signal the position of the split to temporarily move the split. 708 709 Triggered when the shortcut arrow buttons are hovered. 710 Needed to temporarily signal the moving of the split when the user hovers over a split shortcut button. 711 712 Args: 713 x_percent (float): The position of the split as a proportion (0-1) of the base image's horizontal resolution. 714 y_percent (float): The position of the split as a proportion (0-1) of the base image's vertical resolution. 715 """ 716 self.hovered_xy.emit(x_percent, y_percent) 717 718 def on_clicked_set_pushbutton(self, x_percent, y_percent): 719 """Signal the clicking of the lock button with a given split position. 720 721 Args: 722 x_percent (float): The position of the split as a proportion (0-1) of the base image's horizontal resolution. 723 y_percent (float): The position of the split as a proportion (0-1) of the base image's vertical resolution. 724 """ 725 self.clicked_xy.emit(x_percent, y_percent)
Interface for shortcut buttons to position and lock the split in a sliding overlay.
Instantiate without input. See Butterfly Viewer for implementation.
Features:
Button to lock and unlock the split of a sliding overlay. Arrow buttons position the split to shortcut parts of the image (center, top-right, bottom-left, etc.).