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)
class FourDragDropImageLabelForSplitView(aux_dragdrop.FourDragDropImageLabel):
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()

def set_addable_all_except_main(self, boolean):
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.
def on_main_topleft_occupied(self, boolean):
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).
class SplitViewCreator(PyQt5.QtWidgets.QFrame):
 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.

def setMouseTracking(self, flag):
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.

def display_loading_grayout(self, boolean, text='Loading...', pseudo_load_time=0.2):
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.
class SliderDeluxe(PyQt5.QtWidgets.QWidget):
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").
@QtCore.pyqtSlot(int)
def on_slider_changed(self, value):
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.
def on_slider_trigger(self, action):
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.

def on_spinbox_change(self, value):
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())

int: Signal the slider position when spinbox is changed.

def set_value(self, value):
338    def set_value(self, value):
339        """int (0-100): Set value of slider."""
340        self.slider.setValue(value)

int (0-100): Set value of slider.

class SlidersOpacitySplitViews(PyQt5.QtWidgets.QFrame):
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.

def update_sliders( self, opacity_base, opacity_topright, opacity_bottomright, opacity_bottomleft):
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).

def reset_sliders(self):
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).

def set_enabled( self, boolean_base, boolean_topright, boolean_bottomright, boolean_bottomleft):
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.

def set_enabled_base(self, boolean):
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.

def set_enabled_topright(self, boolean):
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.

def set_enabled_bottomright(self, boolean):
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.

def set_enabled_bottomleft(self, boolean):
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.

def setMouseTracking(self, flag):
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.

class PushbuttonSplitViewSet(PyQt5.QtWidgets.QPushButton):
489class PushbuttonSplitViewSet(QtWidgets.QPushButton):
490    """Custom QPushButton for buttons in SplitViewManager to set the position of the split in a SplitView.
491    
492    Args:
493        text (str): The text icon of the split shortcut.
494        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.
495        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.
496    """
497
498    hovered_xy = QtCore.pyqtSignal(float,float)
499    clicked_xy = QtCore.pyqtSignal(float,float)
500
501    def __init__(self, parent=None, text=None, x=None, y=None):
502        super().__init__()
503        self.setText(text)
504        self.x = x
505        self.y = y
506        self.clicked.connect(self.on_clicked)
507
508        self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
509        self.setStyleSheet("""
510            QPushButton {
511                width: 0.85em;
512                height: 0.85em;
513                color: white;
514                background-color: rgba(0, 0, 0, 63);
515                border: 0px black;
516                font-size: 20pt;
517                border-radius: 0.06em;
518            }
519            QPushButton:hover {
520                background-color: rgba(223, 166, 0, 223);
521            }
522            QPushButton:pressed {
523                color: white;
524                background-color: rgba(255, 181, 0, 255);
525            }
526            """)
527            
528    def enterEvent(self, event):
529        """Override enterEvent to signal the shortcutted position of the button when hovered by the mouse.
530        
531        Allows the user to preview the split shortcut without needing to click and lock.
532        
533        Args:
534            event (PyQt event)
535        """
536        self.hovered_xy.emit(self.x, self.y)
537        pass
538
539    def leaveEvent(self, event):
540        """event: Override leaveEvent to pass when mouse leaves area of the button."""
541        pass
542
543    def on_clicked(self):
544        """Signal the shortcutted position of the button when clicked."""
545        self.clicked_xy.emit(self.x, self.y)

Custom QPushButton for buttons in SplitViewManager to set the position of the split in a SplitView.

Arguments:
  • text (str): The text icon of the split shortcut.
  • 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.
  • 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.
def enterEvent(self, event):
528    def enterEvent(self, event):
529        """Override enterEvent to signal the shortcutted position of the button when hovered by the mouse.
530        
531        Allows the user to preview the split shortcut without needing to click and lock.
532        
533        Args:
534            event (PyQt event)
535        """
536        self.hovered_xy.emit(self.x, self.y)
537        pass

Override enterEvent to signal the shortcutted position of the button when hovered by the mouse.

Allows the user to preview the split shortcut without needing to click and lock.

Arguments:
  • event (PyQt event)
def leaveEvent(self, event):
539    def leaveEvent(self, event):
540        """event: Override leaveEvent to pass when mouse leaves area of the button."""
541        pass

event: Override leaveEvent to pass when mouse leaves area of the button.

def on_clicked(self):
543    def on_clicked(self):
544        """Signal the shortcutted position of the button when clicked."""
545        self.clicked_xy.emit(self.x, self.y)

Signal the shortcutted position of the button when clicked.

class SplitViewManager(PyQt5.QtWidgets.QWidget):
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.).

def on_toggle_lock_split_pushbutton(self, boolean):
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)

Signal the locking and unlocking of the split and set the text elements of the lock button.

Triggered when the lock button is toggled on and off (locked and unlocked).

Arguments:
  • boolean (bool): True for locking split; False for unlocking split.
def on_hovered_set_pushbutton(self, x_percent, y_percent):
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)

Signal the position of the split to temporarily move the split.

Triggered when the shortcut arrow buttons are hovered. Needed to temporarily signal the moving of the split when the user hovers over a split shortcut button.

Arguments:
  • x_percent (float): The position of the split as a proportion (0-1) of the base image's horizontal resolution.
  • y_percent (float): The position of the split as a proportion (0-1) of the base image's vertical resolution.
def on_clicked_set_pushbutton(self, x_percent, y_percent):
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)

Signal the clicking of the lock button with a given split position.

Arguments:
  • x_percent (float): The position of the split as a proportion (0-1) of the base image's horizontal resolution.
  • y_percent (float): The position of the split as a proportion (0-1) of the base image's vertical resolution.